Shiro เป็นกรอบการควบคุมการอนุญาตที่มีน้ำหนักเบาพร้อมแอพพลิเคชั่นที่หลากหลาย จุดเน้นของบทความนี้คือการแนะนำการรวมกลุ่มของ Shiro ของฤดูใบไม้ผลิและเพื่อเปิดใช้งานพารามิเตอร์แบบไดนามิกเช่น @RequiresRoles ที่จะได้รับการสนับสนุนโดยการขยายการแสดงออกของ EL ของฤดูใบไม้ผลิ บทนำสู่ชิโร่ไม่ได้อยู่ในขอบเขตของบทความนี้ หากผู้อ่านไม่ทราบเกี่ยวกับชิโร่มากนักพวกเขาสามารถเรียนรู้ข้อมูลที่เกี่ยวข้องผ่านเว็บไซต์อย่างเป็นทางการ นอกจากนี้ยังมีบทความเกี่ยวกับ InfoQ ที่ให้การแนะนำที่ครอบคลุมถึง Shiro และขอแนะนำอย่างเป็นทางการ ที่อยู่คือ https://www.infoq.com/articles/apache-shiro
ชิโร่รวมสปริง
ก่อนอื่นคุณต้องเพิ่ม Shiro-Spring-xxx.jar ลงในโครงการของคุณ หากคุณใช้ Maven เพื่อจัดการโครงการของคุณคุณสามารถเพิ่มการพึ่งพาต่อไปนี้ในการพึ่งพาของคุณ นี่คือเวอร์ชันล่าสุด 1.4.0 ที่ฉันเลือก
<Effercing> <roupId> org.apache.shiro </groupId> <ratifactid> Shiro-spring </artifactid> <version> 1.4.0 </version>
ถัดไปคุณจะต้องกำหนด shirofilter ใน web.xml ของคุณและนำไปใช้เพื่อสกัดกั้นคำขอทั้งหมดที่ต้องมีการควบคุมการอนุญาตซึ่งมักจะกำหนดค่าเป็น /* นอกจากนี้จะต้องมีการเพิ่มตัวกรองไปที่ด้านหน้าเพื่อให้แน่ใจว่าคำขอจะถูกควบคุมครั้งแรกผ่านการอนุญาตของ Shiro หลังจากเข้ามาคลาสของตัวกรองที่สอดคล้องกันที่นี่ได้รับการกำหนดค่าด้วย DelegatingFilterProxy ซึ่งเป็นพร็อกซีตัวกรองที่ให้ไว้ในฤดูใบไม้ผลิ คุณสามารถใช้ถั่วในภาชนะถั่วสปริงเป็นอินสแตนซ์ตัวกรองปัจจุบันและถั่วที่เกี่ยวข้องจะใช้ถั่วที่สอดคล้องกับชื่อตัวกรอง ดังนั้นการกำหนดค่าต่อไปนี้จะมองหาถั่วที่ชื่อ Shirofilter ในภาชนะถั่ว
<silter> <filter-name> shirofilter </filter-name> <silter-class> org.springframework.web.filter.delegatingFilterproxy </filter-class> <int-param> </init-param> </filter> <filter-mapping> <filter-name> shirofilter </filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
เมื่อใช้ชิโร่อย่างอิสระคุณมักจะกำหนด org.apache.shiro.web.servlet.shirofilter เพื่อทำสิ่งที่คล้ายกัน
ถัดไปคือการกำหนด shirofilter ของเราในภาชนะถั่ว ดังต่อไปนี้เรากำหนด ShirofilterFactoryBean ซึ่งจะผลิตถั่วชนิดนามธรรม ผ่าน ShirofilterFactoryBean เราสามารถระบุ SecurityManager ได้ DefaultWebsecurityManager ที่ใช้ที่นี่จำเป็นต้องระบุอาณาจักร หากจำเป็นต้องระบุอาณาจักรหลายแห่งจะมีการระบุผ่านอาณาจักร เพื่อความเรียบง่ายเราใช้ TextConfigurationRealm ตามนิยามข้อความโดยตรง ใช้ LoginURL เพื่อระบุที่อยู่เข้าสู่ระบบ SuccessURL เพื่อระบุที่อยู่ที่ต้องเปลี่ยนเส้นทางหลังจากเข้าสู่ระบบสำเร็จและไม่ได้รับอนุญาตเพื่อระบุหน้าพรอมต์เมื่อสิทธิ์ไม่เพียงพอ FilterChainDefinitions กำหนดความสัมพันธ์ระหว่าง URL และตัวกรองที่จะใช้ นามแฝงตัวกรองทางด้านขวาของเครื่องหมายเท่ากันคือนามแฝงตัวกรอง นามแฝงเริ่มต้นถูกกำหนดไว้ในคลาสการแจงนับ org.apache.shiro.web.filter.mgt.defaultfilter
<bean id = "shirofilter"> <property name = "SecurityManager" ref = "SecurityManager"/> <property name = "loginurl" value = "/login.jsp"/> <property name = "successUrl" value = "/home.jsp"/> value = "/unauthorized.jsp"/> <property name = "FilterChaInDefinitions"> <value>/admin/** = Authc, บทบาท [admin]/logout = logout # ที่อยู่อื่น ๆ ต้องการให้ผู้ใช้เข้าสู่ระบบ/** = authc, logger </value> ref = "realm"/> </ebean> <bean id = "LifecyclecleBeanPostProcessor"/>
<!-เพื่อความเรียบง่ายเราจะใช้การใช้งานจริงตามข้อความที่นี่-> <bean id = "realm"> <property name = "userdefinitions"> <value> user1 = pass1, role1, role2 user2 = pass2, role2, role3 admin = admin, admin
หากคุณต้องการใช้ตัวกรองที่กำหนดเองในนิยาม FilterChainDefinitions คุณสามารถระบุตัวกรองที่กำหนดเองและความสัมพันธ์การแมปนามแฝงผ่านตัวกรองของ ShirofilterFactoryBean ตัวอย่างเช่นดังที่แสดงไว้ด้านล่างเราได้เพิ่มตัวกรองด้วยนามแฝงตัวบันทึกและตัวกรอง /** ที่ระบุด้วยตัวบันทึกนามแฝงใน FilterChainDefinitions
<bean id = "shirofilter"> <property name = "SecurityManager" ref = "SecurityManager"/> <property name = "loginurl" value = "/login.jsp"/> <property name = "successUrl" value = "/home.jsp"/> value = "/unauthorized.jsp"/> <property name = "ตัวกรอง"> <util: map> <entry key = "logger"> <bean/> </entry> </util: แผนที่> </คุณสมบัติ> <property name = "LogUt -filterChaIndinitions" Authc, logger </alue> </porement> </ebean>
ในความเป็นจริงคำนิยามนามแฝงตัวกรองที่เราจำเป็นต้องใช้สามารถกำหนดได้โดยตรงโดย setFilters ของ ShirofilterFactoryBean () แต่จะกำหนดตัวกรองที่สอดคล้องกันโดยตรงในคอนเทนเนอร์ถั่วที่สอดคล้องกัน เพราะโดยค่าเริ่มต้น ShirofilterFactoryBean จะลงทะเบียนถั่วประเภทตัวกรองทั้งหมดในภาชนะถั่วด้วยนามแฝง ID ในตัวกรอง ดังนั้นคำจำกัดความข้างต้นจึงเทียบเท่ากับสิ่งต่อไปนี้
<bean id = "shirofilter"> <property name = "SecurityManager" ref = "SecurityManager"/> <property name = "loginurl" value = "/login.jsp"/> <property name = "successUrl" value = "/home.jsp"/> value = "/unauthorized.jsp"/> <property name = "FilterChaInDefinitions"> <value>/admin/** = authc, บทบาท [admin]/logout = logout # ที่อยู่อื่น ๆ ต้องการให้ผู้ใช้เข้าสู่ระบบ/** = authc, logger </value> </
หลังจากขั้นตอนข้างต้นการรวมของชิโรและสปริงจะเสร็จสมบูรณ์ ในเวลานี้เส้นทางใด ๆ ที่เราร้องขอสำหรับโครงการจะต้องให้เราเข้าสู่ระบบและจะข้ามไปยังเส้นทางที่ระบุโดย LoginUrl โดยอัตโนมัติและให้เราป้อนชื่อผู้ใช้/รหัสผ่านเพื่อเข้าสู่ระบบในเวลานี้เราควรจัดทำแบบฟอร์มเพื่อให้ได้ชื่อผู้ใช้ ชื่อผู้ใช้/รหัสผ่านที่ใช้เมื่อเข้าสู่ระบบคือชื่อผู้ใช้/รหัสผ่านที่เรากำหนดไว้ใน TextConfigurationRealm จากการกำหนดค่าข้างต้นของเราคุณสามารถใช้ USER1/PASS1, ADMIN/ADMIN เป็นต้นหลังจากการเข้าสู่ระบบสำเร็จแล้วมันจะข้ามไปยังที่อยู่ที่ระบุโดยพารามิเตอร์ SuccessURL หากเราเข้าสู่ระบบโดยใช้ USER1/PASS1 เรายังสามารถลองเข้าถึง/ผู้ดูแลระบบ/ดัชนีและในเวลานี้เราจะข้ามไปที่ unauidized.jsp เนื่องจากการอนุญาตไม่เพียงพอ
เปิดใช้งานการสนับสนุนตามคำอธิบายประกอบ
การรวมขั้นพื้นฐานกำหนดให้เราต้องกำหนดการควบคุมการอนุญาตทั้งหมดที่ URL จำเป็นต้องใช้ใน FilterChainDefinitions ของ ShirofilterFactoryBean บางครั้งนี่ก็ไม่ยืดหยุ่น Shiro ให้คำอธิบายประกอบที่สามารถใช้หลังจากรวมฤดูใบไม้ผลิ มันช่วยให้เราสามารถเพิ่มคำอธิบายประกอบที่สอดคล้องกันในคลาสหรือวิธีการที่ต้องการการควบคุมการอนุญาตเพื่อกำหนดสิทธิ์ที่จำเป็นในการเข้าถึงคลาสหรือวิธีการ หากอยู่ในชั้นเรียนในคำจำกัดความหมายความว่าการเรียกใช้วิธีทั้งหมดในชั้นเรียนต้องใช้สิทธิ์ที่สอดคล้องกัน (โปรดทราบว่าจำเป็นต้องมีการโทรภายนอกซึ่งเป็นข้อ จำกัด ของพร็อกซีแบบไดนามิก) ในการใช้คำอธิบายประกอบเหล่านี้เราจำเป็นต้องเพิ่มคำจำกัดความถั่วสองใบต่อไปนี้ลงในคอนเทนเนอร์ถั่วของฤดูใบไม้ผลิเพื่อให้เราสามารถพิจารณาได้ว่าผู้ใช้มีสิทธิ์ที่สอดคล้องกันตามคำจำกัดความคำอธิบายประกอบที่รันไทม์หรือไม่ นี่คือความสำเร็จผ่านกลไก AOP ของฤดูใบไม้ผลิ หากคุณไม่ทราบอะไรเกี่ยวกับฤดูใบไม้ผลิ AOP คุณสามารถอ้างถึงคอลัมน์ "Spring AOP Introduction" ของผู้แต่งที่เขียนโดยผู้เขียน คำจำกัดความของถั่วสองข้อต่อไปนี้ AuthorizationAttributesourceAdVisor กำหนดที่ปรึกษาซึ่งจะสกัดกั้นและตรวจสอบการอนุญาตตามวิธีการกำหนดค่าคำอธิบายประกอบที่จัดทำโดย Shiro DefaultAdvisorautoproxycreator ให้ฟังก์ชั่นของการสร้างวัตถุพร็อกซีสำหรับคลาสที่ทำเครื่องหมายด้วยคำอธิบายประกอบการควบคุมการอนุญาตโดย Shiro และการใช้ AvalityAttributesourceAdvisor เมื่อสกัดกั้นการเรียกวิธีการเป้าหมาย เมื่อคำขอจากผู้ใช้ถูกสกัดกั้นและผู้ใช้ไม่ได้รับอนุญาตทำเครื่องหมายไว้ในวิธีการที่สอดคล้องกันหรือคลาส org.apache.shiro.authz.authorizationException จะถูกโยนออกไป
<ถั่วขึ้นอยู่กับ = "LifeCycleBeanPostProcessor"/> <bean> <property name = "SecurityManager" ref = "SecurityManager" // </ebean>
ถ้า <aop:config/>或<aop:aspectj-autoproxy/> ถูกกำหนดไว้แล้วในคอนเทนเนอร์ถั่วของเราแล้ว defaultadvisorautoproxycreator ไม่สามารถกำหนดได้อีกต่อไป เนื่องจากสองกรณีก่อนหน้านี้จะเพิ่มถั่วโดยอัตโนมัติคล้ายกับ DefaultAdvisorautoproxycreator สำหรับข้อมูลเพิ่มเติมเกี่ยวกับ DefaultAdvisorautoproxycreator คุณสามารถอ้างถึงหลักการของผู้เขียนในการสร้างวัตถุพร็อกซีโดยอัตโนมัติในฤดูใบไม้ผลิ AOP
คำอธิบายประกอบการควบคุมการอนุญาตที่จัดทำโดย Shiro มีดังนี้:
ต้องการการกำหนดค่า: ผู้ใช้จะต้องได้รับการรับรองความถูกต้องในเซสชันปัจจุบันนั่นคือเขาต้องเข้าสู่ระบบด้วยชื่อผู้ใช้/รหัสผ่านและไม่รวมถึงการเข้าสู่ระบบอัตโนมัติ
ต้องใช้ผู้ใช้: ผู้ใช้จะต้องได้รับการรับรองความถูกต้อง สามารถตรวจสอบความถูกต้องได้โดยการเข้าสู่ระบบด้วยชื่อผู้ใช้/รหัสผ่านในเซสชันนี้หรือสามารถลงชื่อเข้าใช้โดยอัตโนมัติด้วย RememberMe
ต้องการความต้องการ: ผู้ใช้ไม่ได้ลงชื่อเข้าใช้
ต้องมีความต้องการ: ผู้ใช้ต้องการให้บทบาทที่ระบุเป็นเจ้าของ
ต้องใช้ประโยชน์: ผู้ใช้ต้องการสิทธิ์ที่ระบุ
สามคนแรกเข้าใจง่ายในขณะที่สองคนสุดท้ายคล้ายกัน ที่นี่ฉันใช้ @requirespermissions เป็นตัวอย่าง ก่อนอื่นให้เปลี่ยนอาณาจักรที่กำหนดไว้ข้างต้นและเพิ่มสิทธิ์ในบทบาท ด้วยวิธีนี้ผู้ใช้ของเรา 1 จะได้รับอนุญาตให้ PERV1, PRASP2 และ PERM3 และ USER2 จะได้รับอนุญาตให้ PERV1, PERM3 และ PERV4
<bean id = "Realm"> <property name = "UserDefinitions"> <dange> user1 = pass1, role1, role2 user2 = pass2, role2, role3 admin = admin, admin, admin </value>
@RequiresPermissions สามารถเพิ่มในวิธีการเพื่อระบุสิทธิ์ที่ต้องอ้างสิทธิ์เมื่อเรียกวิธีการ ในรหัสต่อไปนี้เราระบุว่าต้องได้รับอนุญาตจาก PERC1 เมื่อเข้าถึง /PERM1 ในเวลานี้ทั้ง User1 และ User2 สามารถเข้าถึงได้
@RequestMapping ("/perm1")@headspermissions ("perm1") การอนุญาตวัตถุสาธารณะ 1 () {return "perm1";}หากคุณต้องการระบุว่าคุณต้องมีการอนุญาตหลายอย่างในเวลาเดียวกันเพื่อเข้าถึงวิธีการคุณสามารถระบุสิทธิ์ที่คุณต้องระบุในรูปแบบของอาร์เรย์ (เมื่อระบุแอตทริบิวต์อาร์เรย์เดียวบนคำอธิบายประกอบคุณไม่สามารถเพิ่มวงเล็บปีกกา ตัวอย่างเช่นดังต่อไปนี้เราระบุว่าเมื่อเข้าถึง /Perm1andPerm4 ผู้ใช้จะต้องมีการอนุญาตทั้ง PERM1 และ PERM4 ในเวลานี้มีเพียง User2 เท่านั้นที่สามารถเข้าถึงได้เพราะมีเพียง Perm1 และ Perm4 ในเวลาเดียวกัน
@RequestMapping ("/perm1andperm4")@headspermissions ({"perm1", "perm4"}) วัตถุสาธารณะ perm1andperm4 () {return "perm1andperm4";}เมื่อมีการระบุการอนุญาตหลายรายการในเวลาเดียวกันความสัมพันธ์ระหว่างการอนุญาตหลายครั้งคือความสัมพันธ์นั่นคือการอนุญาตทั้งหมดที่ระบุในเวลาเดียวกันจะต้องใช้ หากคุณต้องการเพียงหนึ่งในการอนุญาตหลายสิทธิ์ที่ระบุไว้เพื่อให้สามารถเข้าถึงได้เราสามารถระบุความสัมพันธ์ระหว่างหรือระหว่างการอนุญาตหลายครั้งผ่าน logical = logical.or.or ตัวอย่างเช่นดังนี้เราระบุว่าเมื่อเข้าถึง /Perm1orPerm4 คุณจะต้องมีสิทธิ์ PERM1 หรือ PERV4 เท่านั้นเพื่อให้ทั้ง User1 และ User2 สามารถเข้าถึงวิธีนี้ได้
@RequestMapping ("/perm1orperm4")@ต้องการการใช้งาน (value = {"perm1", "perm4"}, logical = logical.or) วัตถุสาธารณะ perm1orperm4 () {return "perm1orperm4";}@requirespermissions สามารถทำเครื่องหมายในชั้นเรียนซึ่งระบุว่าเมื่อเข้าถึงวิธีการในชั้นเรียนภายนอกคุณต้องมีสิทธิ์ที่สอดคล้องกัน ตัวอย่างเช่นในต่อไปนี้เราระบุว่าเราจำเป็นต้องมีสิทธิ์อนุญาต 2 ในระดับคลาสในขณะที่วิธีการดัชนี () ไม่ได้ระบุว่าเราต้องการสิทธิ์ใด ๆ แต่เรายังคงต้องมีการระบุสิทธิ์ที่ระดับคลาสเมื่อเข้าถึงวิธีนี้ ในเวลานี้มีเพียงผู้ใช้ 1 เท่านั้นที่สามารถเข้าถึงได้
@restcontroller@requestmapping ("/foo")@quiredpermissions ("perm2") คลาสสาธารณะ foocontroller {@requestmapping (method = requestmethod.get) ดัชนีวัตถุสาธารณะ () {แผนที่ <สตริง, วัตถุ> แผนที่ = new hashmap <> (); map.put ("ABC", 123); แผนที่กลับ; -เมื่อทั้งระดับคลาสและวิธีการมี @requirespermissions ระดับวิธีการมีลำดับความสำคัญสูงกว่าและเฉพาะการอนุญาตตามระดับวิธีการจะได้รับการตรวจสอบในเวลานี้ ดังต่อไปนี้เราระบุว่าจำเป็นต้องได้รับอนุญาต PERC2 ในระดับชั้นเรียนและจำเป็นต้องได้รับอนุญาต PERC3 ในระดับวิธี จากนั้นเมื่อเข้าถึง /foo คุณจะต้องมีสิทธิ์ PERM3 เพื่อเข้าถึงวิธีการดัชนี () ดังนั้นในเวลานี้ทั้ง User1 และ User2 สามารถเข้าถึง /foo
@restcontroller @requestmapping ("/foo") @headspermissions ("perm2") ชั้นเรียนสาธารณะ foocontroller {@requestmapping (วิธีการ = requestMethod.get) @requirespermissions ("Perm3") ดัชนีวัตถุสาธารณะ () {แผนที่ <สตริง map.put ("ABC", 123); แผนที่กลับ; -อย่างไรก็ตามหากเราเพิ่ม @RequiresRoles ("Role1") ในชั้นเรียนในเวลานี้เพื่อระบุว่าเราจำเป็นต้องมีบทบาทบทบาท 1 จากนั้นเมื่อเข้าถึง /FOO เราจำเป็นต้องมี Role1 ที่ระบุโดย @requirespermissions ("PERM3") บน ROL1 บนดัชนี () ในชั้นเรียน เนื่องจากต้องการความต้องการและความต้องการที่เป็นของคำจำกัดความการอนุญาตของมิติที่แตกต่างกันชิโรจะตรวจสอบพวกเขาหนึ่งครั้งในระหว่างการตรวจสอบ แต่ถ้าทั้งคลาสและวิธีการมีคำอธิบายประกอบของคำจำกัดความการควบคุมการอนุญาตประเภทเดียวกันคำจำกัดความในวิธีการจะขึ้นอยู่กับคำจำกัดความเท่านั้น
@restcontroller@requestmapping ("/foo")@ต้องการการใช้งาน ("perm2")@ต้องการ ("role1") คลาสสาธารณะ foocontroller {@requestmapping (method = requestmethod.get) @requirespermissions map.put ("ABC", 123); แผนที่กลับ; -แม้ว่าตัวอย่างจะใช้เพียงต้องการการใช้งาน แต่การใช้คำอธิบายประกอบการควบคุมการอนุญาตอื่น ๆ ก็คล้ายกัน โปรดใช้คำอธิบายประกอบอื่น ๆ โดยเพื่อนที่สนใจ
หลักการของการควบคุมคำอธิบายประกอบ
สิทธิ์ที่เราระบุไว้ข้างต้นนั้นเป็นแบบคงที่โดยใช้ @requirespermissions หนึ่งในวัตถุประสงค์หลักของบทความนี้คือการแนะนำวิธีการที่จะทำให้การอนุญาตที่ระบุแบบไดนามิกโดยการขยายการใช้งาน แต่ก่อนที่เราจะขยายเราต้องรู้ว่ามันทำงานอย่างไรนั่นคือหลักการดำเนินการก่อนที่เราจะสามารถขยายได้ ลองมาดูกันว่า Shiro รวมสปริงเข้ากับ @requirespermissions อย่างไร เมื่อเปิดใช้งานการสนับสนุน @RequiresPermissions เราจะกำหนดถั่วต่อไปนี้ซึ่งเป็นที่ปรึกษาซึ่งสืบทอดมาจาก StaticMethodmatcherPointCutAdvisor วิธีการจับคู่วิธีการของมันคือตราบใดที่คลาสหรือวิธีการมีคำอธิบายประกอบการควบคุมการอนุญาตหลายประการของ Shiro ตรรกะการประมวลผลหลังจากการสกัดกั้นถูกระบุโดยคำแนะนำที่เกี่ยวข้อง
<bean> <property name = "SecurityManager" ref = "SecurityManager"/> </ebean>
ต่อไปนี้เป็นซอร์สโค้ดของ AuthorizationAttributesourceAdvisor เราจะเห็นได้ว่าในวิธีการสร้าง AopallianceannotationsauthorizingMethodinterceptor ถูกระบุโดย SetAdvice () ซึ่งขึ้นอยู่กับการใช้ MethodInterceptor
การอนุญาตระดับสาธารณะในชั้นเรียนการปกครองของผู้เข้าร่วมการศึกษา ENTATICMETHODMATCHERPOINTCUTADVISOR {ส่วนตัว logger สุดท้าย logger สุดท้าย = loggerFactory.getLogger (AuthorizationAtTributesourceAdVisor.class); ชั้นสุดท้ายคงที่ระดับสุดท้าย <? ขยายคำอธิบายประกอบ> [] authz_annotation_classes = คลาสใหม่ [] {ต้องใช้ Permissions.class, chantesroles.class, ต้องใช้ผู้ใช้. Protected SecurityManager SecurityManager = NULL; Public AuthorizationAttributesourceAdVisor () {SetAdvice (ใหม่ AopallianceannotationsauthorizingMethodinterceptor ()); } Public SecurityManager GetSecurityManager () {return SecurityManager; } โมฆะสาธารณะ setSecurityManager (org.apache.shiro.mgt.securityManager MANYMANAGER) {this.SecurityManager = SecurityManager; } การจับคู่บูลีนสาธารณะ (วิธีการ, คลาส targetClass คลาส) {วิธี m = วิธี; if (isauthzannotationpresent (m)) {return true; } // พารามิเตอร์ 'วิธี' อาจมาจากอินเทอร์เฟซที่ไม่มีคำอธิบายประกอบ // ตรวจสอบเพื่อดูว่าการใช้งานมีหรือไม่ if (targetClass! = null) {ลอง {m = targetClass.getMethod (m.getName (), m.getParameterTypes ()); return isauthzannotationpresent (m) || isauthzannotationpresent (TargetClass); } catch (nosuchmethodexception ถูกละเว้น) {// ค่าคืนค่าเริ่มต้นเป็นเท็จ หากเราไม่พบวิธีการนั้นเห็นได้ชัดว่า // ไม่มีคำอธิบายประกอบดังนั้นเพียงใช้ค่าคืนค่าเริ่มต้น }} return false; } บูลีนส่วนตัว isauthzannotationpresent (คลาส <s?> targetClazz) {สำหรับ (คลาส <? ขยายคำอธิบายประกอบ> AnnClass: authz_annotation_classes) {คำอธิบายประกอบ A = Annotationutils.findannotation (TargetClazz, Annclass); if (a! = null) {return true; }} return false; } บูลีนส่วนตัว isauthzannotationpresent (วิธีการ) {สำหรับ (คลาส <? ขยายคำอธิบายประกอบ> Annclass: authz_annotation_classes) {คำอธิบายประกอบ A = Annotationutils.findannotation (วิธีการ Annclass); if (a! = null) {return true; }} return false; -ซอร์สโค้ดของ AopallianceannotationsauthorizingMethodinterceptor มีดังนี้ เมธอด Invoke ของอินเทอร์เฟซ MethodInterceptor ที่ใช้ใน IT เรียกใช้วิธีการเรียกใช้คลาสแม่ ในเวลาเดียวกันเราจำเป็นต้องเห็นว่าการใช้งานการใช้งาน AnnotAntationMethodinterceptor บางอย่างได้ถูกสร้างขึ้นในวิธีการสร้าง การใช้งานเหล่านี้เป็นแกนหลักของการควบคุมการอนุญาต ในภายหลังเราจะเลือกคลาสการใช้งาน PermissionAntationMethodinterceptor เพื่อดูตรรกะการใช้งานเฉพาะ
Public Class AopallianceannotationsauthorizingMethodinterceptor ขยายคำอธิบายประกอบการกำหนดค่าใช้จ่าย methodinterceptor {public aopallianceannotationsauthorizingMethodinterceptor () // ใช้ตัวแก้ไขคำอธิบายประกอบสปริงจำเพาะ - คำอธิบายประกอบของฤดูใบไม้ผลินั้นดีกว่ากระบวนการแก้ปัญหา // RAW JDK AnnotationResolver Resolver = New SpringannotationResolver (); // เราสามารถใช้อินสแตนซ์ตัวแก้ไขเดียวกันได้อีกครั้ง - มันไม่ได้รักษาสถานะ: interceptors.add (ใหม่ RoleanNotationMethodinterceptor (ตัวแก้ไข)); interceptors.add (ใหม่ PermissionAntationMethodinterceptor (Resolver)); interceptors.add (ผู้ใช้ใหม่ผู้ใช้งาน Methodinterceptor (Resolver)); interceptors.add (Guestannotationmethodinterceptor ใหม่ (ตัวแก้ไข)); SetMethodinterceptors (interceptors); } ป้องกัน org.apache.shiro.aop.methodinvocation createMethodinVocation (Object ImplSpecificMethodinVocation) {วิธีสุดท้ายวิธีการ mi = (methodInvocation) impspecificMethodinVocation; ส่งคืน org.apache.shiro.aop.methodinvocation () {วิธีการสาธารณะ getMethod () {return mi.getMethod (); } วัตถุสาธารณะ [] getArguments () {return mi.getArguments (); } public String toString () {return "การเรียกใช้เมธอด [" + mi.getMethod () + "]"; } วัตถุสาธารณะดำเนินการต่อ () โยนได้ {return mi.proceed (); } วัตถุสาธารณะ getthis () {return mi.getthis (); - } วัตถุที่ได้รับการป้องกันการดำเนินการต่อ (Object AopallianceMethodinVocation) โยน {methodInvocation mi = (methodInvocation) AopallianceMethodinVocation; กลับ mi.proceed (); } วัตถุสาธารณะเรียกใช้ (MethodInvocation MethodInvocation) พ่นได้ {org.apache.shiro.aop.methodinvocation mi = createMethodinVocation (methodInvocation); return super.invoke (MI); -โดยการดูการใช้วิธีการเรียกใช้ของคลาสแม่ในที่สุดเราจะเห็นว่าตรรกะหลักคือการเรียกใช้วิธีการรับรองและการใช้วิธีนี้ (ซอร์สโค้ดมีดังนี้) คือการพิจารณาว่าการกำหนดค่าที่กำหนดค่า เมื่อได้รับการสนับสนุนวิธีการที่ได้รับการรับรองจะถูกเรียกร้องให้มีการตรวจสอบการอนุญาตและ AuthorizingAntationMethodinterceptor จะเรียกวิธีการรับรองวิธีการรับรองของ AuthorizingAntationHandler
Void assertauthorized (MethodInvocation MethodInvocation) โยน AuthorizationException {// การใช้งานเริ่มต้นเพียงแค่ทำให้มั่นใจได้ว่าไม่มีการลงคะแนนเสียงใด ๆ if (aamis! = null &&! aamis.isempty ()) {สำหรับ (AuthorizingAntationMethodinterceptor aami: aamis) {ถ้า (aami.supports (methodInvocation)) {aami.assertauthorized (methodInvocation); -ถัดไปลองมองย้อนกลับไปที่ PermissionAntationMethodinterceptor ที่กำหนดโดย AopallianceannotationsauthorizingMethodinterceptor, ซอร์สโค้ดมีดังนี้ การรวมซอร์สโค้ดของ AopallianceannotationsauthorizingMethodinterceptor และซอร์สโค้ดของ PermissionNotationMethodinterceptor เราจะเห็นว่าการอนุญาตใด ๆ PermissionAntationDhandler เป็นคลาสย่อยของ AuthorizingAntationHandler ดังนั้นการควบคุมการอนุญาตขั้นสุดท้ายของเราจึงถูกกำหนดโดยการดำเนินการตามที่ได้รับอนุญาตของ PermissionAntationHandler
Public PermissionAnotationMethodinterceptor ขยาย AuthorizingAnotationMethodInterceptor {Public PermissionAnotationMethodinterceptor () {super (ใหม่ PermissionAnotationHandler ()); } Public PermissionAntationMethodInterceptor (AnnotationResolver Resolver) {Super (ใหม่ PermissionAnotationHandler (), Resolver); -ถัดไปลองดูที่วิธีการที่ได้รับการรับรองการใช้งานของ PermissionAntationHandler และรหัสที่สมบูรณ์มีดังนี้ จากการใช้งานเราจะเห็นได้ว่าจะได้รับค่าการอนุญาตที่กำหนดค่าจากคำอธิบายประกอบและคำอธิบายประกอบที่นี่คือคำอธิบายประกอบที่ต้องการ ยิ่งกว่านั้นเมื่อทำการตรวจสอบสิทธิ์เราจะใช้ค่าข้อความที่ระบุโดยตรงเมื่อกำหนดคำอธิบายประกอบ เราจะเริ่มจากที่นี่เมื่อเราขยายในภายหลัง
Public PermissionAntationHandler ขยาย AuthorizingAntationHandler {Public PermissionAnotationHandler () {super (ต้องการ perspermissions.class); } สตริงที่ได้รับการป้องกัน [] getAnnotationValue (คำอธิบายประกอบ A) {ต้องการการใช้งาน rpannotation = (ต้องใช้ประโยชน์) a; ส่งคืน rpannotation.value (); } โมฆะสาธารณะ assertauthorized (คำอธิบายประกอบ a) โยนการอนุญาต exception {if (! (A Instanceof ต้องการการส่งต่อ)) กลับมา; ต้องการการใช้งาน rpannotation = (ต้องใช้ประโยชน์) a; สตริง [] perms = getAnnotationValue (a); เรื่องหัวเรื่อง = getSubject (); if (perms.length == 1) {subject.checkpermission (perms [0]); กลับ; } if (logical.and.equals (rpannotation.logical ())) {getSubject (). checkpermissions (Perms); กลับ; } if (logical.or.equals (rpannotation.logical ())) {// หลีกเลี่ยงการประมวลผลข้อยกเว้นโดยไม่จำเป็น - "ล่าช้า" การขว้างข้อยกเว้นโดยการเรียก Hasrole Boolean First HasatleastonePermission = false; สำหรับ (การอนุญาตสตริง: PERMS) ถ้า (getSubject () // ทำให้เกิดข้อยกเว้นหากไม่มีการจับคู่บทบาทใด ๆ โปรดทราบว่าข้อความข้อยกเว้นจะทำให้เข้าใจผิดเล็กน้อยหาก (! -ผ่านการแนะนำก่อนหน้านี้เรารู้ว่าคำอธิบายประกอบของพารามิเตอร์วิธีการที่ได้รับอนุญาตของ PermissionAntationHandler นั้นถูกส่งผ่านโดยการอนุญาตให้ใช้งาน Methodinterceptor เมื่อเรียกวิธีการรับรองวิธีการอนุญาต ซอร์สโค้ดมีดังนี้ จากซอร์สโค้ดเราจะเห็นว่ามีคำอธิบายประกอบผ่านวิธี getannotation
โมฆะสาธารณะ assertauthorized (MethodInvocation MI) โยน AuthorizationException {ลอง {((AuthorizingAntationHandler) Gethandler ()). assertauthorized (getAnnotation (MI)); } catch (AuthorizationException AE) {ถ้า (ae.getCause () == null) ae.initcause (การอนุญาตใหม่ ("ไม่ได้รับอนุญาตให้เรียกใช้วิธีการ:" + mi.getMethod ())); โยน ae; -เมื่อเดินไปตามทิศทางนี้ในที่สุดเราจะพบวิธีการใช้งาน getannotation ของ SpringannotationResolver ซึ่งนำไปใช้ดังนี้ ดังที่เห็นได้จากรหัสต่อไปนี้เป็นที่ต้องการค้นหาวิธีการเมื่อมองหาคำอธิบายประกอบ หากไม่พบในวิธีการมันจะมองหาคำอธิบายประกอบที่สอดคล้องกันจากคลาสของการเรียกวิธีการปัจจุบัน จากที่นี่เรายังสามารถดูได้ว่าทำไมสิ่งที่มีผลกับวิธีการเมื่อเรากำหนดคำอธิบายประกอบการควบคุมการอนุญาตประเภทเดียวกันในชั้นเรียนและวิธีการก่อนหน้านี้และเมื่อมีอยู่คนเดียวสิ่งที่กำหนดไว้จะมีผล
Public Class SpringannotationResolver ใช้คำอธิบายประกอบ ennotationResolver {คำอธิบายประกอบสาธารณะ getAnnotation (MethodInvocation MI, คลาส <? ขยายคำอธิบายประกอบ> Clazz) {Method M = Mi.GetMethod (); คำอธิบายประกอบ A = Annotationutils.findannotation (M, clazz); if (a! = null) return a; // วัตถุวิธีการของ MethodInvocation อาจเป็นวิธีที่กำหนดไว้ในอินเทอร์เฟซ // อย่างไรก็ตามหากคำอธิบายประกอบมีอยู่ในการใช้งานของอินเทอร์เฟซ (และไม่ใช่ // อินเทอร์เฟซเอง) มันจะไม่อยู่ในวัตถุวิธีการข้างต้น แต่เราจำเป็นต้อง // ได้รับการแสดงวิธีการจาก TargetClass และตรวจสอบโดยตรงเกี่ยวกับ // การใช้งานเอง: คลาส <?> targetClass = mi.getthis (). getClass (); m = classutils.get mostspecificmethod (m, targetClass); A = Annotationutils.findannotation (M, clazz); if (a! = null) return a; // ดูว่าชั้นเรียนมีคำอธิบายประกอบการแสดงความคิดเห็นแบบเดียวกันกับคำอธิบายประกอบแบบเดียวกัน findannotation (mi.getthis (). getclass (), clazz); - ผ่านการอ่านซอร์สโค้ดด้านบนฉันเชื่อว่าผู้อ่านมีความเข้าใจที่ลึกซึ้งยิ่งขึ้นเกี่ยวกับหลักการของคำอธิบายประกอบการควบคุมการอนุญาตที่สนับสนุนโดยชิโรหลังจากบูรณาการฤดูใบไม้ผลิ ซอร์สโค้ดที่โพสต์ด้านบนเป็นเพียงบางส่วนของหลักที่ผู้เขียนคิดว่าค่อนข้างเป็นแกน หากคุณต้องการทราบเนื้อหาที่สมบูรณ์โดยละเอียดโปรดอ่านรหัสที่สมบูรณ์ด้วยตัวเองตามแนวคิดที่ผู้เขียนกล่าวถึง
หลังจากทำความเข้าใจหลักการของการควบคุมการอนุญาตตามคำอธิบายประกอบผู้อ่านยังสามารถขยายได้ตามความต้องการทางธุรกิจจริง
ขยายโดยใช้นิพจน์สปริงเอล
สมมติว่าตอนนี้มีอินเทอร์เฟซเช่นต่อไปนี้ซึ่งมีวิธีการสืบค้นที่ได้รับประเภทพารามิเตอร์ มาทำให้ง่ายขึ้นที่นี่โดยสมมติว่าตราบใดที่ได้รับพารามิเตอร์และค่าที่แตกต่างกันจะถูกส่งคืน
อินเทอร์เฟซสาธารณะ RealService {Quotion Query (ประเภท int); -อินเทอร์เฟซนี้เปิดสู่โลกภายนอก วิธีนี้สามารถร้องขอผ่าน URL ที่เกี่ยวข้อง เรากำหนดวิธีคอนโทรลเลอร์ที่เกี่ยวข้องดังนี้:
@RequestMapping ("/service/{type}") การสืบค้นวัตถุสาธารณะ (@PathVariable ("type") ประเภท int) {return this.realservice.Query (ประเภท);}บริการอินเทอร์เฟซข้างต้นมีสิทธิ์ในการพิมพ์เมื่อทำการสืบค้น ไม่ใช่ผู้ใช้ทุกคนที่สามารถใช้แต่ละประเภทในการสืบค้นและต้องใช้สิทธิ์ที่สอดคล้องกัน ดังนั้นสำหรับวิธีการประมวลผลข้างต้นเราจำเป็นต้องเพิ่มการควบคุมการอนุญาตและการอนุญาตที่จำเป็นในระหว่างการเปลี่ยนแปลงการควบคุมแบบไดนามิกกับประเภทพารามิเตอร์ สมมติว่าคำจำกัดความของการอนุญาตแต่ละประเภทเป็นรูปแบบของการสืบค้น: ประเภท ตัวอย่างเช่นการอนุญาตที่จำเป็นเมื่อ type = 1 คือการสืบค้น: 1 และการอนุญาตที่จำเป็นเมื่อ type = 2 คือการสืบค้น: 2 เมื่อไม่รวมกับฤดูใบไม้ผลิเราจะทำเช่นนี้:
@RequestMapping ("/service/{type}") การสืบค้นวัตถุสาธารณะ (@PathVariable ("type") ประเภท int) {Securityutils.getSubject (). coppermission ("Query:" + type); ส่งคืนสิ่งนี้ realservice.query (ประเภท);}อย่างไรก็ตามหลังจากการบูรณาการกับฤดูใบไม้ผลิการปฏิบัติข้างต้นมีการเชื่อมโยงกันอย่างมากและเราอยากจะใช้คำอธิบายประกอบแบบบูรณาการเพื่อควบคุมสิทธิ์ สำหรับสถานการณ์ข้างต้นเราค่อนข้างจะระบุสิทธิ์ที่จำเป็นผ่าน @requirespermissions แต่การอนุญาตที่กำหนดไว้ใน @requirespermissions เป็นข้อความคงที่และแก้ไข ไม่สามารถตอบสนองความต้องการแบบไดนามิกของเราได้ ในเวลานี้คุณอาจคิดว่าเราสามารถแยกวิธีการประมวลผลคอนโทรลเลอร์เป็นหลาย ๆ และควบคุมสิทธิ์แยกต่างหาก ตัวอย่างเช่นต่อไปนี้คือ:
@RequestMapping ("/บริการ/1")@ต้องการการใช้งาน ("คำถาม: 1") บริการวัตถุสาธารณะ 1 () {ส่งคืนสิ่งนี้ realService.Query (1);}@ต้องการการใช้งาน this.realservice.query (2);} //...@ requestmapping ("/service/200")@headspermissions ("Query: 200") Public Object Service200 () {return this.realservice.query (200);};};นี่คือโอเคเมื่อช่วงค่าของประเภทมีขนาดค่อนข้างเล็ก แต่ถ้ามีค่าที่เป็นไปได้ 200 ค่าเช่นข้างต้นมันจะเป็นปัญหาเล็กน้อยในการแจกแจงอย่างละเอียดเพื่อกำหนดวิธีการโปรเซสเซอร์แยกต่างหากและดำเนินการควบคุมการอนุญาต นอกจากนี้หากค่าของการเปลี่ยนแปลงประเภทในอนาคตเราต้องเพิ่มวิธีโปรเซสเซอร์ใหม่ ดังนั้นวิธีที่ดีที่สุดคือทำให้ @RequiresPermissions รองรับคำจำกัดความการอนุญาตแบบไดนามิกในขณะที่ยังคงสนับสนุนคำจำกัดความแบบคงที่ จากการวิเคราะห์ก่อนหน้านี้เรารู้ว่าจุดเริ่มต้นคือ PermissionAntationHandler และไม่ได้ให้ส่วนขยายสำหรับการตรวจสอบการอนุญาต หากเราต้องการขยายมันวิธีง่ายๆคือการแทนที่โดยรวม อย่างไรก็ตามการอนุญาตที่เราต้องดำเนินการแบบไดนามิกนั้นเกี่ยวข้องกับพารามิเตอร์วิธีการและพารามิเตอร์วิธีการไม่สามารถรับได้ใน PermissionAntationHandler ด้วยเหตุนี้เราจึงไม่สามารถแทนที่ PermissionAntationHandler ได้โดยตรง PermissionAntationDhandler ถูกเรียกโดย PermissionAntationMethodinterceptor พารามิเตอร์วิธีการสามารถรับได้เมื่อ permissionAntationHandler ถูกเรียกในวิธีการรับรองของคลาสแม่ที่อนุญาตให้ใช้งาน Methodinterceptor ด้วยเหตุผลนี้จุดขยายของเราจึงถูกเลือกในคลาส PermissionAntationMethodinterceptor และเรายังต้องแทนที่โดยรวม EL Expressions ของ Spring สามารถรองรับค่าพารามิเตอร์วิธีการแยกวิเคราะห์ได้ ที่นี่เราเลือกที่จะแนะนำการแสดงออกของ EL ของฤดูใบไม้ผลิ เมื่อ @requirespermissions กำหนดสิทธิ์คุณสามารถใช้การแสดงออกของสปริง EL เพื่อแนะนำพารามิเตอร์วิธีการ ในเวลาเดียวกันเพื่อที่จะคำนึงถึงข้อความคงที่ นี่คือเทมเพลตการแสดงออกของสปริง EL สำหรับเทมเพลตนิพจน์ EL ของฤดูใบไม้ผลิโปรดดูโพสต์บล็อกนี้ เรากำหนดสิทธิ์ของเราเอง ตรรกะการใช้งานของวิธีการอ้างถึงตรรกะใน PermissionAntationHandler แต่คำจำกัดความการอนุญาตใน @RequiresPermissions ที่ใช้เป็นผลมาจากการใช้นิพจน์สปริง EL ตามวิธีการที่เรียกว่าในปัจจุบันซึ่งเป็นผลมาจากการวิเคราะห์การประเมินผล ต่อไปนี้เป็นคำจำกัดความของเราในการใช้งาน AnnotationMethodinterceptor ของเราเอง
ระดับสาธารณะ SelfPermissionNotationMethodInterceptor ขยาย PermissionAntationMethodInterceptor {Private Final SpelexpressionParser Parser = ใหม่ spelexpressionParser (); Private ParameterNamediscoverer Paramnamediscoverer = ใหม่ DefaultParameterNamediscover (); TemplateParSerConText TemplateParSerContext ส่วนตัว = ใหม่ TemplateParSerConText (); public SelfPermissionAnnotationMethodInterceptor(AnnotationResolver resolver) { super(resolver); } @Override public void assertAuthorized(MethodInvocation mi) throws AuthorizationException { Annotation annotation = super.getAnnotation(mi); RequiresPermissions permAnnotation = (RequiresPermissions) annotation; String[] perms = permAnnotation.value(); EvaluationContext evaluationContext = new MethodBasedEvaluationContext(null, mi.getMethod(), mi.getArguments(), paramNameDiscoverer); for (int i=0; i<perms.length; i++) { Expression expression = this.parser.parseExpression(perms[i], templateParserContext); //Replace the original permission definition with the permission definition parsed by Spring EL expression perms[i] = expression.getValue(evaluationContext, String.class); } Subject subject = getSubject(); if (perms.length == 1) { subject.checkPermission(perms[0]); กลับ; } if (Logical.AND.equals(permAnnotation.logical())) { getSubject().checkPermissions(perms); กลับ; } if (Logical.OR.equals(permAnnotation.logical())) { // Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first boolean hasAtLeastOnePermission = false; for (String permission : perms) if (getSubject().isPermitted(permission)) hasAtLeastOnePermission = true; // Cause the exception if none of the role match, note that the exception message will be a bit misleading if (!hasAtLeastOnePermission) getSubject().checkPermission(perms[0]); -定义了自己的PermissionAnnotationMethodInterceptor后,我们需要替换原来的PermissionAnnotationMethodInterceptor为我们自己的PermissionAnnotationMethodInterceptor。根据前面介绍的Shiro整合Spring后使用@RequiresPermissions等注解的原理我们知道PermissionAnnotationMethodInterceptor是由AopAllianceAnnotationsAuthorizingMethodInterceptor指定的,而后者又是由AuthorizationAttributeSourceAdvisor指定的。为此我们需要在定义AuthorizationAttributeSourceAdvisor时通过显示定义AopAllianceAnnotationsAuthorizingMethodInterceptor的方式显示的定义其中的AuthorizingAnnotationMethodInterceptor,然后把自带的PermissionAnnotationMethodInterceptor替换为我们自定义的SelfAuthorizingAnnotationMethodInterceptor。替换后的定义如下:
<bean> <property name="securityManager" ref="securityManager"/> <property name="advice"> <bean> <property name="methodInterceptors"> <util:list> <bean c:resolver-ref="springAnnotationResolver"/> <!-- 使用自定义的PermissionAnnotationMethodInterceptor --> <bean c:resolver-ref="springAnnotationResolver"/> <bean c:resolver-ref="springAnnotationResolver"/> <bean c:resolver-ref="springAnnotationResolver"/> <bean c:resolver-ref="springAnnotationResolver"/> </util:list> </property> </bean> </property></bean><bean id="springAnnotationResolver"/>
为了演示前面示例的动态的权限,我们把角色与权限的关系调整如下,让role1、role2和role3分别拥有query:1、query:2和query:3的权限。此时user1将拥有query:1和query:2的权限。
<bean id="realm"> <property name="userDefinitions"> <value> user1=pass1,role1,role2 user2=pass2,role2,role3 admin=admin,admin </value> </property> <property name="roleDefinitions"> <value> role1=perm1,perm2,query:1 role2=perm1,perm3,query:2 role3=perm3,perm4,query:3 </value> </property></bean>
此时@RequiresPermissions中指定权限时就可以使用Spring EL表达式支持的语法了。因为我们在定义SelfPermissionAnnotationMethodInterceptor时已经指定了应用基于模板的表达式解析,此时权限中定义的文本都将作为文本解析,动态的部分默认需要使用#{前缀和}后缀包起来(这个前缀和后缀是可以指定的,但是默认就好)。在动态部分中可以使用#前缀引用变量,基于方法的表达式解析中可以使用参数名或p参数索引的形式引用方法参数。所以上面我们需要动态的权限的query方法的@RequiresPermissions定义如下。
@RequestMapping("/service/{type}")@RequiresPermissions("query:#{#type}")public Object query(@PathVariable("type") int type) { return this.realService.query(type);}这样user1在访问/service/1和/service/2是OK的,但是在访问/service/3和/service/300时会提示没有权限,因为user1没有query:3和query:300的权限。
สรุป
The above is a detailed explanation of Spring integrating Shiro and expanding the use of EL expressions introduced by the editor. ฉันหวังว่ามันจะเป็นประโยชน์กับทุกคน หากคุณมีคำถามใด ๆ โปรดฝากข้อความถึงฉันและบรรณาธิการจะตอบกลับคุณทันเวลา ขอบคุณมากสำหรับการสนับสนุนเว็บไซต์ Wulin.com!