spring security验证流程
工作需要,又弄起了权限的管理。虽然很早以前都了解过基于容器的权限实现方式,但是一直都觉得那东西太简陋了。后来使用liferay时发现它的权限系统的确做得很优秀,感觉这也可能是它做得最出色的地方吧。但是当时只停留在怎么使用及一些与其衔接的关系之上,并没有对其底层进行了解,新到现在的公司后,发现这一课还是得补上。但是令人惊讶的是,目前可用的选择并不多,甚至很少,最有名的当属spring security了,虽然很有名,但是关于这方面的资料并不是很多,应用示例就更少了。还好有中文的官方文档与http://www.family168.com/bbs/发布的简要教程,因此学起来不至于太困难。然后参照了一篇downpour写的spring security文章,因此勉强熟悉了spring security的应用开发,但是基本只停留在勉强会用的基础之上,而且还花了本人不少时间,在一个项目的运用中,自己更是差点没下得了台,惊出了一身冷汗。当时的感觉spring security就是个垃圾东西,运用很复杂,哪怕是做一个只拦截路径的权限系统,也要经过很多步骤。现在熟悉了它的一些流程后,虽然不知道这样的实现方式是否是最合理的,但是的确也有它的道理。现在利用放假期间,可以静下心来,理解一些以前让自己迷惑的东西了。downpour牛人的那篇文章讲得很好,以至于本人着实花了点时间才把它完全熟悉,当前自己以前对acegi并不熟悉。熟悉了那篇文章后,还是有些地方让自己不太理解,其中之一就是spring security是怎样完成用户角色权限验证的。下面就对这人问题进行简单的介绍:
首先这篇文章是基于downpour那篇文章的,其地址为:
http://www.iteye.com/topic/319965
最先着手就是配置文件,这也是整个spring security最重要的入口点:
............<!-- 处理国际化信息 --><beans:bean id="authenticationManager"ref="messageSource" /></beans:bean><beans:bean id="messageSource"/></beans:bean><authentication-provider user-service-ref="securityManager"><password-encoder hash="md5" /></authentication-provider><!-- AffirmativeBased表示只要有一个Voter通过权限要求,就可以访问 --><beans:bean id="accessDecisionManager"/><beans:property name="decisionVoters"><beans:list><!-- RoleVoter默认角色名称都要以ROLE_开头,否则不会被计入权限控制,如果要修改前缀,可以通过对rolePrefix属性进行修改 --><beans:bean /><beans:bean /></beans:list></beans:property></beans:bean><beans:bean id="resourceSecurityInterceptor"ref="authenticationManager" /><beans:property name="accessDecisionManager" ref="accessDecisionManager" /><beans:property name="objectDefinitionSource"ref="secureResourceFilterInvocationDefinitionSource" /><!-- 每次请求都进行检查,如果设为true,则只第一次检查,默认为true --><beans:property name="observeOncePerRequest" value="false" /><custom-filter after="LAST" /></beans:bean><beans:bean id="secureResourceFilterInvocationDefinitionSource"/>....
if ((grant == deny) && (grant != 0)) { if (this.allowIfEqualGrantedDeniedDecisions) { return; } else { throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied")); } }
<bean id="roleVoter" value="AUTH_"/></bean>
..... public ConfigAttributeDefinition getAttributes(Object filter) throws IllegalArgumentException { FilterInvocation filterInvocation = (FilterInvocation) filter; String requestURI = filterInvocation.getRequestUrl(); Map<String, String> urlAuthorities = this.getUrlAuthorities(filterInvocation); String grantedAuthorities = null; for(Iterator<Map.Entry<String, String>> iter = urlAuthorities.entrySet().iterator(); iter.hasNext();) { Map.Entry<String, String> entry = iter.next(); //url表示从资源表取出的值,在这里代表的是相应的URL String url = entry.getKey(); //这段代码表示数据库内的需要验证的资源URL与当前请求的URL相匹配时进行验证 if(urlMatcher.pathMatchesUrl(url, requestURI)) { //grantedAuthorities表示每个资源对应的角色,如果有多个角色,则以','隔开 grantedAuthorities = entry.getValue(); break; } } if(grantedAuthorities != null) { ConfigAttributeEditor configAttrEditor = new ConfigAttributeEditor(); configAttrEditor.setAsText(grantedAuthorities); return (ConfigAttributeDefinition) configAttrEditor.getValue(); } return null; }....
public void invoke(FilterInvocation fi) throws IOException, ServletException { if ((fi.getRequest() != null) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null) && observeOncePerRequest) { // filter already applied to this request and user wants us to observce // once-per-request handling, so don't re-do security checking fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } else { // first time this request being called, so perform security checking if (fi.getRequest() != null) { fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); } InterceptorStatusToken token = super.beforeInvocation(fi); try { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.afterInvocation(token, null); } } }
/*** Indicates whether once-per-request handling will be observed. By default this is <code>true</code>,meaning the <code>FilterSecurityInterceptor</code> will only execute once-per-request. Sometimes users may wishit to execute more than once per request, such as when JSP forwards are being used and filter security is desired on each included fragment of the HTTP request.*/
protected InterceptorStatusToken beforeInvocation(Object object) { Assert.notNull(object, "Object was null"); if (!getSecureObjectClass().isAssignableFrom(object.getClass())) { throw new IllegalArgumentException("Security invocation attempted for object " + object.getClass().getName() + " but AbstractSecurityInterceptor only configured to support secure objects of type: " + getSecureObjectClass()); } ConfigAttributeDefinition attr = this.obtainObjectDefinitionSource().getAttributes(object); if (attr == null) { if (rejectPublicInvocations) { throw new IllegalArgumentException( "No public invocations are allowed via this AbstractSecurityInterceptor. " + "This indicates a configuration error because the " + "AbstractSecurityInterceptor.rejectPublicInvocations property is set to 'true'"); } if (logger.isDebugEnabled()) { logger.debug("Public object - authentication not attempted"); } publishEvent(new PublicInvocationEvent(object)); return null; // no further work post-invocation } if (logger.isDebugEnabled()) { logger.debug("Secure object: " + object + "; ConfigAttributes: " + attr); } if (SecurityContextHolder.getContext().getAuthentication() == null) { credentialsNotFound(messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attr); } Authentication authenticated = authenticateIfRequired(); // Attempt authorization try { this.accessDecisionManager.decide(authenticated, object, attr); } catch (AccessDeniedException accessDeniedException) { AuthorizationFailureEvent event = new AuthorizationFailureEvent(object, attr, authenticated, accessDeniedException); publishEvent(event); throw accessDeniedException; } if (logger.isDebugEnabled()) { logger.debug("Authorization successful"); } AuthorizedEvent event = new AuthorizedEvent(object, attr, authenticated); publishEvent(event); // Attempt to run as a different user Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attr); if (runAs == null) { if (logger.isDebugEnabled()) { logger.debug("RunAsManager did not change Authentication object"); } // no further work post-invocation return new InterceptorStatusToken(authenticated, false, attr, object); } else { if (logger.isDebugEnabled()) { logger.debug("Switching to RunAs Authentication: " + runAs); } SecurityContextHolder.getContext().setAuthentication(runAs); // revert to token.Authenticated post-invocation return new InterceptorStatusToken(authenticated, true, attr, object); } } /** * Checks the current authentication token and passes it to the AuthenticationManager if * {@link org.springframework.security.Authentication#isAuthenticated()} returns false or the property * <tt>alwaysReauthenticate</tt> has been set to true. * * @return an authenticated <tt>Authentication</tt> object. */ private Authentication authenticateIfRequired() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication.isAuthenticated() && !alwaysReauthenticate) { if (logger.isDebugEnabled()) { logger.debug("Previously Authenticated: " + authentication); } return authentication; }
ConfigAttributeDefinition attr = this.obtainObjectDefinitionSource().getAttributes(object);
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
(User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
this.accessDecisionManager.decide(authenticated, object, attr);
public void decide(Authentication authentication, Object object, ConfigAttributeDefinition config) throws AccessDeniedException { Iterator iter = this.getDecisionVoters().iterator(); int deny = 0; while (iter.hasNext()) { AccessDecisionVoter voter = (AccessDecisionVoter) iter.next(); int result = voter.vote(authentication, object, config); switch (result) { case AccessDecisionVoter.ACCESS_GRANTED: return; case AccessDecisionVoter.ACCESS_DENIED: deny++; break; default: break; } } if (deny > 0) { throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied")); } // To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions(); }
//到这步时,所有的投票者都弃权了// To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions();
public class RoleVoter implements AccessDecisionVoter { //~ Instance fields ================================================================================================ private String rolePrefix = "ROLE_"; //~ Methods ======================================================================================================== public String getRolePrefix() { return rolePrefix; } /** * Allows the default role prefix of <code>ROLE_</code> to be overridden. * May be set to an empty value, although this is usually not desirable. * * @param rolePrefix the new prefix */ public void setRolePrefix(String rolePrefix) { this.rolePrefix = rolePrefix; } public boolean supports(ConfigAttribute attribute) { if ((attribute.getAttribute() != null) && attribute.getAttribute().startsWith(getRolePrefix())) { return true; } else { return false; } } /** * This implementation supports any type of class, because it does not query * the presented secure object. * * @param clazz the secure object * * @return always <code>true</code> */ public boolean supports(Class clazz) { return true; } public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) { int result = ACCESS_ABSTAIN; Iterator iter = config.getConfigAttributes().iterator(); GrantedAuthority[] authorities = extractAuthorities(authentication); while (iter.hasNext()) { ConfigAttribute attribute = (ConfigAttribute) iter.next(); if (this.supports(attribute)) { result = ACCESS_DENIED; // Attempt to find a matching granted authority for (int i = 0; i < authorities.length; i++) { if (attribute.getAttribute().equals(authorities[i].getAuthority())) { return ACCESS_GRANTED; } } } } return result; } GrantedAuthority[] extractAuthorities(Authentication authentication) { return authentication.getAuthorities(); }}
if (attribute.getAttribute().equals(authorities[i].getAuthority())) { return ACCESS_GRANTED; }
public class AuthenticatedVoter implements AccessDecisionVoter { //~ Static fields/initializers ===================================================================================== public static final String IS_AUTHENTICATED_FULLY = "IS_AUTHENTICATED_FULLY"; public static final String IS_AUTHENTICATED_REMEMBERED = "IS_AUTHENTICATED_REMEMBERED"; public static final String IS_AUTHENTICATED_ANONYMOUSLY = "IS_AUTHENTICATED_ANONYMOUSLY"; //~ Instance fields ================================================================================================ private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl(); //~ Methods ======================================================================================================== private boolean isFullyAuthenticated(Authentication authentication) { return (!authenticationTrustResolver.isAnonymous(authentication) && !authenticationTrustResolver.isRememberMe(authentication)); } public void setAuthenticationTrustResolver(AuthenticationTrustResolver authenticationTrustResolver) { Assert.notNull(authenticationTrustResolver, "AuthenticationTrustResolver cannot be set to null"); this.authenticationTrustResolver = authenticationTrustResolver; } public boolean supports(ConfigAttribute attribute) { if ((attribute.getAttribute() != null) && (IS_AUTHENTICATED_FULLY.equals(attribute.getAttribute()) || IS_AUTHENTICATED_REMEMBERED.equals(attribute.getAttribute()) || IS_AUTHENTICATED_ANONYMOUSLY.equals(attribute.getAttribute()))) { return true; } else { return false; } } /** * This implementation supports any type of class, because it does not query the presented secure object. * * @param clazz the secure object * * @return always <code>true</code> */ public boolean supports(Class clazz) { return true; } public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) { int result = ACCESS_ABSTAIN; Iterator iter = config.getConfigAttributes().iterator(); while (iter.hasNext()) { ConfigAttribute attribute = (ConfigAttribute) iter.next(); if (this.supports(attribute)) { result = ACCESS_DENIED; if (IS_AUTHENTICATED_FULLY.equals(attribute.getAttribute())) { if (isFullyAuthenticated(authentication)) { return ACCESS_GRANTED; } } if (IS_AUTHENTICATED_REMEMBERED.equals(attribute.getAttribute())) { if (authenticationTrustResolver.isRememberMe(authentication) || isFullyAuthenticated(authentication)) { return ACCESS_GRANTED; } } if (IS_AUTHENTICATED_ANONYMOUSLY.equals(attribute.getAttribute())) { if (authenticationTrustResolver.isAnonymous(authentication) || isFullyAuthenticated(authentication) || authenticationTrustResolver.isRememberMe(authentication)) { return ACCESS_GRANTED; } } } } return result; }}