Shiro es un marco de control de permisos liviano con una amplia gama de aplicaciones. El enfoque de este artículo es introducir la integración de Spring de Shiro y habilitar parámetros dinámicos como @RquiresRoles se respalde extendiendo las expresiones EL de Spring. La introducción a Shiro no está dentro del alcance de este artículo. Si los lectores no saben mucho sobre Shiro, pueden aprender la información correspondiente a través de su sitio web oficial. También hay un artículo sobre InfoQ que proporciona una introducción integral a Shiro, y también se recomienda oficialmente. Su dirección es https://www.infoq.com/articles/apache-shiro.
Shiro integra la primavera
Primero, debe agregar shiro-spring-xxx.jar a su proyecto. Si está utilizando Maven para administrar su proyecto, puede agregar las siguientes dependencias a sus dependencias. Aquí está la última versión 1.4.0 que he seleccionado.
<Spendency> <MoupRoMID> org.apache.shiro </groupid> <artifactid> shiro-spring </artifactid> <versever> 1.4.0 </versión> </pendency>
A continuación, debe definir un shirofilter en su web.xml y aplicarlo para interceptar todas las solicitudes que requieren control de permiso, generalmente configurado como /*. Además, el filtro debe agregarse al frente para garantizar que la solicitud se controle primero a través de los permisos de Shiro después de que ingrese. La clase de filtro correspondiente aquí está configurada con DelegatingFilterProxy, que es un proxy de filtro proporcionado por Spring. Puede usar un frijol en el contenedor de frijol de resorte como la instancia de filtro actual, y el frijol correspondiente tomará el bean correspondiente al nombre de filtro. Por lo tanto, la siguiente configuración buscará un frijol llamado Shirofilter en el contenedor de frijoles.
<Sterry> <Sterry-name> shiroFilter </filter-name> <filter-class> org.springframework.web.filter.delegatingFilterProxy </filter-class> <itel-param> <amamname> targetFilterlifecycle </amamname-Name> <Armarr-Value> true </amam-value> <//initil-inyil> </filtro> </filtro> <filter-name> shirofilter </filter-name> <url-pattern>/*</sl-Pattern> </filter-mapping>
Cuando usa Shiro de forma independiente, generalmente define un org.apache.shiro.web.servlet.shirofilter para hacer algo similar.
Lo siguiente es definir nuestro Shirofilter en el recipiente de frijoles. De la siguiente manera, definimos un shirofilterfactorybean, que producirá un frijol tipofilter abstracto. A través de ShirofilterFactoryBean podemos especificar un SecurityManager. El valor predeterminado que se usó aquí se usa aquí para especificar un reino. Si se necesita especificar múltiples reinos, se especifica a través de reinos. Para simplificar, utilizamos TextConfigurationRealm basado directamente en la definición de texto. Use LoginUrl para especificar la dirección de inicio de sesión, SuccessUrl para especificar la dirección que debe redirigirse después de que el inicio de sesión sea exitoso, y no autorizado para especificar la página de solicitud cuando los permisos son insuficientes. FilterChainDefinitions define la relación entre la URL y el filtro que se utilizará. El alias de filtro en el lado derecho del signo igual es el alias de filtro. Los alias predeterminados se definen en la clase de enumeración org.apache.shiro.web.filter.mgt.defaultFilter.
<bean id = "shirofilter"> <Property name = "SecurityManager" ref = "SecurityManager"/> <Property Name = "Loginurl" valor = "/login.jsp"/> <Property name = "sateuturl" value = "/home.jsp"/> <Property name = "unauthorizedurl" value = "/unauthorized.jsp"/> <name de propiedad = "unauthorizeRl value="/unauthorized.jsp"/> <property name="filterChainDefinitions"> <value> /admin/** = authc, roles[admin] /logout = logout # Other addresses require that the user has logged in /** = authc,logger </value> </property></bean><bean id="securityManager"> <property name="realm" ref="realm"/></bean><bean id = "lifecycleBeanPostProcessor"/>
<!-Por simplicidad, utilizaremos la implementación del reino basada en texto aquí-> <bean id = "reasm"> <propiedad name = "userDefinitions"> <value> user1 = pass1, role1, rol2 user2 = pass2, role2, role3 admin = admin, admin </value> </bean>
Si necesita usar un filtro personalizado en la definición de FilterChainDefinitions, puede especificar el filtro personalizado y su relación de mapeo de alias a través de los filtros de ShirofilterFactoryBean. Por ejemplo, como se muestra a continuación, hemos agregado un filtro con Logger de alias y filtrado /** con filtro con alias en FilterChainDefinitions.
<bean id = "shirofilter"> <Property name = "SecurityManager" ref = "SecurityManager"/> <Property Name = "Loginurl" valor = "/login.jsp"/> <Property name = "sateuturl" value = "/home.jsp"/> <Property name = "unauthorizedurl" value = "/unauthorized.jsp"/> <name de propiedad = "unauthorizeRl value = "/unauthorized.jsp"/> <Property name = "Filters"> <util: map> <entry key = "logger"> <bean/> </entry> </util: map> </property> <Property name = "filterChaindefinitions"> <valor>/administrador/** = authc, roles [admin]/logrout = logrout # otras direcciones requieren que el usuario tiene el usuario que ha registrado/** **. </value> </property> </bean>
De hecho, la definición de alias de filtro que debemos aplicar también se puede definir directamente mediante setFilters () de ShirofilterFactoryBean (), pero definir directamente el filtro correspondiente correspondiente en el contenedor de frijoles correspondiente. Porque de forma predeterminada, ShirOfilterFactoryBean registrará todos los frijoles de tipo de filtro en el contenedor de frijoles con su alias de identificación en filtros. Por lo tanto, la definición anterior es equivalente a lo siguiente.
<bean id = "shirofilter"> <Property name = "SecurityManager" ref = "SecurityManager"/> <Property Name = "Loginurl" valor = "/login.jsp"/> <Property name = "sateuturl" value = "/home.jsp"/> <Property name = "unauthorizedurl" value = "/unauthorized.jsp"/> <name de propiedad = "unauthorizeRl value = "/unauthorized.jsp"/> <propiedad name = "filterChainDefinitions"> <value>/admin/** = Authc, roles [admin]/logrout = logrout # Otras direcciones requieren que el usuario haya iniciado sesión/** = authc, logger </value> </property> </bean> <bean id = "logger"/>>>
Después de los pasos anteriores, se completa la integración de Shiro y Spring. En este momento, cualquier ruta que solicitemos para el proyecto requerirá que inicie sesión, y saltará automáticamente a la ruta especificada por LoginUrl e ingresemos el nombre de usuario/contraseña para iniciar sesión. En este momento, debemos proporcionar un formulario para obtener el nombre de usuario a través del nombre de usuario, la contraseña a través de la contraseña y luego enviar la solicitud de inicio de sesión, se debe enviar la solicitud a la dirección especificada por loginurl, pero el método de solicitud, pero se debe cambiar la solicitud. El nombre de usuario/contraseña utilizado al iniciar sesión es el nombre de usuario/contraseña que definimos en TextConfigurationRealm. Según nuestra configuración anterior, puede usar User1/Pass1, Admin/Admin, etc. Después de iniciar sesión con éxito, saltará a la dirección especificada por el parámetro Successurl. Si estamos conectados usando User1/Pass1, también podemos intentar acceder/Admin/Index, y en este momento saltaremos a no autorizado.jsp debido a los permisos insuficientes.
Habilitar soporte basado en anotaciones
La integración básica requiere que definamos todos los controles de permiso que la URL necesita aplicar en FilterChainDefinitions of ShirofilterFactoryBean. Esto a veces no es tan flexible. Shiro nos proporciona anotaciones que se pueden usar después de integrar la primavera. Nos permite agregar anotaciones correspondientes a la clase o método que requieren control de permiso para definir los permisos requeridos para acceder a la clase o el método. Si está en clase en la definición, significa que llamar a todos los métodos de la clase requiere permisos correspondientes (tenga en cuenta que debe ser una llamada externa, que es la limitación del proxy dinámico). Para usar estas anotaciones, necesitamos agregar las siguientes dos definiciones de frijoles al contenedor de frijoles de Spring, para que podamos determinar si el usuario tiene los permisos correspondientes basados en la definición de anotación en tiempo de ejecución. Esto se logra a través del mecanismo AOP de primavera. Si no sabe nada sobre Spring AOP, puede consultar la "columna de Introducción Spring AOP del autor escrita por el autor. Las siguientes dos definiciones de frijoles, AuthorizationAtTributesUrCeAdVisor define un asesor, que interceptará y verificará los permisos basados en el método de configuración de anotación proporcionado por Shiro. DefaultAdVisoraUtoProxyCreator proporciona la función de crear objetos proxy para la clase marcadas con anotaciones de control de permisos proporcionadas por Shiro y aplicar la autorización de la cantidad de visualización al interceptar la llamada del método de destino. Cuando se intercepta una solicitud del usuario y el usuario no tiene permiso marcado en el método o clase correspondiente, se lanzará una excepción org.apache.shiro.authz.authorizationException.
<bean Depends-on = "lifecycleBeanPostProcessor"/> <bean> <propiedad name = "SecurityManager" ref = "SecurityManager" // </bean>
If <aop:config/>或<aop:aspectj-autoproxy/> ya está definido en nuestro contenedor de frijoles, entonces el valor predeterminado deDvisoraUtoProxyCreator ya no se puede definir. Porque los dos casos anteriores agregarán automáticamente los frijoles similares a DefaultAdVisoraUtoProxyCreator. Para obtener más información sobre DefaultAdvisoraUtOproxyCreator, también puede consultar el principio del autor de crear automáticamente objetos proxy en Spring AOP.
Las anotaciones de control del permiso proporcionadas por Shiro son las siguientes:
Se requiere autenticación: el usuario debe ser autenticado en la sesión actual, es decir, debe iniciar sesión con el nombre de usuario/contraseña, y no incluye el inicio de sesión automático RememberMe.
Requerir User: Se requiere que el usuario sea autenticado. Se puede autenticar iniciando sesión con el nombre de usuario/contraseña en esta sesión, o puede iniciarse automáticamente con RememberMe.
Requiereguest: el usuario no ha iniciado sesión.
RequiereRoles: el usuario requiere que se posea el rol especificado.
RequierePermissions: el usuario requiere los permisos especificados.
Los primeros tres son fáciles de entender, mientras que los dos últimos son similares. Aquí uso @RquiresPermissions como ejemplo. Primero, cambiemos el reino definido anteriormente y agregue permisos al rol. De esta manera, nuestro usuario1 tendrá permisos para Perm1, Perm2 y Perm3, y User2 tendrá permisos para Perm1, Perm3 y Perm4.
<bean id = "reasm"> <propiedad name = "userDefinitions"> <value> user1 = pass1, role1, rol2 user2 = pass2, role2, role3 admin = admin, admin </valor> </propiedad> <propiedad name = "roleDefinitions"> <al value> rol1 = perman1, perm2 rol2 = perm1, perm33 = permanente 3, perm4 </value> </Valor </Valor> </role
@RequiresPermissions se puede agregar en un método para especificar los permisos que deben reclamarse al llamar al método. En el siguiente código, especificamos que el permiso de Perm1 debe estar poseído al acceder /Perm1. En este momento, se puede acceder a User1 y User2.
@RequestMapping ("/PERM1")@RequesterPermissions ("Perm1") Permiso público Permission1 () {return "Perm1";}Si necesita especificar que debe tener múltiples permisos al mismo tiempo para acceder a un método, puede especificar los permisos que necesita especificar en forma de una matriz (al especificar un atributo de una sola matriz en la anotación, no puede agregar aparatos ortopédicos, pero cuando necesita especificar múltiples permisos, se requieren broters). Por ejemplo, de la siguiente manera, especificamos que al acceder /Perm1andPerm4, el usuario debe tener permisos Perm1 y Perm4. En este momento, solo User2 puede acceder a él, porque solo tiene Perm1 y Perm4 al mismo tiempo.
@RequestMapping ("/PERM1AndPerm4")@RequierePermissions ({"Perm1", "Perm4"}) Objeto público Perm1AndPerm4 () {return "Perm1andPerm4";}Cuando se especifican múltiples permisos al mismo tiempo, la relación entre múltiples permisos es la relación, es decir, se requieren todos los permisos especificados al mismo tiempo. Si solo necesita tener uno de los múltiples permisos especificados para que sean accesibles, podemos especificar la relación entre el OR entre múltiples permisos a través de Logical = Logical.or. Por ejemplo, de la siguiente manera, especificamos que al acceder /Perm1ORPerm4, solo necesita tener permisos Perm1 o Perm4, para que tanto User1 como User2 puedan acceder a este método.
@RequestMapping ("/PERM1ORPERM4")@requierePermissions (valor = {"Perm1", "Perm4"}, Logical = Logical.or) Object público Perm1ORPerm4 () {return "Perm1ORPerm4";}@RequiresPermissions también se puede marcar en clase, lo que indica que al acceder a los métodos en clase externamente, debe tener los permisos correspondientes. Por ejemplo, a continuación, especificamos que necesitamos tener permiso Perm2 a nivel de clase, mientras que el método index () no especifica que necesitemos ningún permiso, pero aún necesitamos tener permisos especificados en el nivel de clase al acceder a este método. En este momento, solo User1 puede acceder a él.
@RestController@requestMapping ("/foo")@RequiessPermissions ("Perm2") Public Class Foocontroller {@RequestMapping (método = requestMethod.get) Public Object index () {Map <String, Object> Map = New HashMap <> (); map.put ("ABC", 123); mapa de retorno; }}Cuando tanto los niveles de clase como de método tienen @requirespermissions, el nivel de método tiene una prioridad más alta, y solo se verificarán los permisos requeridos por el nivel de método en este momento. De la siguiente manera, especificamos que el permiso Perm2 se requiere a nivel de clase y se requiere el permiso Perm3 a nivel de método. Luego, al acceder /FOO, solo necesita tener permisos Perm3 para acceder al método index (). Entonces, en este momento, tanto User1 como User2 pueden acceder /FOO.
@RestController @requestMapping ("/foo") @RequiessperMissions ("Perm2") Public Class Foocontroller {@RequestMapping (método = requestmethod.get) @requirespermissions ("PERM3") Public Object Index () {MAP <String, Object> MAP = new Hashmap <> (); map.put ("ABC", 123); mapa de retorno; }}Sin embargo, si agregamos @RquiresRoles ("rol1") a la clase en este momento para especificar que necesitamos tener rol rol1, luego, al acceder /foo, necesitamos tener el rol1 especificado por @requirespermissions ("perman3") en el rol1 en el método index () en clase. Debido a que los requisitos y las operaciones requeridas pertenecen a las definiciones de permiso de diferentes dimensiones, Shiro los verificará una vez durante la verificación, pero si tanto la clase como el método tienen anotaciones del mismo tipo de definición de control de permiso, la definición en el método solo se basará en la definición.
@RestController@requestMapping ("/foo")@requerido requerido ("perman2")@requerirroles ("rol1") public class foocontroller {@RequestMapping (método = requestmethod.get) @RequiresPermissions ("Perm3") Public Object index () {MAP <String> map = map = newhMap <> (); map.put ("ABC", 123); mapa de retorno; }}Aunque el ejemplo solo usa las necesidades requeridas, el uso de otras anotaciones de control de permisos también es similar. Utilice otras anotaciones de amigos interesados.
Principio de permisos de control de anotación
Los permisos que especificamos anteriormente son estáticos usando @RquiresPermissions. Uno de los propósitos principales de este artículo es introducir un método para hacer la dinámica de los permisos especificados al extender la implementación. Pero antes de expandirnos, tenemos que saber cómo funciona, es decir, el principio de implementación, antes de que podamos expandirnos. Así que echemos un vistazo a cómo Shiro integra la primavera con @RquiresPermissions. Al habilitar el soporte para @RquiresPermissions, definimos el siguiente bean, que es un asesor, que se hereda de StaticMethodMatcherPointCutAdvisor. Su lógica de coincidencia del método es que mientras la clase o el método tenga varias anotaciones de control de permisos de Shiro, el consejo correspondiente se especifica la lógica de procesamiento después de la intercepción.
<Bean> <Property name = "SecurityManager" ref = "SecurityManager"/> </le bean>
El siguiente es el código fuente de AuthorizationAttributesurCeAdVisor. Podemos ver que en su método de constructor, SetAdVice () se especifica aOPALLIANCEANNOTATIONATIONATIONSMethodinterceptor, que se basa en la implementación de MethodInterceptor.
Autorización de clase públicaattributesOurCeAdVisor extiende staticMethodMatcherPointCutAdvisor {private static final logger log = loggerFactory.getLogger (autorizatizatTributesOurCeadVisor.class); Clase final estática privada <? extiende la anotación> [] authz_annotation_classes = new class [] {requierePermissions.class, requestRoles.class, request.class, requestsguest.class, requerido autentication.class}; SecurityManager protegido SecurityManager = nulo; Public AuthorizationAttributeSourCeadVisor () {setAdVice (new AopallianceanNotationsAuthorizingMethodinterceptor ()); } public SecurityManager getSecurityManager () {return SecurityManager; } public void setSecurityManager (org.apache.shiro.mgt.securityManager SecurityManager) {this.securityManager = SecurityManager; } Public Boolean Matches (método, método, clase TargetClass) {método m = método; if (isauthzannotationPresent (m)) {return true; } // El parámetro 'Método' podría ser de una interfaz que no tiene la anotación. // Verifique si la implementación la tiene. if (TargetClass! = NULL) {try {M = TargetClass.getMethod (m.getName (), m.getParametertyPes ()); return isauthzannotationPresent (m) || IsauthzannotationPresent (TargetClass); } Catch (nosuchmethodException ignorado) {// El valor de retorno predeterminado es falso. Si no podemos encontrar el método, obviamente // no hay anotación, así que solo use el valor de retorno predeterminado. }} return false; } boolean privado IsauthzannotationPresent (class <?> TargetClazz) {para (class <? Extensa Annotation> Annclass: AuthZ_Annotation_Classes) {Annotation A = AnnotationUtilss.Findannotation (TargetClazz, AnnClass); if (a! = null) {return true; }} return false; } boolean privado IsauthzannotationPresent (método Método) {para (class <? Extensa anotación> Annclass: authz_annotation_classes) {anotación a = annotationutils.findannotation (método, annclass); if (a! = null) {return true; }} return false; }}El código fuente de AopallianCeanNotationsAuthorizingMethodinterceptor es el siguiente. El método de Invocación de la interfaz de Interceptor de método implementada en TI llama al método de invocación de la clase principal. Al mismo tiempo, necesitamos ver que algunas implementaciones de Autorizing AnnotationMethodinterceptor se hayan creado en su método de constructor. Estas implementaciones son el núcleo de implementación de control de permisos. Más tarde, seleccionaremos la Clase de implementación de PermissionSanTationMethodinterceptor para ver su lógica de implementación específica.
Clase pública AOPALLIANCeanNotationsAuthorizingMethodInterceptor Extiens AnnotationsAuthorizingMethodInterceptor implementa Methodinterceptor {public aOpallianceanNotationationsAuthorizingMethodinterceptore () {List <AuthoratingMethodinterceptor> Interceptors = NewRayList <autorizatinganToDinterceptoreTorscor) (5) (5); // Use un resolución de anotaciones específicos de primavera: los anotaciones de Spring es más agradable que el proceso de resolución JDK //. Anotationresolver resolver = new SpringAnnotationResolver (); // Podemos reutilizar la misma instancia de resolución: no retiene el estado: Interceptors.Add (nuevo RoleanNotationMethodinterceptor (resolución)); Interceptors.Add (nuevo PermissionAntationMethodinterceptor (resolución)); Interceptors.Add (New UserAnnotationMethodinterceptor (resolución)); Interceptors.Add (New GuestAnnotationMethodinterceptor (resolución)); setMethodinterceptors (interceptores); } protegido org.apache.shiro.aop.methodinVocation createMethodInVocation (objeto implSpecificMethodInVocation) {final MethodInVocation mi = (MethodInVocation) ImpecificMethodinVocation; return new org.apache.shiro.aop.methodinVocation () {Method público getMethod () {return mi.getMethod (); } objeto público [] getArGuments () {return mi.getArGuments (); } public String toString () {return "Method Invocation [" + mi.getMethod () + "]"; } Public Object proceds () lanza lanzable {return mi.procede (); } objeto público getThis () {return mi.getthis (); }}; } Objeto protegido ContinerinVocation (objeto aopalliancemethodinVocation) lanza lando {metodInVocation mi = (metodInVocation) aopalliancemethodinVocation; return mi.proceed (); } Public Object Invoke (MethodInVocation MethodInVocation) lanza lanzables {org.apache.shiro.aop.methodinVocation mi = createMethodInVocation (metodInVocation); regresar super.invoke (MI); }}Al observar la implementación del método de Invoke de la clase principal, finalmente veremos que la lógica principal es llamar al método ASERTAUTHORIZADO, y la implementación de este método (el código fuente es el siguiente) es determinar si la autorización configurada de la autorización de Methodinterceptor admite la verificación del permiso del método actual (mediante la juzgar si tiene su anotación compatible con la clase o método). Cuando se le respalde, su método asertautorizado se solicitará a la verificación de permiso, y el AutorizingNotationMethodinterceptor a su vez llamará al método asertautorizado de autorización de ytationhandler.
Void protegido asertautorizado (MethodInvocation MethodInvocation) lanza AuthorizationException {// Implementación predeterminada solo asegura que no se emitan votos: Collection <AuthoringAntationMethodinterceptor> aamis = getMethodinterceptors (); if (aamis! = null &&! aamis.isempty ()) {para (autorizarannotationMethodinterceptor aami: aamis) {if (aami.supports (metodinvocation)) {aami.asstauthorized (metodinvocation); }}}}A continuación, veamos hacia atrás al permisoantationMethodinterceptor definido por AopallianCeanNotationsAuthorizingMethodinterceptor, el código fuente es el siguiente. Combinando el código fuente de AOPALLIANCEANNOTATIONATIONATIONIZANDOMethodinterceptor y el Código de Permiso de PermissionSannotationMethodinterceptor, podemos ver que el PermissionSannotationHandler y SpringAnnotationResolver se especifican en PermissionAntatationMethodinterceptor. PermissionSannotationHandler es una subclase de AuthorizingNotationHandler. Por lo tanto, nuestro control de permiso final está determinado por la implementación asertautorizada de PermissionSannotationHandler.
Permiso de clase pública ElNotationMethodinterceptor se extiende la autorización de yotationMethodinterceptor {Public PermissionSannotationMethodinterceptor () {Super (New PermissionSanTatationHandler ()); } PROMISIÓN PUBLICANETATATIONMethodinterceptor (annotationresolver resolver) {super (nuevo permiso yotationHanler (), resolución); }}A continuación, veamos la implementación del método asertautorizado de PermissionSannotationHandler, y el código completo es el siguiente. Desde la implementación podemos ver que obtendrá el valor de permiso configurado de la anotación, y la anotación aquí es la anotación de necesidades de permeación. Además, al realizar la verificación del permiso, usamos directamente el valor de texto especificado al definir la anotación. Comenzaremos desde aquí cuando lo expandamos más tarde.
PROMISIÓN DE LA CLASE PUBLICAnotationHandler extiende AuthorizingAnnotationHandler {Public PermissionSannotationHandler () {super (requierePermissions.class); } cadena protegida [] getAnnotationValue (anotación a) {requierePermissions rPannotation = (RequierePermissions) a; return rPannotation.Value (); } public void asertauthauthorized (anotación a) arroja autorizaciónxception {if (! (una instancia de requierepermissions)) return; RequierePermissions rPannotation = (requiere Permisos) a; Cadena [] permans = getAnnotationValue (a); Sujeto sujeto = getSubject (); if (perms.length == 1) {Sujeto.CheckPermission (Perms [0]); devolver; } if (logical.and.equals (rPannotation.logical ())) {getSubject (). checkPermissions (Perms); devolver; } if (logical.or.equals (rPannotation.Logical ())) {// Evite el procesamiento de excepciones de forma inequívoca - "retrasar", lanzando la excepción llamando a Hasrole First Boolean HasatLeastOnePermission = false; para (Permiso de cadena: Perms) if (getSubject (). ISPERMITET (Permiso)) HasatLeastOnePermission = True; // Causa la excepción si ninguna de las coincidencias de roles, tenga en cuenta que el mensaje de excepción será un poco engañoso si (! HasatLeastOnperMission) getSubject (). CheckPermission (Perms [0]); }}}A través de la introducción anterior, sabemos que la anotación del parámetro del método asertautorizado del permiso y la manipulación se aprueba autorizando la anotación de modificación cuando llame al método asertautorizado de autorización de ytationhandler. El código fuente es el siguiente. Del código fuente, podemos ver que la anotación se obtiene a través del método getNotation.
public void asertauthorized (MethodInVocation MI) lanza AuthorizationException {try {((autorizatingAnnotationHandler) gethandler ()). AsserTauthorized (getAnnotation (mi)); } catch (AuthorizationException ae) {if (ae.getCause () == null) ae.initCause (nueva autorizaciónxception ("no autorizado para invocar el método:" + mi.getmethod ())); tirar ae; }}Caminando en esta dirección, eventualmente encontraremos la implementación del método GetAnnotation de SpringAnnotationResolver, que se implementa de la siguiente manera. Como se puede ver en el siguiente código, se prefiere buscar el método al buscar anotaciones. Si no se encuentra en el método, buscará la anotación correspondiente de la clase de la llamada del método actual. A partir de aquí, también podemos ver por qué el que entra en vigencia en el método cuando definimos el mismo tipo de anotación de control de permiso en la clase y el método antes, y cuando existe solo, el que está definido entra en vigencia.
Clase pública SpringAnnotationResolver implementa anotationresolver {anotación pública getAnnotation (MethodInVocation mi, clase <? Extensa anotación> clazz) {método m = mi.getmethod (); Anotación a = anotationutil.findannotation (m, clazz); if (a! = null) devolver a; // El objeto Método de MethodinVocation podría ser un método definido en una interfaz. // Sin embargo, si la anotación existía en la implementación de la interfaz (y no // la interfaz en sí), no estará en el objeto del método anterior. En su lugar, necesitamos // adquirir la representación del método de TargetClass y verificar directamente en la implementación // en sí: class <?> TargetClass = Mi.getThis (). GetClass (); m = classUtils.getMemSpecificMethod (m, TargetClass); a = anotationutil.findannotation (m, clazz); if (a! = null) devolver a; // ver si la clase tiene la misma anotación annotationUtiliTils.findannotation (mi.getthis (). GetClass (), clazz); }} A través de la lectura del código fuente anterior, creo que los lectores tienen una comprensión más profunda del principio de las anotaciones de control de permisos respaldadas por Shiro después de integrar la primavera. El código fuente publicado anteriormente es solo algunos de los principales que el autor cree que son relativamente centrales. Si desea conocer el contenido completo en detalle, lea el código completo por sí mismo a lo largo de las ideas mencionadas por el autor.
Después de comprender este principio de control del permiso basado en anotaciones, los lectores también pueden expandirse en consecuencia de acuerdo con las necesidades comerciales reales.
Extendido usando Spring El Expressions
Supongamos que ahora hay una interfaz como la siguiente, que tiene un método de consulta que recibe un tipo de parámetro. Simplifiquemos aquí, suponiendo que mientras se reciba dicho parámetro y se devolverán diferentes valores.
interfaz pública RealService {Consulta de objeto (int type); }Esta interfaz está abierta al mundo exterior. Este método se puede solicitar a través de la URL correspondiente. Definimos el método del controlador correspondiente de la siguiente manera:
@RequestMapping ("/Service/{type}") Public Object Query (@PathVariable ("type") int type) {return this.realService.query (type);}El servicio de interfaz anterior tiene permisos para el tipo al realizar una consulta. No todos los usuarios pueden usar cada tipo para consultar, y requiere permisos correspondientes. Por lo tanto, para el método del procesador anterior, debemos agregar control de permiso y los permisos requeridos durante el control de control dinámicamente con el tipo de parámetro. Suponga que la definición de cada permiso de tipo es la forma de consulta: tipo. Por ejemplo, el permiso requerido cuando el tipo de tipo = 1 es consulta: 1, y el permiso requerido cuando el tipo = 2 es consulta: 2. Cuando no está integrado con la primavera, hacemos esto de la siguiente manera:
@RequestMapping ("/Service/{type}") Public Object Query (@PathVariable ("type") int type) {SecurityUtils.getSubject (). CheckPermission ("Query:" + Type); Devuelve this.RealService.query (type);}Sin embargo, después de la integración con la primavera, las prácticas anteriores están altamente acopladas, y preferiríamos usar las anotaciones integradas para controlar los permisos. Para el escenario anterior, preferiríamos especificar los permisos requeridos a través de @requirespermissions, pero los permisos definidos en @RquiresPermissions son texto estático y fijos. No puede satisfacer nuestras necesidades dinámicas. En este momento, puede pensar que podemos dividir el método de procesamiento del controlador en múltiples y controlar los permisos por separado. Por ejemplo, lo siguiente es:
@RequestMapping ("/Servicio/1")@RequestPermissions ("Query: 1") Public Object Service1 () {return this.RealService.Query (1);}@requierePermissions ("Query: 2")@requestMapping ("/Service/2") Public Object Service2 () {return return this.RealService.Query (2);} //...@ requestmapping ("/servicio/200")@RequestPermissions ("Consulta: 200") Public Object Service200 () {return this.realservice.query (200);}Esto está bien cuando el rango de valor de tipo es relativamente pequeño, pero si hay 200 valores posibles como el anterior, será un poco problemático enumerarlos exhaustivamente para definir un método de procesador separado y realizar un control de permiso. Además, si el valor del tipo cambia en el futuro, tenemos que agregar un nuevo método de procesador. Por lo tanto, la mejor manera es hacer que @RquiresPermissions Support Definiciones de permiso dinámico, al tiempo que mantiene el soporte de definición estática. A través del análisis anterior, sabemos que el punto de entrada es el permiso y la mano de vista, y no proporciona extensiones para la verificación del permiso. Si queremos expandirlo, la forma simple es reemplazarlo como un todo. Sin embargo, los permisos que necesitamos procesar dinámicamente están relacionados con los parámetros del método, y los parámetros del método no se pueden obtener en el PermissionSannotationHandler. Por esta razón, no podemos reemplazar directamente el PermissionSannotationHandler. PermissionSannotationHandler se llama por permisoannotationMethodinterceptor. Los parámetros del método se pueden obtener cuando el permiso y elhandler se denominan en el método asertautorizado de su clase principal que autoriza el Methodinterceptor. Por esta razón, nuestro punto de extensión se selecciona en la clase de anotaciones de permiso y nointerceptor, y también necesitamos reemplazarlo como un todo. Las expresiones EL de Spring pueden admitir valores de parámetros del método de análisis. Aquí elegimos presentar las expresiones El de Spring. Cuando @RquiresPermissions define los permisos, puede usar Spring El Expressions para introducir parámetros de método. Al mismo tiempo, para tener en cuenta el texto estático. Aquí hay una plantilla de expresión de Spring El. Para la plantilla de expresión de EL de Spring, consulte esta publicación de blog. Definimos nuestro propio permiso ,NotationMethodinterceptor, lo heredamos del permiso y elinterceptor del permiso y anulamos el método asertAuthoried. La lógica de implementación del método se refiere a la lógica en PermissionSannotationHandler, pero la definición de permiso en @RquiresPermissions utilizada es el resultado de nuestro uso de la expresión de Spring EL basado en el método actualmente llamado como resultado de la reducción de la evaluación de texto. La siguiente es nuestra propia definición de PermissionSannotationMethodinterceptor Implementación.
clase pública SelfpermissionAnnotationMethodinterceptor extiende el permisoannotationMethodinterceptor {private final spelexpressionParser parser = new SpelExpressionParser (); PARAMETERNADERSEDSECURNADO PRIVADO Final ParamNamedScoverer = new DefaultParamTernamedScoverer (); TemplateParsercontext privado final 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]); devolver; } if (Logical.AND.equals(permAnnotation.logical())) { getSubject().checkPermissions(perms); devolver; } 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的权限。
Resumir
The above is a detailed explanation of Spring integrating Shiro and expanding the use of EL expressions introduced by the editor. Espero que sea útil para todos. Si tiene alguna pregunta, déjame un mensaje y el editor le responderá a tiempo. ¡Muchas gracias por su apoyo al sitio web de Wulin.com!