Shiro هو إطار التحكم في الإذن خفيف الوزن مع مجموعة واسعة من التطبيقات. يتمثل محور هذه المقالة في تقديم تكامل Spring لـ Shiro وتمكين المعلمات الديناميكية مثل REquiresRoles ليتم دعمه من خلال تمديد تعبيرات EL Spring. مقدمة شيرو ليست ضمن نطاق هذه المقالة. إذا كان القراء لا يعرفون الكثير عن شيرو ، فيمكنهم تعلم المعلومات المقابلة من خلال موقعها الرسمي. هناك أيضًا مقال عن InfoQ يوفر مقدمة شاملة لشيرو ، ويوصى به رسميًا أيضًا. عنوانه هو https://www.infoq.com/articles/apache-shiro.
شيرو يدمج الربيع
أولاً ، تحتاج إلى إضافة shiro-spring-xxx.jar إلى مشروعك. إذا كنت تستخدم Maven لإدارة مشروعك ، فيمكنك إضافة التبعيات التالية إلى تبعياتك. إليك أحدث الإصدار 1.4.0 الذي اخترته.
<Rependency> <roupeD> org.apache.shiro </rougeid> <StifactId> shiro-spring </artifactid> <sophy> 1.4.0 </version> </sependency>
بعد ذلك ، تحتاج إلى تحديد shirofilter في web.xml الخاص بك وتطبيقه لاعتراض جميع الطلبات التي تتطلب التحكم في الإذن ، وعادة ما يتم تكوينها على أنها /*. بالإضافة إلى ذلك ، يجب إضافة المرشح إلى المقدمة للتأكد من أن الطلب يتم التحكم فيه أولاً من خلال أذونات Shiro بعد ظهوره. يتم تكوين الفئة المقابلة للمرشح هنا باستخدام DevatingFilterProxy ، وهو وكيل مرشح مقدمة من الربيع. يمكنك استخدام حبة في حاوية الفاصوليا الزنبركية كمثيل للمرشح الحالي ، وستأخذ الفاصوليا المقابلة الفاصوليا المقابلة مع اسم المرشح. لذلك سيبحث التكوين التالي عن حبة تسمى Shirofilter في حاوية الفول.
<ilipter> <iliter-name> shirofilter </filter-name> <lipter-class> org.springframework.web.filter.delegatingfilterproxy </filter-class> <Ing-param> <Param-name> targetfilterlterlifecycle </param-name> <Param-Value> </ith-param> </filter> <filter-mapping> <filter-name> shirofilter </filter-name> <url-pattern>/*</url-pattern> </filter mapping>
عند استخدام Shiro بشكل مستقل ، عادة ما تحدد org.apache.shiro.web.servlet.shirofilter للقيام بشيء مماثل.
التالي هو تحديد Shirofilter لدينا في حاوية الفول. على النحو التالي ، نحدد shirofilterfactorybean ، والتي ستنتج فاصوليا من نوع structionShirofilter. من خلال shirofilterfactorybean يمكننا تحديد رجل الأمن. يحتاج 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 = "sucturerl value = "/unauneorized.jsp"/> <property name = "FilterChainDefinitions"> <value>/** = Authc ، الأدوار [admin]/logout = logout # تتطلب عناوين أخرى أن المستخدم قد سجله/** = Authc ، logger </value> </propert ref = "Realm"/> </bean> <bean id = "LifecyCleBeanPostProcessor"/>
<!-من أجل البساطة ، سوف نستخدم تنفيذ العالم المستند إلى النص هنا-> <bean id = "RealM"> <property name = "userDefinitions"> <value> user1 = pass1 ، rob1 ، orde2 user2 = pass2 ، robe2 ، admin = admin ، admin </value> </pertar
إذا كنت بحاجة إلى استخدام مرشح مخصص في تعريف FilterChainDefinitions ، فيمكنك تحديد المرشح المخصص وعلاقة التعيين الخاصة به من خلال مرشحات Shirofilterfactorybean. على سبيل المثال ، كما هو موضح أدناه ، أضفنا مرشحًا مع مسجل الاسم المستعار ، ومرشح محدد /** مع مسجل الاسم المستعار في التعريفات المرشح.
<bean id = "shirofilter"> <property name = "securityManager" ref = "securityManager"/> <property name = "loginurl" value = "/login.jsp"/> <property name = "sucturerl value = "/unaunterized.jsp"/> <property name = "filters"> <util: map> <interpt key = "logger"> <bean/> </tervation> </util: map> </spleneration> <property name = "filterchaindefinitions"> <value> </value> </property> </ban>
في الواقع ، يمكن أيضًا تعريف تعريف الاسم المستعار للمرشح الذي نحتاجه إلى تطبيقه مباشرة بواسطة SetFilters () من ShirofilterFactoryBean ، ولكن تحديد الفاصوليا المقابلة مباشرة في حاوية الفاصوليا المقابلة. لأنه افتراضيًا ، سيقوم Shirofilterfactorybean بتسجيل جميع فاصوليا نوع المرشح في حاوية الفول مع الاسم المستعار للمعرف في المرشحات. لذلك ، فإن التعريف أعلاه يعادل ما يلي.
<bean id = "shirofilter"> <property name = "securityManager" ref = "securityManager"/> <property name = "loginurl" value = "/login.jsp"/> <property name = "sucturerl value = "/unauntorized.jsp"/> <property name = "filterchaindefinitions"> <value>/** = AUTHC ، أدوار [admin]/logout = logout # تتطلب العناوين الأخرى أن المستخدم قد سجل/** = AUTHC ، logger </value> </propert
بعد الخطوات المذكورة أعلاه ، يتم الانتهاء من دمج شيرو والربيع. في هذا الوقت ، سيطلب منا أي مسار طلبنا للمشروع تسجيل الدخول ، وسوف نقفز تلقائيًا إلى المسار المحدد بواسطة LogInurl ودعنا ندخل اسم المستخدم/كلمة المرور لتسجيل الدخول. في هذا الوقت ، يجب أن نقدم نموذجًا للحصول على اسم المستخدم من خلال اسم المستخدم من خلال كلمة المرور من خلال كلمة المرور ، ثم عند إرسال طلب تسجيل الدخول إلى ما بعد ذلك. اسم المستخدم/كلمة المرور المستخدمة عند تسجيل الدخول هو اسم المستخدم/كلمة المرور التي حددناها في TextConfigurationRealM. استنادًا إلى التكوين أعلاه ، يمكنك استخدام user1/pass1 ، المسؤول/المسؤول ، إلخ. بعد تسجيل الدخول بنجاح ، ستقفز إلى العنوان المحدد بواسطة معلمة Successurl. إذا تم تسجيل الدخول باستخدام user1/pass1 ، فيمكننا أيضًا محاولة الوصول إلى/admin/index ، وفي هذا الوقت ، سنقفز إلى غير مصرح به. jsp بسبب عدم كفاية الأذونات.
تمكين الدعم القائم على التعليقات التوضيحية
يتطلب التكامل الأساسي منا تحديد جميع أدوات التحكم في الإذن التي يحتاج عنوان URL إلى تطبيقها في مرشحات FilterChainDefinitions لـ Shirofilterfactorybean. هذا في بعض الأحيان ليس بهذا المرونة. يزودنا Shiro بالتعليقات التعليقات التوضيحية التي يمكن استخدامها بعد دمج الربيع. يتيح لنا إضافة التعليقات التوضيحية المقابلة إلى الفصل أو الطريقة التي تتطلب التحكم في الإذن لتحديد الأذونات المطلوبة للوصول إلى الفئة أو الطريقة. إذا كان ذلك على الفصل في التعريف ، فهذا يعني أن استدعاء جميع الطرق في الفصل يتطلب أذونات مقابلة (لاحظ أنه يجب أن يكون مكالمة خارجية ، وهي قيود على الوكيل الديناميكي). لاستخدام هذه التعليقات التوضيحية ، نحتاج إلى إضافة تعريفين بين الفاصوليا التاليين إلى حاوية الفاصوليا في Spring ، حتى نتمكن من تحديد ما إذا كان لدى المستخدم الأذونات المقابلة بناءً على تعريف التعليقات التوضيحية في وقت التشغيل. يتم تحقيق ذلك من خلال آلية AOP في الربيع. إذا كنت لا تعرف أي شيء عن Spring AOP ، فيمكنك الرجوع إلى "عمود مقدمة الربيع AOP" الذي كتبه المؤلف. يحدد تعريفان الفاصوليا التاليان ، AuthorizationAttributesourCeadVisor مستشارًا ، والذي سيعترض الأذونات والتحقق منها بناءً على طريقة تكوين التعليقات التي توفرها Shiro. يوفر DefaultAdvisorautOproxyCreator وظيفة إنشاء كائنات وكيل للفئة المميزة بتعليقات التحكم في الإذن التي توفرها Shiro ، وتطبيق EantorizationAtributesourCeadVisor عند اعتراض استدعاء طريقة الهدف. عندما يتم اعتراض طلب من المستخدم ولن يكون لدى المستخدم إذنًا محددًا على الطريقة أو الفئة المقابلة ، سيتم طرح org.apache.shiro.authz.authorizationexception استثناء.
<bean يعتمد على = "LifecycleBeanPostProcessor"/> <boan> <property name = "SecurityManager" Ref = "SecurityManager" // </bean>
إذا كان <aop:config/>或<aop:aspectj-autoproxy/> تم تعريفه بالفعل في حاوية الفاصوليا الخاصة بنا ، فإن defaultAdvisorautOproxyCreator لم يعد من الممكن تعريفه. نظرًا لأن الحالتين السابقتين ستضيفان حبة مشابهة لـ DefaultAdvisorautOproxyCreator. لمزيد من المعلومات حول DefaultAdvisorautoproxyCreator ، يمكنك أيضًا الرجوع إلى مبدأ المؤلف المتمثل في إنشاء كائنات وكيل تلقائيًا في الربيع AOP.
تعليقات التحكم في الإذن التي توفرها شيرو هي كما يلي:
يتطلب الصخور: يحتاج المستخدم إلى المصادقة في الجلسة الحالية ، أي أنه يحتاج إلى تسجيل الدخول باستخدام اسم المستخدم/كلمة المرور ، ولا يتضمن تسجيل الدخول التلقائي.
يتطلب Ususer: يتعين على المستخدم المصادقة. يمكن المصادقة عليه عن طريق تسجيل الدخول باستخدام اسم المستخدم/كلمة المرور في هذه الجلسة ، أو يمكن تسجيل الدخول تلقائيًا باستخدام DemicerMe.
يتطلب GUSEST: لا يتم تسجيل الدخول للمستخدم.
يتطلب ROTOROLES: يتطلب المستخدم أن يكون الدور المحدد مملوكًا.
يتطلب signsips: يتطلب المستخدم الأذونات المحددة.
من السهل فهم الثلاثة الأولى ، في حين أن الأخيرين متشابهين. هنا أستخدم requirepermissions كمثال. أولاً ، دعنا نغير العالم المحدد أعلاه وأضف أذونات إلى الدور. وبهذه الطريقة ، سيكون لدى user1 أذونات لـ Perm1 و Perm2 و Perm3 و user2 أذونات إلى PerM1 و Perm3 و Perm4.
<bean id = "RealM"> <property name = "userDefinitions"> <value> user1 = pass1 ، role1 ، or order2 = pass2 ، robe2 ، rolee3 admin = admin ، admin </value> </spertar
يمكن إضافة REQUIRESPERMISSIONS على طريقة لتحديد الأذونات التي تحتاج إلى المطالبة عند استدعاء الطريقة. في الكود التالي ، نحدد أن إذن PERM1 يجب أن يكون مكونًا عند الوصول /PEM1. في هذا الوقت ، يمكن الوصول إلى كل من user1 و user2.
requestmapping ("/perm1")@requireperMissions ("perm1") complic complision 1 () {return "perm1" ؛}إذا كنت بحاجة إلى تحديد أنه يجب أن يكون لديك أذونات متعددة في نفس الوقت للوصول إلى طريقة ما ، فيمكنك تحديد الأذونات التي تحتاجها لتحديدها في شكل صفيف (عند تحديد سمة صفيف واحدة على التعليق التوضيحي ، لا يمكنك إضافة أقواس ، ولكن عندما تحتاج إلى تحديد أذونات متعددة ، تكون هناك حاجة إلى أذونات). على سبيل المثال ، على النحو التالي ، نحدد أنه عند الوصول إلى /perm1andperm4 ، يجب أن يكون لدى المستخدم كل من أذونات PERM1 و PERM4. في هذا الوقت ، يمكن لـ USER2 فقط الوصول إليه ، لأنه يحتوي فقط على PERM1 و PERM4 في نفس الوقت.
requestmapping ("/perm1andperm4")@requireperMissions ({"perm1" ، "perm4"}) الكائن العام perm1andperm4 () {return "perm1andperm4" ؛}عندما يتم تحديد أذونات متعددة في نفس الوقت ، فإن العلاقة بين الأذونات المتعددة هي العلاقة ، أي أن جميع الأذونات المحددة في نفس الوقت مطلوبة. إذا كنت بحاجة فقط إلى الحصول على واحدة من الأذونات المتعددة المحددة التي يمكن الوصول إليها ، فيمكننا تحديد العلاقة بين أو بين الأذونات المتعددة من خلال logical.or. على سبيل المثال ، على النحو التالي ، نحدد أنه عند الوصول إلى /perm1orperm4 ، تحتاج فقط إلى الحصول على أذونات perm1 أو perm4 ، بحيث يمكن لكل من user1 و user2 الوصول إلى هذه الطريقة.
requestmapping ("/perm1orperm4")@requireperMissions (value = {"perm1" ، "perm4"} ، logical = logical.or) كائن عام perm1orperm4 () {return "perm1orperm4" ؛}يمكن أيضًا وضع علامة على requirespermissions في الفصل ، مما يشير إلى أنه عند الوصول إلى الأساليب في الفصل خارجيًا ، يجب أن يكون لديك أذونات مقابلة. على سبيل المثال ، في ما يلي ، نحدد أننا نحتاج إلى الحصول على إذن PERM2 على مستوى الفصل ، في حين أن طريقة الفهرس () لا تحدد أننا نحتاج إلى أي أذونات ، لكن لا يزال يتعين علينا الحصول على أذونات محددة في مستوى الفصل عند الوصول إلى هذه الطريقة. في هذا الوقت ، يمكن لـ User1 الوصول إليه فقط.
@RESTCONTROLLER@requestMapping ("/foo")@requireperMissions ("perm2") فئة عامة foOconTroller {REquestMapping (method = requestMethod.get) INDEX () {map <string ، Object> map = new hashmap <> () ؛ map.put ("ABC" ، 123) ؛ خريطة العودة }}عندما يكون لكل من الفئة والطريقة مستويات @requirepermiss ، فإن مستوى الطريقة له أولوية أعلى ، وسيتم التحقق من الأذونات فقط التي يتطلبها مستوى الطريقة في هذا الوقت. على النحو التالي ، نحدد أن إذن PERM2 مطلوب على مستوى الفصل وأن إذن PERM3 مطلوب على مستوى الطريقة. ثم عند الوصول /FOO ، تحتاج فقط إلى الحصول على أذونات PERM3 للوصول إلى طريقة الفهرس (). لذلك في هذا الوقت يمكن لكل من user1 و user2 الوصول إلى /foo.
@RESTCONTROLLER @requestMapping ("/foo") @requireperMissions ("perm2") فئة عامة foOconTroller {REquestMapping (method = requestMethod.get) @requirespermissions ("perm3") فهرس الكائنات العامة () {map <string> map = new hashmap <> () ؛ map.put ("ABC" ، 123) ؛ خريطة العودة }}ومع ذلك ، إذا أضفنا @requiresRoles ("rob1") إلى الفصل في هذا الوقت لتحديد أننا بحاجة إلى دور الدور 1 ، ثم عند الوصول /FOO ، نحتاج إلى أن يحدد دور 1 بواسطة requireperMissions ("perm3") على الدور 1 على طريقة الفهرس () على الفصل. نظرًا لأن عمليات التسجيل والمتطلبات التي تتطلبها تنتمي إلى تعريفات الإذن لأبعاد مختلفة ، فإن Shiro ستقوم بفحصها مرة واحدة أثناء التحقق ، ولكن إذا كان لكل من الفئة والطريقة تعليقات عن نفس نوع تعريف التحكم في الإذن ، فسيعتمد التعريف في الطريقة فقط على التعريف.
@RESTCONTROLLER@requestMapping ("/foo")@requireperMissions ("perm2")@متطلبات ("rob1") فئة عامة foocontroller {@requestmapping (method = requestMethod.get)@requirepermissions ("perm3" index () {map <string ، object> map = new hashmap <> () ؛ map.put ("ABC" ، 123) ؛ خريطة العودة }}على الرغم من أن المثال لا يستخدم إلا يتطلب sisspissions ، فإن استخدام تعليقات التحكم في الإذن الأخرى متشابهة أيضًا. يرجى استخدام التعليقات التوضيحية الأخرى من قبل الأصدقاء المهتمين.
مبدأ أذونات التحكم في التعليقات التوضيحية
الأذونات التي حددناها أعلاه ثابتة باستخدام requirespermissions. أحد الأغراض الرئيسية لهذه المقالة هو تقديم طريقة لجعل الأذونات المحددة ديناميكية من خلال توسيع التنفيذ. ولكن قبل التوسع ، علينا أن نعرف كيف يعمل ، أي مبدأ التنفيذ ، قبل أن نتمكن من التوسع. لذلك دعونا نلقي نظرة على كيفية دمج Shiro Spring مع requirepermissions. عند تمكين الدعم لـ @REQUIREPREMISTIONS ، نحدد الفول التالي ، وهو مستشار ، ورثه من StaticMethodMatcherPointCutAdvisor. طريقة مطابقة الطريقة هي أنه طالما أن الفئة أو الطريقة لديها عدة تعليقات توضيحية للتحكم في الإذن لشيرو ، يتم تحديد منطق المعالجة بعد الاعتراض من خلال النصيحة المقابلة.
<Bean> <property name = "SecurityManager" ref = "SecurityManager"/> </bean>
فيما يلي رمز المصدر لـ EmittLiveTributesourCeadVisor. يمكننا أن نرى أنه في طريقة مُنشئها ، يتم تحديد AopallianCeannotationSauthorizationMethodInterceptor ، بواسطة SetAdvice () ، والذي يعتمد على تنفيذ MethodInterceptor.
EutloryattRibutesourCeadVisor من الطبقة العامة يمتد StaticMethodMatcherPointCutAdvisor {Private Static Final Logger Log = loggerFactory.getLogger (AuthorizationAtributesourCeadVisor.Class) ؛ الفئة النهائية الثابتة الخاصة <؟ يمتد التعليق التوضيحي> [] Authz_annotation_classes = فئة جديدة [] {requireperMissions.class ، يتطلب roles.class ، يتطلب. SecurityManager SecurityManager المحمي = NULL ؛ إذن publicattRibutesourCeadVisor () {setAdvice (AopallianCeannotationSauthorizationMethodInterceptor ()) ؛ } الأمن العام GetSecurityManager () {return SecurityManager ؛ } public void setSecurityManager (org.apache.shiro.mgt.securityManager SecurityManager) {this.securityManager = SecurityManager ؛ } مطابقات منطقية عامة (طريقة الطريقة ، Class TargetClass) {method m = method ؛ if (isauthzannotationpresent (m)) {return true ؛ } // يمكن أن تكون المعلمة "الطريقة" من واجهة لا تحتوي على التعليقات التوضيحية. // تحقق لمعرفة ما إذا كان التنفيذ يحتوي على ذلك. if (targetClass! = null) {try {m = targetclass.getMethod (m.getName () ، m.getParameterTypes ()) ؛ إرجاع isauthzannotationpresent (م) || Isauthzannotationpresent (TargetClass) ؛ } catch (تجاهل nosuchmethodexception) {// قيمة الإرجاع الافتراضية خاطئة. إذا لم نتمكن من العثور على الطريقة ، فمن الواضح // لا يوجد أي شرح ، لذلك فقط استخدم قيمة الإرجاع الافتراضية. }} إرجاع خطأ ؛ } isauthzannotationprespresent boolean الخاص (الفئة <؟> targetclazz) {for (class <؟ Extring annotation> annclass: authz_annotation_classes) {التعليقات التوضيحية a = enrotationutils.findannotation (targetclazz ، annclass) ؛ if (a! = null) {return true ؛ }} إرجاع خطأ ؛ } boolean private IsauthZannotationPresent (method method) {for (class <؟ Extends annotation> annclass: authz_annotation_classes) {enrotation a = enrotationutils.findannotation (method ، annclass) ؛ if (a! = null) {return true ؛ }} إرجاع خطأ ؛ }}الكود المصدري لـ AopallianCeannotationsauthorizingMethodInterceptor هو كما يلي. تستدعي طريقة الاستدعاء لواجهة MethodInterceptor التي تم تنفيذها في طريقة استدعاء الفئة الأصل. في الوقت نفسه ، نحتاج إلى أن نرى أن بعض تطبيقات التفويض methodinterceptor قد تم إنشاؤها في طريقة مُنشئها. هذه التطبيقات هي جوهر التحكم في الإذن. في وقت لاحق ، سنختار فئة تنفيذ ArmissionAntationMethodInterceptor لرؤية منطق التنفيذ المحدد.
الطبقة العامة AOPALLIANCEANNOTATIONSOUTHORINGMETHODINTEPTOR يمتد التعليقات التوضيحية annotationsaorizationMethOdInterceptor method {public aopallianceannotationsauthorizationMethoDiNTOP () {list <uventAntAntationMethodInterInterceptor> interceptors = new arraylistation <AuplizingAntation // استخدم حل التعليقات التوضيحية الخاصة بـ Spring - SPRING's annotationUtils أجمل من عملية حل JDK RAW. RECENTATINGERSOLVER RESOLVER = New SpringAntationResolver () ؛ // يمكننا إعادة استخدام نفس مثيل الحل - لا يحتفظ بالدولة: اعتراض. interceptors.Add (New SomissionAnnotationMethodInterceptor (Resolver)) ؛ interceptors.Add (userannotationMethodInterceptor (Resolver)) ؛ interceptors.Add (New GuestannotationMethodInterceptor (Resolver)) ؛ setMethodInterceptors (اعتراضات) ؛ } محمي org.apache.shiro.aop.methodinvocation createMethodInvocation (الكائن ضمني methodinvocation) {النهائي methodinvocation mi = (methodInvocation) ImpecifecificMethodInvocation ؛ إرجاع new org.apache.shiro.aop.methodinvocation () {method public getMethod () {return mi.getMethod () ؛ } الكائن العام [] getArguments () {return mi.getArguments () ؛ } السلسلة العامة toString () {return "method invocation [" + mi.getMethod () + "]" ؛ } الكائن العام متابع () يلقي رمي {return mi.proceed () ؛ } الكائن العام getThis () {return mi.getthis () ؛ }} ؛ } كائن محمي متابع (كائن AOPALLIANCEMETHODINVOCING) يلقي {methodInvocation mi = (methodInvocation) aopalliancemethodinvocation ؛ إرجاع mi.proceed () ؛ } استدعاء الكائن العام (MethodInvocation MethodInvocation) يلقي رمي {org.apache.shiro.aop.methodinvocation mi = createMethodInvocation (MethodInvocation) ؛ إرجاع Super.invoke (MI) ؛ }}من خلال النظر في تنفيذ طريقة الاستدعاء للفئة الأصل ، سنرى أخيرًا أن المنطق الأساسي هو استدعاء الطريقة المصرفية ، وأن تنفيذ هذه الطريقة (رمز المصدر هو كما يلي) هو تحديد ما إذا كان المتكوين في الفئة أو الطريقة التي يدعمها في الفئة الداعمة). عند دعمها ، سيتم استدعاء طريقة التأكيد الخاصة بها للتحقق من الإذن ، وسيقوم OuDizingAntationMethodInterceptor بدوره باستدعاء طريقة تأكيد المصرح بها.
تُلقي الفراغ المحمي (MethodInvocation MethodInvocation) إلقاء AuthorizationException {// التنفيذ الافتراضي فقط لا يضمن عدم التصويت على الرفض: جمع <uputiantAntationMethodInterceptor> aamis = getMethodInterceptors () ؛ if (aamis! = null &&! aamis.isempty ()) {for (uputizingAntationMethodInterceptor aami: aamis) {if (aami.supports (methodInvocation)) {aami.assertauthorized (methodInvocation) ؛ }}}}بعد ذلك ، دعونا نلقي نظرة على AmormissionAnnotationMethodInterceptor المحددة بواسطة AopallianCeannotationsAuthorizingMethodInterceptor ، فإن رمز المصدر هو كما يلي. الجمع بين الكود المصدري لـ AopallianCeannotationsAuthorizationMethodInterceptor ورمز المصدر لـ SomrissionAntationMethodInterboctor ، يمكننا أن نرى أن ArmissionAntationHandler و SpringannotationResolver يتم تحديدها في PoxissionAnnotationMethodInterceptor. SERMISSONANTANTATIONHALLLER هي فئة فرعية من التفويض. لذلك يتم تحديد التحكم في الإذن النهائي لدينا من خلال التنفيذ المؤقت لـ ArmrissionAntationHandler.
الطبقة العامة PRORISSENANTANTATINGETHODINTEPTOR يمتد OPTIMANTANTANTATIONMETHODINTEPTOR {public promissionannotationMethodInterceptor () {super (new ArmissionAntationHandler ()) ؛ } PublicissionAntAntationMethOdInterceptor (antationResolver Resolver) {Super (New ArmissionAntationHandler () ، Resolver) ؛ }}بعد ذلك ، دعونا نلقي نظرة على تنفيذ الطريقة المصرح بها لـ SermissionAnnotationHandler ، والرمز الكامل كما يلي. من التنفيذ ، يمكننا أن نرى أنه سيحصل على قيمة إذن تم تكوينه من التعليق التوضيحي ، والشرح هنا هو شرح التعليقات الشرحية المتطلبات. علاوة على ذلك ، عند إجراء التحقق من الإذن ، نستخدم مباشرة قيمة النص المحددة عند تحديد التعليق التوضيحي. سنبدأ من هنا عندما نوسعها لاحقًا.
الطبقة العامة PRORISSENANTANTANTALLERLERLERLERS EXUPLISTANTANTANTATINGLERLER {public prosissannotationHandler () {super (يتطلب permissions.class) ؛ } السلسلة المحمية [] getAnnotationValue (التعليق التوضيحي A) {يتطلب permissions rpannotation = (يتطلب permissions) a ؛ إرجاع rpannotation.value () ؛ } public void assertaursized (التعليق التوضيحي A) يلقي التفويض {if (! (A extryOf يتطلب sisspermissions)) العودة ؛ يتطلب simplesions rpannotation = (يتطلب signmissions) أ ؛ سلسلة [] perms = getAnnotationValue (a) ؛ موضوع الموضوع = getUbject () ؛ if (perms.length == 1) {thision.checkpermission (perms [0]) ؛ يعود؛ } if (logical.and.equals (rpannotation.logical ())) {getUbject (). checkPermissions (perms) ؛ يعود؛ } if (logical.or.equals (rpannotation.logical ())) {// تجنب معالجة الاستثناءات دون تأخير - "تأخير" إلقاء الاستثناء عن طريق استدعاء هاسول الأول المنطقي hasatleastonepermission = false ؛ لـ (إذن السلسلة: perms) إذا (getUbject (). isPermitted (إذن)) hasatleastonepermission = true ؛ // تسبب في الاستثناء إذا لم يكن هناك أي من دورات الدور ، لاحظ أن رسالة الاستثناء ستكون مضللة بعض الشيء إذا (! hasatleastonepermission) getSubject (). checkpermission (perms [0]) ؛ }}}من خلال المقدمة السابقة ، نعلم أن التعليق التوضيحي لمعلمة الأسلوب المصرح بها من SermissionAntationHandler يتم تمريرها عن طريق التفويض interninterbotion عند استدعاء طريقة التأكيد على التفويض. رمز المصدر كما يلي. من رمز المصدر ، يمكننا أن نرى أنه يتم الحصول على التعليق التوضيحي من خلال طريقة getAnnotation.
public void assertaorized (MethodInvocation MI) يلقي التفويض {try {((elevizingannotationhandler) gethandler ()). } catch (AuthorizationException ae) {if (ae.getCause () == null) ae.initcause (uputizationException new ("غير مصرح به للاستدعاء طريقة:" + mi.getMethod ())) ؛ رمي ae }}المشي على طول هذا الاتجاه ، سنجد في النهاية تنفيذ طريقة getAnnotation لـ SpringAntationResolver ، والذي يتم تنفيذه على النحو التالي. كما يتضح من الكود التالي ، من المفضل البحث عن الطريقة عند البحث عن التعليقات التوضيحية. إذا لم يتم العثور عليها على الطريقة ، فسيبحث عن التعليق التوضيحي المقابل من فئة استدعاء الطريقة الحالية. من هنا ، يمكننا أيضًا أن نرى سبب سريان هذا الطريق عندما نحدد نفس النوع من توضيح التحكم في الإذن على الفصل والطريقة من قبل ، وعندما يكون ذلك بمفرده ، فإن النابض المحدد يسري.
الطبقة العامة SpringAntationResolver تنفذ التعليق التوضيحي {getAnnotation التعليقات العامة (MethodInvocation MI ، Class <؟ تمديد التعليقات التوضيحية> clazz) {method m = mi.getmethod () ؛ التعليقات التوضيحية A = anotationutils.findannotation (M ، Clazz) ؛ إذا (a! = null) إرجاع أ ؛ // يمكن أن يكون كائن طريقة MethodInvocation طريقة محددة في واجهة. // ومع ذلك ، إذا كان التعليق التوضيحي موجودًا في تطبيق الواجهة (وليس // الواجهة نفسها) ، فلن يكون ذلك على كائن الطريقة أعلاه. بدلاً من ذلك ، نحتاج إلى // الحصول على تمثيل الطريقة من TargetClass والتحقق مباشرة من تطبيق // نفسه: الفئة <؟> targetClass = mi.getthis (). getClass () ؛ M = classutils.getmormspecificMethod (M ، TargetClass) ؛ a = anotationutils.findannotation (m ، clazz) ؛ إذا (a! = null) إرجاع أ ؛ // معرفة ما إذا كان الفصل لديه نفس التعليق التوضيحي EstationUtils.Findannotation (mi.getthis (). getClass () ، clazz) ؛ }} من خلال قراءة رمز المصدر أعلاه ، أعتقد أن القراء لديهم فهم أعمق لمبدأ التعليقات التوضيحية للسيطرة على الإذن بدعم من شيرو بعد دمج الربيع. رمز المصدر المنشور أعلاه هو فقط بعض من الأساسيات التي يعتقد المؤلف أنها جوهرية نسبيا. إذا كنت تريد معرفة المحتوى الكامل بالتفصيل ، فيرجى قراءة الكود الكامل بنفسك على طول الأفكار التي ذكرها المؤلف.
بعد فهم هذا المبدأ من التحكم في الإذن بناءً على التعليقات التوضيحية ، يمكن للقراء أيضًا التوسع وفقًا لاحتياجات العمل الفعلية.
ممتد باستخدام تعبيرات الربيع EL
لنفترض أن هناك الآن واجهة مثل ما يلي ، والتي لديها طريقة استعلام تتلقى نوع المعلمة. دعنا نقوم بتبسيطها هنا ، على افتراض أنه طالما تم استلام هذه المعلمة وسيتم إرجاع القيم المختلفة.
الواجهة العامة realService {Object Query (int type) ؛ }هذه الواجهة مفتوحة للعالم الخارجي. يمكن طلب هذه الطريقة من خلال عنوان URL المقابل. نحدد طريقة وحدة التحكم المقابلة على النحو التالي:
requestmapping ("/service/{type}") استعلام الكائن العام (pathVariable ("type") int) {إرجاع this.realservice.query (type) ؛}تحتوي خدمة الواجهة أعلاه على أذونات للنوع عند إجراء الاستعلام. لا يمكن لكل مستخدم استخدام كل نوع للاستعلام ، ويتطلب أذونات مقابلة. لذلك ، بالنسبة لطريقة المعالج أعلاه ، نحتاج إلى إضافة عنصر تحكم في الإذن ، والأذونات المطلوبة أثناء التحكم تتغير ديناميكيًا مع نوع المعلمة. لنفترض أن تعريف كل إذن من النوع هو شكل الاستعلام: اكتب. على سبيل المثال ، الإذن المطلوب عندما يكون النوع = 1 هو الاستعلام: 1 ، والإذن المطلوب عندما يكون النوع = 2 الاستعلام: 2. عندما لا يتم دمجها مع الربيع ، فإننا نفعل هذا على النحو التالي:
requestmapping ("/service/{type}") استعلام الكائن العام (pathvariable ("type") int) {securityUtils.getSubject (). checkpermission ("query:" + type) ؛ إرجاع this.realservice.query (النوع) ؛}ومع ذلك ، بعد التكامل مع الربيع ، تكون الممارسات المذكورة أعلاه مقترنة للغاية ، ونفضل استخدام التعليقات التوضيحية المتكاملة للتحكم في الأذونات. بالنسبة للسيناريو أعلاه ، نفضل تحديد الأذونات المطلوبة من خلال requirespermissions ، ولكن الأذونات المحددة في requirespermissions هي نص ثابت ومثبت. لا يمكن أن تلبي احتياجاتنا الديناميكية. في هذا الوقت ، قد تعتقد أنه يمكننا تقسيم طريقة معالجة وحدة التحكم إلى متعددة والتحكم في الأذونات بشكل منفصل. على سبيل المثال ، ما يلي هو:
requestmapping ("/service/1")@requireperMissions ("query: 1") service service1 () {return this.realservice.query (1) ؛}@requireperMissions ("query: 2")@requestmapping ("/service/2" this.realservice.query (2) ؛} //...requestmapping ("/service/200")@requisterMissions ("Query: 200") service 200 () {return this.realservice.query (200) ؛}يكون هذا جيدًا عندما يكون نطاق القيمة من النوع صغيرًا نسبيًا ، ولكن إذا كان هناك 200 قيمة ممكنة مثل ما سبق ، فسيكون من المقلق بعض الشيء تعدادها بشكل شامل لتحديد طريقة معالج منفصلة وأداء التحكم في الإذن. بالإضافة إلى ذلك ، إذا تغيرت قيمة النوع في المستقبل ، فيجب علينا إضافة طريقة معالج جديدة. لذا فإن أفضل طريقة هي جعل requirepermissions تدعم تعريفات إذن ديناميكية ، مع الحفاظ على دعم التعريف الثابت. من خلال التحليل السابق ، نعلم أن نقطة الدخول هي إذن ، ولا توفر ملحقات للتحقق من الإذن. إذا أردنا توسيعه ، فإن الطريقة البسيطة هي استبداله ككل. ومع ذلك ، فإن الأذونات التي نحتاجها إلى معالجتها ديناميكيًا ترتبط بمعلمات الطريقة ، ولا يمكن الحصول على معلمات الطريقة في ArmissionAntationHandler. لهذا السبب ، لا يمكننا استبدال SomrissionAntationHandler مباشرة. يتم استدعاء ArmissionAnnotationHandler بواسطة SomrissionAntationMethodInterceptor. يمكن الحصول على معلمات الطريقة عندما يتم استدعاء SomrissionAntationHandler في الطريقة المصرح بها من فئة الأم OutlizingAntationMethodInterceptor. لهذا السبب ، يتم تحديد نقطة التمديد الخاصة بنا على فئة SermissionAntationMethodInterceptor ، ونحتاج أيضًا إلى استبدالها ككل. يمكن أن تدعم تعبيرات EL Spring قيم المعلمة طريقة التحليل. هنا نختار تقديم تعبيرات الربيع EL. عندما تحدد requirepermissions الأذونات ، يمكنك استخدام تعبيرات الربيع EL لتقديم معلمات الطريقة. في الوقت نفسه ، من أجل مراعاة النص الثابت. هنا قالب التعبير الربيعي. لقالب التعبير EL الخاص بـ Spring ، يرجى الرجوع إلى منشور المدونة هذا. نحدد مقبولنا الخاص بالذات ، ونرثه من PolisshanNotationMethodInterceptor ، وتجاوز طريقة التأكيد. يشير منطق تنفيذ الطريقة إلى المنطق في SomrissionAnnotationHandler ، ولكن تعريف الإذن في requirepermissions المستخدمة هو نتيجة استخدامنا للتعبير الربيعي EL استنادًا إلى الطريقة التي تسمى حاليًا نتيجة تحليل TolduationContext. فيما يلي تعريفنا الخاص لتطبيق SomrissAntationMethodInterceptor.
الطبقة العامة SelfpermissionAntationMethodInterceptor يمتد من الحصول على إذن methodinterceptor parameTerNameDiscoverEser الخاص الخاص = جديد defaultParameterNameDiscoverer () ؛ private templateparsercontext templateParserContext = new 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!