English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
最近项目需要用到Spring Security的权限控制,故花了点时间简单的去看了一下其权限控制相关的源码(版本为4。2)。
AccessDecisionManager
Spring Security是通过AccessDecisionManager进行授权管理的,先来张官方图镇楼。
AccessDecisionManager
El interfaz AccessDecisionManager define los siguientes métodos:
//Se llama a AccessDecisionVoter para votar (método clave) void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException; boolean supports(ConfigAttribute attribute); boolean supports(Class clazz);
Ahora veamos la implementación específica de su clase:
AffirmativeBased
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException { int deny = 0; for (AccessDecisionVoter voter : getDecisionVoters()) { //Se llama a AccessDecisionVoter para votar (llamémoslo voto), más adelante veremos el código fuente de vote. int result = voter.vote(authentication, object, configAttributes); if (logger.isDebugEnabled()) { logger.debug("Voter: " + voter + ", returned: " + result); } switch (result) { case AccessDecisionVoter.ACCESS_GRANTED://El valor es1 //Si el votante vota ACCESS_GRANTED, se aprueba case AccessDecisionVoter.ACCESS_DENIED://El valor es-1 deny++; break; default: break; } } if (deny > 0) { //Si dos o más AccessDecisionVoter (llamémoslos votantes) votan ACCESS_DENIED, se considera directamente no aprobado throw new AccessDeniedException(messages.getMessage( "AbstractAccessDecisionManager.accessDenied", "Access is denied"); } // To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions(); }
A través del código anterior se puede ver directamente la estrategia AffirmativeBased:
UnanimousBased
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) throws AccessDeniedException { int grant = 0; int abstain = 0; List<ConfigAttribute> singleAttributeList = new ArrayList<ConfigAttribute>(1); singleAttributeList.add(null); for (ConfigAttribute attribute : attributes) { singleAttributeList.set(0, attribute); for (AccessDecisionVoter voter : getDecisionVoters()) { //El votante configurado realiza la votación int result = voter.vote(authentication, object, singleAttributeList); if (logger.isDebugEnabled()) { logger.debug("Voter: " + voter + ", returned: " + result); } switch (result) { case AccessDecisionVoter.ACCESS_GRANTED: grant++; break; case AccessDecisionVoter.ACCESS_DENIED: //Si cualquier votante vota en contra, se considera inmediatamente sin acceso throw new AccessDeniedException(messages.getMessage( "AbstractAccessDecisionManager.accessDenied", "Access is denied");}} default: abstain++; break; } } } // Para llegar a este punto, no hubo votos en contra if (grant > 0) { //Si no hay votos en contra y hay votos a favor, se considera que se aprueba } // To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions(); }
Por lo tanto, la estrategia de UnanimousBased es:
ConsensusBased
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException { int grant = 0; int deny = 0; int abstain = 0; for (AccessDecisionVoter voter : getDecisionVoters()) { //El votante configurado realiza la votación int result = voter.vote(authentication, object, configAttributes); if (logger.isDebugEnabled()) { logger.debug("Voter: " + voter + ", returned: " + result); } switch (result) { case AccessDecisionVoter.ACCESS_GRANTED: grant++; break; case AccessDecisionVoter.ACCESS_DENIED: deny++; break; default: abstain++; break; } } if (grant > deny) { //Si el número de votos a favor es mayor que el número de votos en contra, se considera que se aprueba } if (deny > grant) { //Si el número de votos a favor es menor que el número de votos en contra, se considera que no se aprueba throw new AccessDeniedException(messages.getMessage( "AbstractAccessDecisionManager.accessDenied", "Access is denied"); } if ((grant == deny) && (grant != 0)) { //this.allowIfEqualGrantedDeniedDecisions por defecto es true //通过的票数和反对的票数相等,则可根据配置allowIfEqualGrantedDeniedDecisions进行判断是否通过 if (this.allowIfEqualGrantedDeniedDecisions) { } de lo contrario { throw new AccessDeniedException(messages.getMessage( "AbstractAccessDecisionManager.accessDenied", "Access is denied"); } } // To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions(); }
由此可见,ConsensusBased的策略:
到此,应该明白AffirmativeBased、UnanimousBased、ConsensusBased三者的区别了吧,spring security默认使用的是AffirmativeBased, 如果有需要,可配置为其它两个,也可自己去实现。
投票者
以上AccessDecisionManager的实现类都只是对权限(投票)进行管理(策略的实现),具体投票(vote)的逻辑是通过调用AccessDecisionVoter的子类(投票者)的vote方法实现的。spring security默认注册了RoleVoter和AuthenticatedVoter两个投票者。下面来看看其源码。
AccessDecisionManager
boolean supports(ConfigAttribute attribute); boolean supports(Class<63;> clazz); //方法核心,此方法由上面介绍的AccessDecisionManager调用,子类实现此方法进行投票。 int vote(Authentication authentication, S object,) Collection<ConfigAttribute> attributes);}}
RoleVoter
private String rolePrefix = "ROLE_"; //Sólo se manejan los que comienzan con ROLE_ (se puede cambiar el valor de rolePrefix en la configuración) public boolean supports(ConfigAttribute attribute) { if ((attribute.getAttribute() != null) && attribute.getAttribute().startsWith(getRolePrefix())) { return true; } de lo contrario { return false; } } public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) { if(authentication == null) { //Si el usuario no ha pasado la autenticación, se vota en contra return ACCESS_DENIED; } int result = ACCESS_ABSTAIN; //Obtener los permisos reales del usuario Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication); for (ConfigAttribute attribute : attributes) { if (this.supports(attribute)) { result = ACCESS_DENIED; // Intentar encontrar una autoridad concedida coincidente for (GrantedAuthority authority : authorities) { if (attribute.getAttribute().equals(authority.getAuthority())) { //Si coincide la autoridad, se vota a favor return ACCESS_GRANTED; } } } } //Si se ha procesado pero no se ha votado a favor, es una votación en contra, si no se ha procesado, se considera una abstención (ACCESS_ABSTAIN). devolver resultado; }
Muy simple, además, también podemos extender nuestro propio voter implementando AccessDecisionManager. Pero, para lograr esto, también debemos entender de dónde viene el parámetro attributes, este es un parámetro muy crucial. Una imagen oficial puede ilustrar este problema claramente:
Ahora, echemos un vistazo al llamador de AccessDecisionManager, AbstractSecurityInterceptor.
AbstractSecurityInterceptor
... //Como se mencionó anteriormente, el predeterminado es AffirmativeBased, configurable private AccessDecisionManager accessDecisionManager; ... protected InterceptorStatusToken antesDeInvocación(Object object) { ... //Método abstracto, la subclase lo implementa, pero también se puede ver que ConfigAttribute se obtiene de SecurityMetadataSource (de hecho, el predeterminado es DefaultFilterInvocationSecurityMetadataSource). Colección<ConfigAttribute> atributos = this.obtenerFuenteMetadatosSeguridad() .getAttributes(object); ... //Obtener la información del usuario autenticado actual Autenticación autenticado = autenticarSiEsNecesario(); try { //Llamar a AccessDecisionManager this.accessDecisionManager.decide(autenticado, object, atributos); } catch (ExcepciónDenegaciónAcceso accessDeniedException) { publicar evento(new EventoFalloAutorización(object, atributos, autenticado, excepción de acceso denegado)); lanzar excepción de acceso denegado; } ... } public abstract SecurityMetadataSource obtainSecurityMetadataSource();
Estos métodos son llamados por las subclases de AbstractSecurityInterceptor (por defecto es FilterSecurityInterceptor), así que veamos:
FilterSecurityInterceptor
... //La clase de implementación de SecurityMetadataSource, lo que indica que se puede configurar externamente. Esto también sugiere que podemos extender el SecurityMetadataSource mediante la implementación personalizada de la clase de implementación de SecurityMetadataSource para obtener el ConfigAttribute necesario en la práctica private FilterInvocationSecurityMetadataSource securityMetadataSource; ... //punto de entrada public void doFilter(ServletRequest request, ServletResponse, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); //método clave invoke(fi); } public void invoke(FilterInvocation fi) throws IOException, ServletException { si ((fi.getRequest() != null) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null) && observarUnaVezPorSolicitud) { // filtro ya aplicado a esta solicitud y el usuario desea que lo observemos // una vez-per-manejo de solicitud, por lo que no se debe volver a-realizar verificación de seguridad fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } de lo contrario { // primera vez que se realiza esta solicitud, por lo que se realiza verificación de seguridad if (fi.getRequest() != null) { fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); } //Aquí se llama al método del padre (AbstractSecurityInterceptor), lo que también llama a accessDecisionManager InterceptorStatusToken token = super.beforeInvocation(fi); try { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.finallyInvocation(token); } //Luego ejecuta (método del padre), uno detrás del otro, AOP en todas partes super.afterInvocation(token, null); } }
Bueno, aquí debería estar bastante claro sobre la gestión de permisos de Spring Security. Después de leer esto, ¿puedes expandir un conjunto de necesidades de permisos que se adapte a tus necesidades? Si aún no estás muy claro, no importa, en el siguiente artículo lo haremos en práctica, desarrollando un sistema de permisos propio basado en él.
Esto es todo el contenido de este artículo, espero que haya sido útil para su aprendizaje y que todos apoyen a la tutorial de alarido.
Declaración: El contenido de este artículo se ha obtenido de la red, es propiedad del autor original, el contenido se ha contribuido y subido por los usuarios de Internet de manera autónoma, este sitio no posee los derechos de propiedad, no ha sido editado por humanos y no asume responsabilidad alguna por las responsabilidades legales. Si encuentra contenido sospechoso de infracción de derechos de autor, por favor envíe un correo electrónico a: notice#oldtoolbag.com (al enviar un correo electrónico, reemplace # con @) para denunciar y proporcionar evidencia relevante. Una vez confirmado, este sitio eliminará inmediatamente el contenido sospechoso de infracción.