- 浏览: 458596 次
- 性别:
- 来自: Runner
文章分类
- 全部博客 (97)
- commons (3)
- EJB3 (5)
- liferay (1)
- 设计模式 (1)
- JBoss (1)
- 常用 (6)
- guice (4)
- JAX-WS (1)
- 持久层处理 (3)
- 单元测试 (3)
- jmesa (6)
- mongodb (3)
- View显示 (6)
- lucene (1)
- struts2 (2)
- spring security (2)
- spring (4)
- hibernate (1)
- mysql (1)
- ruby (21)
- python (2)
- ubuntu (1)
- ibatis (2)
- javascript (3)
- 数据库 (1)
- swing (1)
- java基础 (2)
- dojo (1)
- android (9)
最新评论
-
BigHanson:
写得非常棒,醍醐灌顶 可以转发吗
spring security验证流程 -
背后的光:
spring security验证流程 -
hanlongjie:
commons-lang(time应用) -
litianpeng:
虽然是2009年写的帖子 不过还是要请问一下先生 我按照你的 ...
Ajax Fancy Capcha -
蔡小鱼:
ibatis总结
工作需要,又弄起了权限的管理。虽然很早以前都了解过基于容器的权限实现方式,但是一直都觉得那东西太简陋了。后来使用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最重要的入口点:
上面的“accessDecisionManager”就是切入点,首先需要说明的是,在验证用户是否能通过验证时,spring security提供了三种策略,分别对应那个策略类:
UnanimousBased.java 只要有一个Voter不能完全通过权限要求,就禁止访问。
AffirmativeBased.java只要有一个Voter可以通过权限要求,就可以访问。
ConsensusBased.java只要通过的Voter比禁止的Voter数目多就可以访问了。
在此说一点,ConsensusBased这个类有点特别,如果通过的票数与禁止的票数相同怎么办?
这个类有个allowIfEqualGrantedDeniedDecisions属性,默认为true,关键代码:
上面的代码表示如果allowIfEqualGrantedDeniedDecisions为true而且通过的票数不为0就授权访问。
在提供好验证策略以后,继续关注其是怎么进行验证的。在上面的XML文件中,accessDescisionManager有个allowIfAllAbstainDecisions属性,这个属性的默认值为false,作用见注释处。现在主要关注的是其decisionVoters属性,中文的理解就是投票,RoleVoter.java 主要作用就是完成角色的投票验证,需要注意的是,它验证的角色,名称必须以ROLE_开头,当然这也可以通过配置文件改变,如:
至于RoleVoter是如何完成验证的呆会再说,先回顾一下com.javaeye.sample.security.interceptor.SecureResourceFilterInvocationDefinitionSource:
虽然不重要,但是还是有必要引用一下:
处于继承树顶端的AbstractSecurityInterceptor有三个实现类:
FilterSecurityInterceptor,负责处理FilterInvocation,实现对URL资源的拦截。
MethodSecurityInterceptor,负责处理MethodInvocation,实现对方法调用的拦截。
AspectJSecurityInterceptor,负责处理JoinPoint,主要也是用于对方法调用的拦截。
为了限制用户访问被保护资源,Spring Security提供了一套元数据,用于定义被保护资源的访问权限,这套元数据主要体现为ConfigAttribute和ConfigAttributeDefinition。每个ConfigAttribute中只包含一个字符串,而一个ConfigAttributeDefinition中可以包含多个ConfigAttribute。对于系统来说,每个被保护资源都将对应一个ConfigAttributeDefinition,这个ConfigAttributeDefinition中包含的多个ConfigAttribute就是访问该资源所需的权限。
实际应用中,ConfigAttributeDefinition会保存在ObjectDefinitionSource中,这是一个主要接口,FilterSecurityInterceptor所需的DefaultFilterInvocationDefinitionSource和MethodSecurityInterceptor所需的MethodDefinitionAttributes都实现了这个接口。ObjectDefinitionSource可以看做是Spring Security中权限配置的源头,框架内部所有的验证组件都是从ObjectDefintionSource中获得数据,来对被保护资源进行权限控制的。
为了从xml中将用户配置的访问权限转换成ObjectDefinitionSource类型的对象,Spring Security专门扩展了Spring中提供的PropertyEditor实现了ConfigAttributeEditor,它可以把以逗号分隔的一系列字符串转换成包含多个ConfigAttribute的ConfigAttributeDefintion对象。
"ROLE_ADMIN,ROLE_USER"
↓
ConfigAttributeDefinition
ConfigAttribute["ROLE_ADMIN"]
ConfigAttribute["ROLE_USER"]
对于FilterSecurityInterceptor来说,最终生成的就是一个包含了url pattern和ConfigAttributeConfiguration的ObjectDefinitionSource。
<intercept-url pattern="/admin.jsp" access="ROLE_ADMIN,ROLE_USER" />
↓
ConfigAttributeDefinition
"/admin.jsp" → ConfigAttribute["ROLE_ADMIN"]
ConfigAttribute["ROLE_USER"]
换而言之,无论我们将权限配置的原始数据保存在什么地方,只要最终可以将其转换为ObjectDefintionSource就可以提供给验证组件进行调用,实现权限控制。
当时一直不明白这个getAttributes到底拿来做什么的。下面一步步进行追终,通过配置文件可知,这个类首先会到org.springframework.security.intercept.web.FilterSecurityInterceptor这个类中,这个类有个主要的方法:
在此有两点需要说明,首先是observeOncePerRequest属性,一般情况下保持其默认的true,文档上有说:
其次我们需要关注的重点是FilterSecurityInterceptor的超类AbstractSecurityInterceptor的beforeInvocation方法,下面贴出这个类中我们最需要关注的代码:
上面的代码首先关注其中的一行代码:
不错,这行代码就是SecureResourceFilterInvocationDefinitionSource存在的主要目的,它主要提供URL与ROLE这两个东西,至于需要验证的用户来源,上面有句代码:
众所周知,用户登陆成功后,可以通过:
上面的代码可以获取当前登陆的用户的基本信息,所以验证时需要它也很自然的。既然所需要的东西都具备了,下面就讲讲该怎么验证的问题了。在上面的AbstractSecurityInterceptor内,有句代码:
上面这个accessDecisionManager就是最开始讲的那个accessDecisionManager,终于回到原来的问题上了,由于上面的配置文件中使用了AffirmativeBased投票策略,下面就直接进入此类的decide方法:
这个方法主要有三个作用,第一作用就是完成投票,第二就是验证,从上面的switch与if语句可以看出,只要有一个投票者赞成,就直接返回,验证通过。如果没有一个投票者赞成(弃权)而且有人反对,deny++,到if(deny>0)时扔出异常,最后禁止访问。最后一句
这就到了allowIfAllAbstainDecisions属性起作用的时候了。下面就来讲讲AffirmativeBased中用到的投票类RoleVoter,这个类主要工作就是完成投票工作,然后将结果反馈给AffirmativeBased,下面列出RoleVoter的代码:
这个类中最重要的代码就是这句:
这句代码的意思就是把SecureResourceFilterInvocationDefinitionSource传入的角色名称与SecurityContextHolder.getContext().getAuthentication()传入的用户所拥有的角色的角色名称相比较,如果相等则通过验证。
在配置文件中还用到了一个投票类AuthenticatedVoter,这个类与RoleVoter属于同级,RoleVoter用来验证角色,那AutherticatedVoter又是用来干什么的呢?
这个类完整代码:
作用见下面引用:
AuthenticatedVoter用于判断ConfigAttribute上是否拥有IS_AUTHENTICATED_FULLY,IS_AUTHENTICATED_REMEMBERED或IS_AUTHENTICATED_ANONYMOUSLY之类的配置。
如果配置为IS_AUTHENTICATED_FULLY,那么只有AuthenticationTrustResolver的isAnonymous()和isRememberMe()都返回false时才能通过验证。
如果配置为IS_AUTHENTICATED_REMEMBERED,那么会在AuthenticationTrustResolver的isAnonymous()返回false时通过验证。
如果配置为IS_AUTHENTICATED_ANONYMOUSLY,就可以在AuthenticationTrustResolver的isAnonymous()和isRememberMe()两个方法返回任意值时都可以通过验证。
其中上面引用中的ConfigAttribute就是指SecureResourceFilterInvocationDefinitionSource的getAttributes()方法中ConfigAttributeDefinition中的ConfigAttribute,downpour的文章只是传入了ROLE_USER,ROLE_ADMIN等内容,要想让AuthenticatedVoter有用武之地,可以传入
IS_AUTHENTICATED_FULLY、IS_AUTHENTICATED_REMEMBERED、IS_AUTHENTICATED_ANONYMOUSLY中的值。
需要注意的是AffirmativeBased中遍历的投票者是要分先后的,也就是说RoleVoter在AuthenticatedVoter前面的话,会先进行RoleVoter验证,如果RoleVoter投票未通过,再进行了AuthenticatedVoter投票。
这样spring security的验证流程就基本清楚了,当然这篇文章也还是有些地方讲得不完善,以事有时间再来修改。
首先这篇文章是基于downpour那篇文章的,其地址为:
http://www.iteye.com/topic/319965
最先着手就是配置文件,这也是整个spring security最重要的入口点:
............ <!-- 处理国际化信息 --> <beans:bean id="authenticationManager" class="org.springframework.security.providers.ProviderManager"> <beans:property name="messageSource" ref="messageSource" /> </beans:bean> <beans:bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <beans:property name="basename" value="classpath:org/springframework/security/messages_zh_CN" /> </beans:bean> <authentication-provider user-service-ref="securityManager"> <password-encoder hash="md5" /> </authentication-provider> <!-- AffirmativeBased表示只要有一个Voter通过权限要求,就可以访问 --> <beans:bean id="accessDecisionManager" class="org.springframework.security.vote.AffirmativeBased"> <!-- 是否允许所有的投票者弃权,如果为false,表示如果所有的投票者弃权,就禁止访问 --> <beans:property name="allowIfAllAbstainDecisions" value="false" /> <beans:property name="decisionVoters"> <beans:list> <!-- RoleVoter默认角色名称都要以ROLE_开头,否则不会被计入权限控制,如果要修改前缀,可以通过对rolePrefix属性进行修改 --> <beans:bean class="org.springframework.security.vote.RoleVoter" /> <beans:bean class="org.springframework.security.vote.AuthenticatedVoter" /> </beans:list> </beans:property> </beans:bean> <beans:bean id="resourceSecurityInterceptor" class="org.springframework.security.intercept.web.FilterSecurityInterceptor"> <beans:property name="authenticationManager" 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" class="com.javaeye.sample.security.interceptor.SecureResourceFilterInvocationDefinitionSource" /> ....
上面的“accessDecisionManager”就是切入点,首先需要说明的是,在验证用户是否能通过验证时,spring security提供了三种策略,分别对应那个策略类:
UnanimousBased.java 只要有一个Voter不能完全通过权限要求,就禁止访问。
AffirmativeBased.java只要有一个Voter可以通过权限要求,就可以访问。
ConsensusBased.java只要通过的Voter比禁止的Voter数目多就可以访问了。
在此说一点,ConsensusBased这个类有点特别,如果通过的票数与禁止的票数相同怎么办?
这个类有个allowIfEqualGrantedDeniedDecisions属性,默认为true,关键代码:
if ((grant == deny) && (grant != 0)) { if (this.allowIfEqualGrantedDeniedDecisions) { return; } else { throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied")); } }
上面的代码表示如果allowIfEqualGrantedDeniedDecisions为true而且通过的票数不为0就授权访问。
在提供好验证策略以后,继续关注其是怎么进行验证的。在上面的XML文件中,accessDescisionManager有个allowIfAllAbstainDecisions属性,这个属性的默认值为false,作用见注释处。现在主要关注的是其decisionVoters属性,中文的理解就是投票,RoleVoter.java 主要作用就是完成角色的投票验证,需要注意的是,它验证的角色,名称必须以ROLE_开头,当然这也可以通过配置文件改变,如:
<bean id="roleVoter" class="org.springframework.security.vote.RoleVoter"> <property name="rolePrefix" value="AUTH_"/> </bean>
至于RoleVoter是如何完成验证的呆会再说,先回顾一下com.javaeye.sample.security.interceptor.SecureResourceFilterInvocationDefinitionSource:
..... 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; } ....
虽然不重要,但是还是有必要引用一下:
引用
处于继承树顶端的AbstractSecurityInterceptor有三个实现类:
FilterSecurityInterceptor,负责处理FilterInvocation,实现对URL资源的拦截。
MethodSecurityInterceptor,负责处理MethodInvocation,实现对方法调用的拦截。
AspectJSecurityInterceptor,负责处理JoinPoint,主要也是用于对方法调用的拦截。
为了限制用户访问被保护资源,Spring Security提供了一套元数据,用于定义被保护资源的访问权限,这套元数据主要体现为ConfigAttribute和ConfigAttributeDefinition。每个ConfigAttribute中只包含一个字符串,而一个ConfigAttributeDefinition中可以包含多个ConfigAttribute。对于系统来说,每个被保护资源都将对应一个ConfigAttributeDefinition,这个ConfigAttributeDefinition中包含的多个ConfigAttribute就是访问该资源所需的权限。
实际应用中,ConfigAttributeDefinition会保存在ObjectDefinitionSource中,这是一个主要接口,FilterSecurityInterceptor所需的DefaultFilterInvocationDefinitionSource和MethodSecurityInterceptor所需的MethodDefinitionAttributes都实现了这个接口。ObjectDefinitionSource可以看做是Spring Security中权限配置的源头,框架内部所有的验证组件都是从ObjectDefintionSource中获得数据,来对被保护资源进行权限控制的。
为了从xml中将用户配置的访问权限转换成ObjectDefinitionSource类型的对象,Spring Security专门扩展了Spring中提供的PropertyEditor实现了ConfigAttributeEditor,它可以把以逗号分隔的一系列字符串转换成包含多个ConfigAttribute的ConfigAttributeDefintion对象。
"ROLE_ADMIN,ROLE_USER"
↓
ConfigAttributeDefinition
ConfigAttribute["ROLE_ADMIN"]
ConfigAttribute["ROLE_USER"]
对于FilterSecurityInterceptor来说,最终生成的就是一个包含了url pattern和ConfigAttributeConfiguration的ObjectDefinitionSource。
<intercept-url pattern="/admin.jsp" access="ROLE_ADMIN,ROLE_USER" />
↓
ConfigAttributeDefinition
"/admin.jsp" → ConfigAttribute["ROLE_ADMIN"]
ConfigAttribute["ROLE_USER"]
换而言之,无论我们将权限配置的原始数据保存在什么地方,只要最终可以将其转换为ObjectDefintionSource就可以提供给验证组件进行调用,实现权限控制。
当时一直不明白这个getAttributes到底拿来做什么的。下面一步步进行追终,通过配置文件可知,这个类首先会到org.springframework.security.intercept.web.FilterSecurityInterceptor这个类中,这个类有个主要的方法:
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); } } }
在此有两点需要说明,首先是observeOncePerRequest属性,一般情况下保持其默认的true,文档上有说:
/*** 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. */
其次我们需要关注的重点是FilterSecurityInterceptor的超类AbstractSecurityInterceptor的beforeInvocation方法,下面贴出这个类中我们最需要关注的代码:
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);
不错,这行代码就是SecureResourceFilterInvocationDefinitionSource存在的主要目的,它主要提供URL与ROLE这两个东西,至于需要验证的用户来源,上面有句代码:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
众所周知,用户登陆成功后,可以通过:
(User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
上面的代码可以获取当前登陆的用户的基本信息,所以验证时需要它也很自然的。既然所需要的东西都具备了,下面就讲讲该怎么验证的问题了。在上面的AbstractSecurityInterceptor内,有句代码:
this.accessDecisionManager.decide(authenticated, object, attr);
上面这个accessDecisionManager就是最开始讲的那个accessDecisionManager,终于回到原来的问题上了,由于上面的配置文件中使用了AffirmativeBased投票策略,下面就直接进入此类的decide方法:
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(); }
这个方法主要有三个作用,第一作用就是完成投票,第二就是验证,从上面的switch与if语句可以看出,只要有一个投票者赞成,就直接返回,验证通过。如果没有一个投票者赞成(弃权)而且有人反对,deny++,到if(deny>0)时扔出异常,最后禁止访问。最后一句
//到这步时,所有的投票者都弃权了 // To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions();
这就到了allowIfAllAbstainDecisions属性起作用的时候了。下面就来讲讲AffirmativeBased中用到的投票类RoleVoter,这个类主要工作就是完成投票工作,然后将结果反馈给AffirmativeBased,下面列出RoleVoter的代码:
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; }
这句代码的意思就是把SecureResourceFilterInvocationDefinitionSource传入的角色名称与SecurityContextHolder.getContext().getAuthentication()传入的用户所拥有的角色的角色名称相比较,如果相等则通过验证。
在配置文件中还用到了一个投票类AuthenticatedVoter,这个类与RoleVoter属于同级,RoleVoter用来验证角色,那AutherticatedVoter又是用来干什么的呢?
这个类完整代码:
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; } }
作用见下面引用:
引用
AuthenticatedVoter用于判断ConfigAttribute上是否拥有IS_AUTHENTICATED_FULLY,IS_AUTHENTICATED_REMEMBERED或IS_AUTHENTICATED_ANONYMOUSLY之类的配置。
如果配置为IS_AUTHENTICATED_FULLY,那么只有AuthenticationTrustResolver的isAnonymous()和isRememberMe()都返回false时才能通过验证。
如果配置为IS_AUTHENTICATED_REMEMBERED,那么会在AuthenticationTrustResolver的isAnonymous()返回false时通过验证。
如果配置为IS_AUTHENTICATED_ANONYMOUSLY,就可以在AuthenticationTrustResolver的isAnonymous()和isRememberMe()两个方法返回任意值时都可以通过验证。
其中上面引用中的ConfigAttribute就是指SecureResourceFilterInvocationDefinitionSource的getAttributes()方法中ConfigAttributeDefinition中的ConfigAttribute,downpour的文章只是传入了ROLE_USER,ROLE_ADMIN等内容,要想让AuthenticatedVoter有用武之地,可以传入
IS_AUTHENTICATED_FULLY、IS_AUTHENTICATED_REMEMBERED、IS_AUTHENTICATED_ANONYMOUSLY中的值。
需要注意的是AffirmativeBased中遍历的投票者是要分先后的,也就是说RoleVoter在AuthenticatedVoter前面的话,会先进行RoleVoter验证,如果RoleVoter投票未通过,再进行了AuthenticatedVoter投票。
这样spring security的验证流程就基本清楚了,当然这篇文章也还是有些地方讲得不完善,以事有时间再来修改。
评论
7 楼
BigHanson
2016-09-09
写得非常棒,醍醐灌顶 可以转发吗
6 楼
背后的光
2016-03-05
5 楼
leon709
2011-12-08
深入了,强大
4 楼
gccr
2011-02-18
花了好几天整SpringSecurity.今天才算是给搭起来.
既然楼主出了这样的文件,我就不用再去看源码了.多谢楼主了.
明天好好研究一下楼主的文章.
既然楼主出了这样的文件,我就不用再去看源码了.多谢楼主了.
明天好好研究一下楼主的文章.
3 楼
faithfighting
2010-09-30
springsecurity的文章很少,有这么好的学习资料,没人顶起真的可惜。我这段时间正在学这个框架,感觉学起来还真的是麻烦~
2 楼
fansofjava
2010-06-20
说来惭愧,做了一段时间后,本想研究一下acl的,但发现精力跟不上了,对java这东西是没什么兴趣了,也就没怎么去看了。
1 楼
shuiguozheng
2010-06-20
这么好的文章,怎么没人顶了! 和楼主一样,我也是做权限管理的,专做会员管理,希望能多多交流。之前权限都是通过公司封装的一个工具实现的,源码也没有看到,呵呵,现在对ss3开始痴迷了, 我qq是279737094 欢迎加我!
相关推荐
Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架,它是安全领域中Spring生态系统的一部分。Spring Security旨在为Java应用程序提供一个全面的安全解决方案,尤其适用于企业级应用场景。它主要...
下面我们将深入探讨Spring Security的验证流程以及如何自定义验证方法。 首先,Spring Security的验证流程主要包括以下几个步骤: 1. 用户尝试登录,输入用户名和密码。 2. Spring Security将这些信息封装成一个...
Spring Security 是一个强大的和高度可定制的身份验证和访问控制框架,用于Java应用程序。它提供了全面的安全解决方案,包括用户认证、权限授权、会话管理、CSRF防护以及基于HTTP的访问控制。在这个例子中,我们将...
- 默认情况下,Spring Security 开启了跨站请求伪造(CSRF)防护,通过生成并验证CSRF令牌,防止恶意第三方发起未经授权的操作。 6. **表达式式语言**: - Spring Security 使用一种强大的表达式语言(EL),如`...
Spring Security是一个功能强大、高度定制的安全框架,它专门用于为基于Spring的应用程序提供安全性解决方案。Spring Security架构的设计初衷是为了解决认证和授权的需求,确保应用程序的安全性。它提供了全面的安全...
可能是使用Draw.io绘制的SpringSecurity架构图或流程图,帮助可视化理解SpringSecurity的工作原理。 总之,SpringSecurity为开发者提供了强大的安全工具,通过灵活的配置和丰富的扩展性,能够满足各种复杂的Web...
在"springsecurity学习笔记"中,你可能会涉及以下主题: - Spring Security的基本配置,包括web安全配置和全局安全配置。 - 如何自定义认证和授权流程,比如实现自定义的AuthenticationProvider和...
在微服务架构中,Spring Security 可以帮助我们保护每个微服务的入口点,确保只有经过验证的用户才能访问受保护的资源。 整合Spring Security到Spring Cloud Gateway的过程通常包括以下步骤: 1. **配置Redis**:...
- **直接设置 SecurityContextHolder 的内容**:允许在特定情况下直接设置用户的 Authentication 信息,从而绕过常规的身份验证流程。 **5.4 在 web 应用中验证** - **ExceptionTranslationFilter**:用于处理身份...
首先,让我们了解Spring Security的基本工作流程。Spring Security通过过滤器链处理HTTP请求,其中最重要的过滤器是`UsernamePasswordAuthenticationFilter`,它负责处理登录尝试。当用户尝试登录时,这个过滤器会...
在"SpringBoot+SpringSecurity"的组合中,SpringSecurity负责处理用户身份验证和权限管理,确保只有经过授权的用户才能访问特定的资源和服务。这在WebSocket应用中尤为重要,因为WebSocket连接一旦建立,就可能长期...
Spring Security 是一个强大的安全框架,主要用于Java Web应用的安全管理,包括认证、授权和访问控制等。在Spring Boot中,Spring Security 提供了简洁的API和自动化配置,使得开发者能够快速集成安全功能。在这个名...
Spring Security OAuth 是一个用于保护RESTful Web服务的框架,它为OAuth 1.0a和OAuth 2.0协议提供了全面的支持。在这个源码中,我们可能会看到如何将Spring Security与OAuth结合,以构建安全的Web应用程序和服务。...
浅析Spring Security登录验证流程 Spring Security登录验证流程是Spring Security框架中最核心的部分之一,本文将详细介绍Spring Security登录验证流程的源码解析,通过源码讲解登录验证流程,具有很高的参考价值。...
本教程将详细讲解如何在Spring Boot 3.x版本中结合Spring Security的最新版实现JWT(JSON Web Token)登录验证。 首先,让我们了解JWT。JWT是一种轻量级的身份认证和授权机制,它以JSON对象的形式在客户端和服务器...
最后,课程可能还会涵盖Spring Security与Spring Boot的集成,以及如何与JWT(JSON Web Tokens)结合,以实现轻量级的令牌验证。 总而言之,通过学习Spring Security OAuth2.0,开发者能够掌握一套完整的认证授权...
SpringSecurity是Java领域中一款强大的安全框架,专为Spring应用设计,提供了全面的安全管理解决方案,包括身份验证、授权、会话管理以及CSRF防护等。本教程配套源码旨在帮助开发者深入理解SpringSecurity的工作原理...
### 初识 Spring Security #### 一、Spring Security 概述 **Spring Security**是一种广泛应用于Java企业级项目中的安全框架,它基于Spring AOP(面向切面编程)和Servlet过滤器来提供全面的安全解决方案。该框架...
而SpringSecurity,则是用于实现企业级安全控制的框架,包括身份验证、授权、会话管理等。 在“SpringMVC 集成 SpringSecurity”的实践中,我们需要理解以下几个关键知识点: 1. **SpringSecurity架构**:Spring...
在Spring Security 3中,你可以看到如何配置不同的认证提供者,以及如何自定义认证流程。 2. **授权(Authorization)**:Spring Security 提供细粒度的访问控制,如基于角色的访问控制(RBAC)。在demos中,你可以...