前言
- 我们前几节讲解Spring Security的时候,核心原理就是上图所示的过滤器链路。
- 我们核心考察的是类:FilterSecurityInterceptor;FilterSecurityInterceptor用于最后校验我们的请求是否最后能够到达我们的REST API.如果不能的话,会抛出异常,抛出异常的话,会由于其前面的ExceptionTranslationFilter处理。
- 我们这里与权限相关的主要类是:FilterSecurityInterceptor和ExceptionTranslationFilter
-
我们之前讲解的各种过滤器,现在我们主要讲解的是:AnonymousAuthenticationFilter(匿名认证过滤器),他处于我们上面绿色过滤器最后,不管前面有各种过滤器,后面都会到AnonymousAuthenticationFilter(匿名认证过滤器):看代码之后,主要看其里面的逻辑是判断当前SecurityContextHolder里面是否有:Authentication;也就是说:前面过滤器是否成功进行了成功地身份认证。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { if (SecurityContextHolder.getContext().getAuthentication() == null) { SecurityContextHolder.getContext().setAuthentication(this.createAuthentication((HttpServletRequest)req)); if (this.logger.isDebugEnabled()) { this.logger.debug("Populated SecurityContextHolder with anonymous token: '" + SecurityContextHolder.getContext().getAuthentication() + "'"); } } else if (this.logger.isDebugEnabled()) { this.logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '" + SecurityContextHolder.getContext().getAuthentication() + "'"); } chain.doFilter(req, res); }
-
我们之前说过,认证成功之后,我们返回的是一个:Authentication.我们查看:AnonymousAuthenticationFilter的创建:Authentication 我们知道:里面的用户信息:principal是:anonymousUser,如果前面的过滤器没有认证成功,那么此时 SecurityContextHolder.getContext()的认证信息:Authentication就是AnonymousAuthenticationFilter自己创建的。
protected Authentication createAuthentication(HttpServletRequest request) { AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(this.key, this.principal, this.authorities); auth.setDetails(this.authenticationDetailsSource.buildDetails(request)); return auth; } public AnonymousAuthenticationFilter(String key) { this(key, "anonymousUser", AuthorityUtils.createAuthorityList(new String[]{"ROLE_ANONYMOUS"})); } public AnonymousAuthenticationFilter(String key, Object principal, List<GrantedAuthority> authorities) { this.authenticationDetailsSource = new WebAuthenticationDetailsSource(); Assert.hasLength(key, "key cannot be null or empty"); Assert.notNull(principal, "Anonymous authentication principal must be set"); Assert.notNull(authorities, "Anonymous authorities must be set"); this.key = key; this.principal = principal; this.authorities = authorities; }
- 所以通过上面分析我们知道:如果我们认证成功,那么返回的Authentication里面的principal就是我们UserDetailsService里面封装的UserDetails信息,如果没有认证成功,那么Authentication里面的principa就是上面的"anonymousUser"字符串。
- 不管怎样,最后传给我们FilterSecurityInterceptor的Authentication都会存在,然后FilterSecurityInterceptor决定当前包含的Authentication包含的权限是否可以访问你当前请求的url。
内容
1.SpringSecurity授权逻辑图分析
我们现在主要关注:FilterSecurityInterceptor和ExceptionTranslationFilter,首先我们观察下spring security中和授权相关的类接口以及调用关系。
其中核心的类和接口就只有3个:
FilterSecurityInterceptor、AccessDecisionManager、AccessDecisionVoter。
- FilterSecurityInterceptor是我们Spring Security过滤器链路上最后的拦截器,是我们授权的主入口,FilterSecurityInterceptor其实是一个Filter
- AccessDecisionManager(访问决定管理器),其实是一个接口,他有一个抽象的实现:AbstractAccessDecisionManager和3个具体类;他是一个管理者,管理的是什么了?从名字我们可以看到,他管理的是一组:AccessDecisionVoter(授权决定投票者)
- AccessDecisionVoter会综合所有投票者的投票结果,然后给出一个最终结果过还是不过,具体判断过与不过,有3套这样的逻辑,具体的投票逻辑是在AbstractAccessDecisionManager的子类里面:
a.AffirmativeBased:只有有一个voter投通过,那么整体请求就通过。 b.ConsensusBased:比较投通过和不通过的票数, 哪一种意见多就用哪一种。
c.UnanimousBased:不管有多少个voter投通过,只要有一个投不通过。整个请求不通过。默认spring security默认使用第一种:AffirmativeBased - 那么SecurityConfig和SecurityContextHolder是干什么的呢?我们之前说要判断一个请求是否能够正常访问?需要两方面的数据:a.系统配置信息(具体url需要什么样的信息),这些配置信息我们是配置到:
FilterSecurityInterceptor会从我们的安全配置:SecurityConfig里面信息读出来,封装成一组SecurityAttribute这样的一组对象,这组对象里面其实每一个SecurityAttribute对应着一个url所对应的权限。这里面其实就是你系统配置的信息。
Authentication信息封装的基本认证信息。 - Authentication、ConfigAttribute和FilterSecurityInterceptor携带的请求信息一起传给我们的AccessDecisionManager,然后AccessDecisionManager交给 投票者,由投票者来投票是过还是不过。
2.SpringSecurity源码追踪
我们看两个流程:一个是:用户没登录时候,被拒绝访问流程,另一个是用户登录以后,访问通过流程。
2.1 用户没登录时候,被拒绝访问流程
我们访问:http://127.0.0.1:8088/
断点:FilterSecurityInterceptor
//封装请求、响应、过滤器链到FilterInvocation
FilterInvocation fi = new FilterInvocation(request, response, chain);
//判断:此请求是否经过FilterSecurityInterceptor处理,经过的话直接返回到下一个Filter中。
if (fi.getRequest() != null && fi.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filterApplied") != null && this.observeOncePerRequest) {
//处理授权的核心逻辑,就是我们FilterSecurityInterceptor判断是否可以访问RESTAPI,授权主要也在这里,如果授权不通过,则会抛出异常。
InterceptorStatusToken token = super.beforeInvocation(fi);
protected InterceptorStatusToken beforeInvocation(Object object)方法中
//读取我们的SecurityConfig将其封装成一个对象。
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
DefaultFilterInvocationSecurityMetadataSource:
我们可以看到每一个url,所具备的权限。
这个map就是根据我们WebSecurityConfig里面protected void configure(HttpSecurity http) throws Exception()生成的。
//判断SecurityContextHolder.getContext()里面是否有授权信息,因为授权信息我们就算没有认证成功,也会在AnonymousAuthenticationFilter生成,如果确实为空,则有异常了。
if (SecurityContextHolder.getContext().getAuthentication() == null) {
this.credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes);
}
通过decide方法决定是不是让其通过.
未有授权通过就会抛出异常:异常先从:最先调用的AffirmativeBased抛出异常,然后抛出异常到父类,再次到接口,最后抛出异常到: FilterSecurityInterceptor;然后往前抛到FilterSecurityInterceptor前面的异常转换过滤器(ExceptionTranslationFilter);
然后进入: this.handleSpringSecurityException(request, response, chain, (RuntimeException)ase);
把我发出去去认证。
把我发出去去认证,其实就是跳到了我们之前在:WebSecurityConfig配置里面loginPage所配置的。
2.1 用户没登录时候,被拒绝访问流程
之前是用户未登录时候的授权流程,我们现在开始登陆。
登录完成之后,我们再次访问:
http://127.0.0.1:8088/user/1
这个hasRole('ROLE_ADMIN')是怎么来的呢?
我们在配置文件总配置了:ADMIN
然后进入源码跟踪:ExpressionUrlAuthorizationConfigurer的:
public ExpressionUrlAuthorizationConfigurer<H>.ExpressionInterceptUrlRegistry hasRole(String role) {
return this.access(ExpressionUrlAuthorizationConfigurer.hasRole(role));
}
然后进入:ExpressionUrlAuthorizationConfigurer
private static String hasRole(String role) {
Assert.notNull(role, "role cannot be null");
if (role.startsWith("ROLE_")) {
throw new IllegalArgumentException("role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'");
} else {
return "hasRole('ROLE_" + role + "')";
}
}
说明返回的字符串会拼接前缀返回:"hasRole('ROLE_" + role + "')"。
所以我们授权时候也需要给:ROLE_ADMIN
上面我们拿到了系统配置和Authentication信息,现在我们进入投票Voter,结果如下:
最后拿到了我们的接口返回信息:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。