- 1.springsecurity总体结构
- 2.认证流程
- 3.授权流程
安全访问控制是springsecurity解决的核心问题,而安全访问控制功能其实就是对所有进入系统的请求进行拦截校验每个请求是否能够访问它索期望的资源。其底层的实现就是通过filter拦截器或Aop面向切面编程等技术实现的,所以需从filter入手,逐步深入springsecurity原理。
当初始化Spring Security时,会创建一个名为SpringSecurityFilterChain 的Servlet过滤器,类型为
org.springframework.security.web.FilterChainProxy,它实现了javax.servlet.Filter,因此外部的请求会经过此类,下图是Spring Security过虑器链结构图:
FilterChainProxy是一个代理类,其中包含一系列的过滤器,这些filter过滤器统一被spring管理,他们各有各的职责,但是他们不直接处理认证和授权,而是把任务交给认证管理器(AuthenticationManager)和授权管理器(AccessDecisionManager)进行处理。
过滤器链中主要的几个过滤器及其作用:
SecurityContextPersistenceFilter 这个Filter是整个拦截过程的入口和出口(也就是第一个和最后一个拦截器),会在请求开始时从配置好的 SecurityContextRepository 中获取 SecurityContext,然后把它设置给SecurityContextHolder。在请求完成后将 SecurityContextHolder 持有的 SecurityContext 再保存到配置好的 SecurityContextRepository,同时清除 securityContextHolder 所持有的 SecurityContext;
UsernamePasswordAuthenticationFilter 用于处理来自表单提交的认证。该表单必须提供对应的用户名和密码,其内部还有登录成功或失败后进行处理的 AuthenticationSuccessHandler 和AuthenticationFailureHandler,这些都可以根据需求做相关改变;FilterSecurityInterceptor 是用于保护web资源的,使用AccessDecisionManager对当前用户进行授权访问,前面已经详细介绍过了;ExceptionTranslationFilter 能够捕获来自 FilterChain 所有的异常,并进行处理。但是它只会处理两类异常:AuthenticationException 和 AccessDeniedException,其它的异常它会继续抛出。
用户请求进来之后,用户提交用户名、密码被SecurityFilterChain中的UsernamePasswordAuthenticationFilter 过滤器获取到,封装为请求Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。
然后过滤器将Authentication提交至认证管理器(AuthenticationManager)进行认证,AuthenticationManager实际不干活,真正干活的是DaoAuthenticationProvider类,该类中的additionalAuthenticationCheckers方法来对Authentication对象中的user信息和从数据库中查询到的用户信息(用户名和密码)进行对比。
认证成功后, AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除) Authentication 实例。
SecurityContextHolder 安全上下文容器将填充了信息的Authentication ,通过SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。可以看出AuthenticationManager接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,它的实现类为ProviderManager。而Spring Security支持多种认证方式,因此ProviderManager维护着一个List 列表,存放多种认证方式,最终实际的认证工作是由AuthenticationProvider完成的。
Authentication是spring security包中的接口,直接继承自Principal类,而Principal是位于java.security包中的。它是表示着一个抽象主体身份,任何主体都有一个名称,因此包含一个getName()方法。以下是它只要的方法:
(1)getAuthorities(),权限信息列表,默认是GrantedAuthority接口的一些实现类,通常是代表权限信息的一系列字符串。
(2)getCredentials(),凭证信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
(3)getDetails(),细节信息,web应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值。
(4)getPrincipal(),身份信息,大部分情况下返回的是UserDetails接口的实现类,UserDetails代表用户的详细信息,那从Authentication中取出来的UserDetails就是当前登录用户信息,它也是框架中的常用接口之一。
Spring Security可以通过http.authorizeRequests() 对web请求进行授权保护。SpringSecurity使用标准Filter建立了对web请求的拦截,最终实现对资源的授权访问。
分析授权流程:
- 拦截请求,已认证用户访问受保护的web资源将被SecurityFilterChain中的FilterSecurityInterceptor 的子类拦截。
- 获取资源访问策略,FilterSecurityInterceptor会从SecuritymetadataSource 的子类DefaultFilterInvocationSecuritymetadataSource 获取要访问当前资源所需要的权限Collection
。SecuritymetadataSource其实就是读取访问策略的抽象,而读取的内容,其实就是我们配置的访问规则, 读取访问策略如:
public interface AccessDecisionManager { void decide(Authentication authentication , Object object, CollectionconfigAttributes ) throws AccessDeniedException, InsufficientAuthenticationException; //略.. }
这里着重说明一下decide的参数:
authentication:要访问资源的访问者的身份
object:要访问的受保护资源,web请求对应FilterInvocation
configAttributes:是受保护资源的访问策略,通过SecuritymetadataSource获取。
decide接口就是用来鉴定当前用户是否有访问对应受保护资源的权限。
AccessDecisionManager采用投票的方式来确定是否能够访问受保护资源。AccessDecisionManager中包含的一系列AccessDecisionVoter将会被用来对Authentication是否有权访问受保护对象进行投票,AccessDecisionManager根据投票结果,做出最终决策。
AccessDecisionVoter是一个接口,其中定义有三个方法,具体结构如下所示。
public interface AccessDecisionVoter{ int ACCESS_GRANTED = 1; int ACCESS_ABSTAIN = 0; int ACCESS_DENIED = ‐1; boolean supports(ConfigAttribute var1); boolean supports(Class> var1); int vote(Authentication var1, S var2, Collectionvar3); }
vote()方法的返回结果会是AccessDecisionVoter中定义的三个常量之一。ACCESS_GRANTED表示同意,ACCESS_DENIED表示拒绝,ACCESS_ABSTAIN表示弃权。如果一个AccessDecisionVoter不能判定当前Authentication是否拥有访问对应受保护对象的权限,则其vote()方法的返回值应当为弃权ACCESS_ABSTAIN。
Spring Security内置了三个基于投票的AccessDecisionManager实现类如下,它们分别是
Affirmativebased、Consensusbased和Unanimousbased。
Affirmativebased的逻辑是:
(1)只要有AccessDecisionVoter的投票为ACCESS_GRANTED则同意用户进行访问;
(2)如果全部弃权也表示通过;
(3)如果没有一个人投赞成票,但是有人投反对票,则将抛出AccessDeniedException。
Spring security默认使用的是Affirmativebased。
Consensusbased的逻辑是:
(1)如果赞成票多于反对票则表示通过。
(2)反过来,如果反对票多于赞成票则将抛出AccessDeniedException。
(3)如果赞成票与反对票相同且不等于0,并且属性allowIfEqualGrantedDeniedDecisions的值为true,则表示通过,否则将抛出异常AccessDeniedException。参数allowIfEqualGrantedDeniedDecisions的值默认为true。
(4)如果所有的AccessDecisionVoter都弃权了,则将视参数allowIfAllAbstainDecisions的值而定,如果该值为true则表示通过,否则将抛出异常AccessDeniedException。参数allowIfAllAbstainDecisions的值默认为false。Unanimousbased的逻辑与另外两种实现有点不一样,另外两种会一次性把受保护对象的配置属性全部传递给AccessDecisionVoter进行投票,而Unanimousbased会一次只传递一个ConfigAttribute给AccessDecisionVoter进行投票。这也就意味着如果我们的AccessDecisionVoter的逻辑是只要传递进来的ConfigAttribute中有一个能够匹配则投赞成票,但是放到Unanimousbased中其投票结果就不一定是赞成了。
Unanimousbased的逻辑具体来说是这样的:
(1)如果受保护对象配置的某一个ConfigAttribute被任意的AccessDecisionVoter反对了,则将抛出
AccessDeniedException。
(2)如果没有反对票,但是有赞成票,则表示通过。
(3)如果全部弃权了,则将视参数allowIfAllAbstainDecisions的值而定,true则通过,false则抛出
AccessDeniedException。
Spring Security也内置一些投票者实现类如RoleVoter、AuthenticatedVoter和WebexpressionVoter等,待深入研究。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)