转载

33.SpringSecurity-SpringSecurity Oauth授权源码解读

前言

33.SpringSecurity-SpringSecurity Oauth授权源码解读

  1. 我们前几节讲解Spring Security的时候,核心原理就是上图所示的过滤器链路。
  2. 我们核心考察的是类:FilterSecurityInterceptor;FilterSecurityInterceptor用于最后校验我们的请求是否最后能够到达我们的REST API.如果不能的话,会抛出异常,抛出异常的话,会由于其前面的ExceptionTranslationFilter处理。
  3. 我们这里与权限相关的主要类是:FilterSecurityInterceptor和ExceptionTranslationFilter
  4. 我们之前讲解的各种过滤器,现在我们主要讲解的是: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);
         }
  5. 我们之前说过,认证成功之后,我们返回的是一个: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;
     }
  6. 所以通过上面分析我们知道:如果我们认证成功,那么返回的Authentication里面的principal就是我们UserDetailsService里面封装的UserDetails信息,如果没有认证成功,那么Authentication里面的principa就是上面的"anonymousUser"字符串。
  7. 不管怎样,最后传给我们FilterSecurityInterceptor的Authentication都会存在,然后FilterSecurityInterceptor决定当前包含的Authentication包含的权限是否可以访问你当前请求的url。

内容

1.SpringSecurity授权逻辑图分析

33.SpringSecurity-SpringSecurity Oauth授权源码解读

我们现在主要关注:FilterSecurityInterceptor和ExceptionTranslationFilter,首先我们观察下spring security中和授权相关的类接口以及调用关系。

其中核心的类和接口就只有3个:

FilterSecurityInterceptor、AccessDecisionManager、AccessDecisionVoter。

  1. FilterSecurityInterceptor是我们Spring Security过滤器链路上最后的拦截器,是我们授权的主入口,FilterSecurityInterceptor其实是一个Filter
  2. AccessDecisionManager(访问决定管理器),其实是一个接口,他有一个抽象的实现:AbstractAccessDecisionManager和3个具体类;他是一个管理者,管理的是什么了?从名字我们可以看到,他管理的是一组:AccessDecisionVoter(授权决定投票者)
  3. AccessDecisionVoter会综合所有投票者的投票结果,然后给出一个最终结果过还是不过,具体判断过与不过,有3套这样的逻辑,具体的投票逻辑是在AbstractAccessDecisionManager的子类里面:
    a.AffirmativeBased:只有有一个voter投通过,那么整体请求就通过。 b.ConsensusBased:比较投通过和不通过的票数, 哪一种意见多就用哪一种。
    c.UnanimousBased:不管有多少个voter投通过,只要有一个投不通过。整个请求不通过。默认spring security默认使用第一种:AffirmativeBased
  4. 那么SecurityConfig和SecurityContextHolder是干什么的呢?我们之前说要判断一个请求是否能够正常访问?需要两方面的数据:a.系统配置信息(具体url需要什么样的信息),这些配置信息我们是配置到:
    33.SpringSecurity-SpringSecurity Oauth授权源码解读
    FilterSecurityInterceptor会从我们的安全配置:SecurityConfig里面信息读出来,封装成一组SecurityAttribute这样的一组对象,这组对象里面其实每一个SecurityAttribute对应着一个url所对应的权限。这里面其实就是你系统配置的信息。
    Authentication信息封装的基本认证信息。
  5. Authentication、ConfigAttribute和FilterSecurityInterceptor携带的请求信息一起传给我们的AccessDecisionManager,然后AccessDecisionManager交给 投票者,由投票者来投票是过还是不过。

2.SpringSecurity源码追踪

我们看两个流程:一个是:用户没登录时候,被拒绝访问流程,另一个是用户登录以后,访问通过流程。

2.1 用户没登录时候,被拒绝访问流程

我们访问: http://127.0.0.1 :8088/

断点:FilterSecurityInterceptor

33.SpringSecurity-SpringSecurity Oauth授权源码解读

//封装请求、响应、过滤器链到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:

33.SpringSecurity-SpringSecurity Oauth授权源码解读

我们可以看到每一个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);
                }

33.SpringSecurity-SpringSecurity Oauth授权源码解读

33.SpringSecurity-SpringSecurity Oauth授权源码解读

通过decide方法决定是不是让其通过.

33.SpringSecurity-SpringSecurity Oauth授权源码解读

33.SpringSecurity-SpringSecurity Oauth授权源码解读

未有授权通过就会抛出异常:异常先从:最先调用的AffirmativeBased抛出异常,然后抛出异常到父类,再次到接口,最后抛出异常到: FilterSecurityInterceptor;然后往前抛到FilterSecurityInterceptor前面的异常转换过滤器(ExceptionTranslationFilter);

33.SpringSecurity-SpringSecurity Oauth授权源码解读

然后进入: this.handleSpringSecurityException(request, response, chain, (RuntimeException)ase);

33.SpringSecurity-SpringSecurity Oauth授权源码解读

把我发出去去认证。

33.SpringSecurity-SpringSecurity Oauth授权源码解读

33.SpringSecurity-SpringSecurity Oauth授权源码解读

把我发出去去认证,其实就是跳到了我们之前在:WebSecurityConfig配置里面loginPage所配置的。

33.SpringSecurity-SpringSecurity Oauth授权源码解读

33.SpringSecurity-SpringSecurity Oauth授权源码解读

33.SpringSecurity-SpringSecurity Oauth授权源码解读

2.1 用户没登录时候,被拒绝访问流程

之前是用户未登录时候的授权流程,我们现在开始登陆。

33.SpringSecurity-SpringSecurity Oauth授权源码解读

登录完成之后,我们再次访问:

http://127.0.0.1:8088/user/1

33.SpringSecurity-SpringSecurity Oauth授权源码解读

这个hasRole('ROLE_ADMIN')是怎么来的呢?

我们在配置文件总配置了:ADMIN

33.SpringSecurity-SpringSecurity Oauth授权源码解读

然后进入源码跟踪: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

33.SpringSecurity-SpringSecurity Oauth授权源码解读

上面我们拿到了系统配置和Authentication信息,现在我们进入投票Voter,结果如下:

33.SpringSecurity-SpringSecurity Oauth授权源码解读

33.SpringSecurity-SpringSecurity Oauth授权源码解读

最后拿到了我们的接口返回信息:

33.SpringSecurity-SpringSecurity Oauth授权源码解读

原文  https://segmentfault.com/a/1190000022100573
正文到此结束
Loading...