SpringSecurity Oauth2 - 08 SpEL权限表达式源码分析及两种权限控制方式原理

文章目录

1. 权限管理介绍

SpringSecurity提供的权限管理功能主要有两种类型:

① 基于过滤器的权限管理(FilterSecurityInterceptor):用来拦截HTTP请求,拦截下来之后,根据HTTP请求地址进行权限校验;

请求首先到大过滤器FilterSecurityInterceptor,在其执行过程中,首先会由前置处理器去判断发起请求的用户是否具备响应的权限,如果具备则继续向下走,到达目标方法后执行完毕。拦截请求的url,这种权限管理方式粒度较粗。

② 基于AOP的权限管理(MethodSecurityInterceptor):用来处理方法级别的权限问题。当需要调用某一个方法时,通过AOP将操作拦截下来,然后判断用户是否具备相关的权限,如果具备,则允许方法调用,否则禁止方法调用。

当目标方法的调用被MethodSecurityInterceptor拦截下来之后,再起invoke方法中首先会由前置处理器去判断当前用户是否具备调用目标方法所需的权限,如果具备则继续执行目标方法。

2. 权限表达式介绍

SpringSecurity 内置的SpEL表达式,可以再请求的URL或者访问的方法上,通过SpEL来配置需要的权限:

在这里插入图片描述

3. 基于url地址的权限控制

@Override
public void configure(HttpSecurity http) throws Exception {
    // http.authorizeRequests()主要是对url进行访问权限控制,通过这个方法来实现url授权操作
    http.authorizeRequests()
            // 写法:url匹配规则.权限表达式
            // permitAll()权限表达式:用户可以在没有身份验证的情况下访问 /api/v1/login,/api/v1/token 地址
            .antMatchers("/api/v1/login", "/api/v1/token").permitAll()
            // hasAuthority()权限表达式:用户必须具备knowledgeEdit权限才可以访问 /api/v1/hello 地址
            .antMatchers("/api/v1/hello").hasAuthority("knowledgeEdit")
            // url匹配规则.权限表达式
            // hasRole()权限表达式:用户必须具备ADMIN角色才可以访问 /api/v1/doc 地址
            .antMatchers("/api/v1/doc").hasRole("ADMIN");
    // 其他请求只要认证后的用户就可以访问
    http.authorizeRequests().anyRequest().authenticated();
    http.formLogin().disable();
    http.httpBasic().disable();
}

4. 基于方法级别的权限控制

@EnableGlobalMethodSecurity 该注解是用来开启权限注解,用法如下:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled=true, jsr250Enabled=true)
public class SecurityConfig extends WebsecurityConfigurerAdapter{}

perPostEnabled: 开启 Spring Security 提供的四个权限注解,@PostAuthorize、@PostFilter、@PreAuthorize 以及@PreFilter,注解会将一个描述权限规则的SpEL表达式作为一个值来接收。

@RestController
@RequestMapping("/api/v2")
public class UserController {
    // 在目标方法执行之前进行权限校验
    // 通过权限表达式来配置访问该方法需要具备的权限
    @PreAuthorize("hasAnyAuthority('superAdmin')")
    @PostMapping(value = "/domain/users", params = { "_method=GET" })
    public List<UserEntity> getUsersByNamesInSameDomain(){
        
    }
}

5. 源码 - 权限表达式

SpringSecurity中通 SecurityExpressionOperations 接口定义了基本的权限表达式,他的实现类如下:

1. 源码 SecurityExpressionOperations
public interface SecurityExpressionOperations1 {
    /**
     * Gets the Authentication used for evaluating the expressions
     *
     * 当用户登录成功后,当前用户信息将保存在 Authentication 对象中,
     * Authentication#getAuthorities() 方法:返回当前对象所具备的权限信息,也就是已经授予当前登录用户的权限。
     */
    Authentication getAuthentication();

    /**
     * Determines if the {@link #getAuthentication()} has a particular authority within {@link Authentication#getAuthorities()}.
     *
     * 判断当前用户具备的权限信息,是否存在指定权限
     */
    boolean hasAuthority(String authority);

    /**
     * Determines if the {@link #getAuthentication()} has any of the specified authorities within {@link Authentication#getAuthorities()}.
     *
     * 判断当前用户具备的权限信息,是否存在指定权限中的任意一个
     */
    boolean hasAnyAuthority(String... authorities);

    /**
     * Determines if the {@link #getAuthentication()} has a particular authority within {@link Authentication#getAuthorities()}.
     *
     * 判断当前用户具备的权限信息,是否存在指定角色
     */
    boolean hasRole(String role);

    /**
     * Determines if the {@link #getAuthentication()} has any of the specified authorities within {@link Authentication#getAuthorities()}.
     *
     * 判断当前用户具备的权限信息,是否存在指定角色中的任意一个
     */
    boolean hasAnyRole(String... roles);

    /**
     * 允许所有的请求调用
     *
     * @return true
     */
    boolean permitAll();

    /**
     * 拒绝所有的请求调用
     *
     * @return false
     */
    boolean denyAll();

    /**
     * Determines if the {@link #getAuthentication()} is anonymous
     *
     * 当前用户是否是一个匿名用户
     */
    boolean isAnonymous();

    /**
     * Determines ifthe {@link #getAuthentication()} is authenticated
     * 
     * 判断用户是否已经认证成功
     */
    boolean isAuthenticated();

    /**
     * Determines if the {@link #getAuthentication()} was authenticated using remember me
     *
     * 当前用户是否通过RememberMe自动登录
     */
    boolean isRememberMe();

    /**
     * Determines if the {@link #getAuthentication()} authenticated without the use of remember me
     *
     * 当前登录用户是否既不是匿名用户又不是通过RememberMe登录的
     */
    boolean isFullyAuthenticated();

    /**
     * Determines if the {@link #getAuthentication()} has permission to access the target given the permission
     *
     * 当前登录用户是否具有指定目标的指定权限
     */
    boolean hasPermission(Object target, Object permission);

    /**
     * Determines if the {@link #getAuthentication()} has permission to access the domain object with a given id, type, and permission.
     *
     *  当前登录用户是否具有指定目标的指定权限
     */
    boolean hasPermission(Object targetId, String targetType, Object permission);
}

hasAuthority,hasAnyAuthority,hasRole,hasAnyRole四个方法主要是将传入的参数和authentication对象中保存的方法进行比对,如果用户具有相应的权限就返回true,否则返回false,permitAll方法总是返回true,而denyAll方法总是返回false,isAnonymous,isAuthenticated,isRememberMe,isFullyAuthenticated这四个方法时根据对authentication对象的分析返回true还是false,最后的hasPermission方法则需要调用PermissionEvaluator中对应的方法进行计算,返回true还是false。

2. 源码 SecurityExpressionRoot
/**
 * 为了创建自定义表达式,我们首先需要实现SecurityExpressionOperations表达式 
 */
public abstract class SecurityExpressionRoot implements SecurityExpressionOperations {

   /*用户登录成功后,保存用户信息的Authentication对象*/
   protected final Authentication authentication;
   private AuthenticationTrustResolver trustResolver;
   private RoleHierarchy roleHierarchy;
   /*用户角色列表*/
   private Set<String> roles;
   /*角色前缀*/
   private String defaultRolePrefix = "ROLE_";

   public final boolean permitAll = true;
   public final boolean denyAll = false;

   private PermissionEvaluator permissionEvaluator;
   public final String read = "read";
   public final String write = "write";
   public final String create = "create";
   public final String delete = "delete";
   public final String admin = "administration";

   public final Authentication getAuthentication() {
      return authentication;
   }

   /**
    * 构造方法
    */
   public SecurityExpressionRoot(Authentication authentication) {
      if (authentication == null) {
         throw new IllegalArgumentException("Authentication object cannot be null");
      }
      this.authentication = authentication;
   }

   /**
    * 判断当前用户具备的权限信息,是否存在指定权限
    * SpEL权限表达式:hasAuthority: @PreAuthorize("hasAuthority('superAdmin')")
    */
   public final boolean hasAuthority(String authority) {
      return hasAnyAuthority(authority);
   }

   /**
    * 判断当前用户具备的权限信息,是否存在指定权限中的任意一个
    * SpEL权限表达式:hasAnyAuthority:@PreAuthorize("hasAnyAuthority('superAdmin','incidentQuery')")
    */
   public final boolean hasAnyAuthority(String... authorities) {
      //
      return hasAnyAuthorityName(null, authorities);
   }

   /**
    * 判断当前用户具备的权限信息,是否存在指定角色
    * SpEL权限表达式:hasRole:@PreAuthorize("hasRole("ROLE_ADMIN")")
    */
   public final boolean hasRole(String role) {
      return hasAnyRole(role);
   }

   /**
    * 判断当前用户具备的权限信息,是否存在指定角色中的任意一个
    * SpEL权限表达式:hasAnyRole:@PreAuthorize("hasAnyRole("ROLE_ADMIN","ROLE_USER")")
    */
   public final boolean hasAnyRole(String... roles) {
      // 与 hasAnyAuthority 不同的是,hasRole 会自动给传入的字符串加上 ROLE_ 前缀
      // 代码里如果写的是 admin,框架会自动加上 ROLE_ 前缀,所以数据库就必须是 ROLE_admin。
      return hasAnyAuthorityName(defaultRolePrefix, roles);
   }

   /**
    * 允许所有的请求调用
    * SpEL权限表达式:hasAnyRole:@PreAuthorize("permitAll()")
    */
   public final boolean permitAll() {
      return true;
   }

   /**
    * 拒绝所有的请求调用
    * SpEL权限表达式:hasAnyRole:@PreAuthorize("denyAll()")
    */
   public final boolean denyAll() {
      return false;
   }

   /**
    * 当前用户是否是一个匿名用户
    * SpEL权限表达式:hasAnyRole:@PreAuthorize("isAnonymous()")
    */
   public final boolean isAnonymous() {
      return trustResolver.isAnonymous(authentication);
   }

   /**
    * 判断用户是否已经认证成功
    * SpEL权限表达式:hasAnyRole:@PreAuthorize("isAuthenticated()")
    */
   public final boolean isAuthenticated() {
      return !isAnonymous();
   }

   /**
    * 当前用户是否通过RememberMe自动登录
    * SpEL权限表达式:hasAnyRole:@PreAuthorize("isRememberMe()")
    */
   public final boolean isRememberMe() {
      return trustResolver.isRememberMe(authentication);
   }

   /**
    *  SpEL权限表达式:hasAnyRole:@PreAuthorize("isFullyAuthenticated()")
    *  前登录用户是否既不是匿名用户又不是通过RememberMe登录的
    */
   public final boolean isFullyAuthenticated() {
      return !trustResolver.isAnonymous(authentication) && !trustResolver.isRememberMe(authentication);
   }

   public Object getPrincipal() {
      return authentication.getPrincipal();
   }

   public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
      this.trustResolver = trustResolver;
   }

   public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
      this.roleHierarchy = roleHierarchy;
   }

   public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) {
      this.permissionEvaluator = permissionEvaluator;
   }

   /**
    * 当前登录用户是否具有指定目标的指定权限
    */
   public boolean hasPermission(Object target, Object permission) {
      return permissionEvaluator.hasPermission(authentication, target, permission);
   }

   /**
    * 当前登录用户是否具有指定目标的指定权限
    */
   public boolean hasPermission(Object targetId, String targetType, Object permission) {
      return permissionEvaluator.hasPermission(authentication, (Serializable) targetId, targetType, permission);
   }

   private Set<String> getAuthoritySet() {
      if (roles == null) {
         roles = new HashSet<>();
         // 获取登录用户具备的权限信息
         Collection<? extends GrantedAuthority> userAuthorities = authentication.getAuthorities();
         if (roleHierarchy != null) {
            userAuthorities = roleHierarchy.getReachableGrantedAuthorities(userAuthorities);
         }
         roles = AuthorityUtils.authorityListToSet(userAuthorities);
      }
      return roles;
   }

   private boolean hasAnyAuthorityName(String prefix, String... roles) {
      Set<String> roleSet = getAuthoritySet();
      for (String role : roles) {
         String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
         if (roleSet.contains(defaultedRole)) {
            return true;
         }
      }
      return false;
   }

   public void setDefaultRolePrefix(String defaultRolePrefix) {
      this.defaultRolePrefix = defaultRolePrefix;
   }

   private static String getRoleWithDefaultPrefix(String defaultRolePrefix, String role) {
      if (role == null) {
         return role;
      }
      if (defaultRolePrefix == null || defaultRolePrefix.length() == 0) {
         return role;
      }
      if (role.startsWith(defaultRolePrefix)) {
         return role;
      }
      return defaultRolePrefix + role;
   }
}
3. 源码 MethodSecurityExpressionOperations
/**
 * Interface which must be implemented if you want to use filtering in method security expressions.
 */
public interface MethodSecurityExpressionOperations extends SecurityExpressionOperations {
   void setFilterObject(Object filterObject);

   Object getFilterObject();

   void setReturnObject(Object returnObject);

   Object getReturnObject();

   Object getThis();
}
4. 源码 MethodSecurityExpressionRoot
class MethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
   private Object filterObject;
   private Object returnObject;
   private Object target;

   MethodSecurityExpressionRoot(Authentication a) {
      super(a);
   }

   public void setFilterObject(Object filterObject) {
      this.filterObject = filterObject;
   }

   public Object getFilterObject() {
      return filterObject;
   }

   public void setReturnObject(Object returnObject) {
      this.returnObject = returnObject;
   }

   public Object getReturnObject() {
      return returnObject;
   }

   void setThis(Object target) {
      this.target = target;
   }

   public Object getThis() {
      return target;
   }
}

6. 资源服务器 server-resource

相关文章:

SpringSecurity Oauth2实战 - 05 /oauth/token请求认证流程源码分析

SpringSecurity Oauth2实战 - 06 拦截器获取用户登录信息并存储到本地线程ThreadLocal

SpringSecurity Oauth2实战 - 07 整合Redis延长页面自动退出登录时间

在前面三篇文章中,我们增加了自定义认证管理器 CustomAuthProvider 获取认证令牌 access_token ,增加了拦截器 UserInfoInterceptor 用来获取用户登录信息并存储搭配本地线程,增加了自定义 CustomTokenExtractor 用来延长页面自动退出登录的时间。

在此基础上我们来继续研究新东西,然后走一遍整个源码流程。

1. 自定义认证管理器 CustomAuthProvider
@Slf4j
@Component
public class CustomAuthProvider implements AuthenticationProvider {

    @Autowired
    private CustomUserDetailService customUserDetailService;

    /**
     * 认证
     * @param authentication 待认证的对象 principal:loginJsonString, credentials:""
     * @return Authentication 认证成功后填充的对象
     * @throws AuthenticationException 异常
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // loginJsonString
        String contentJsonStr
                = Objects.isNull(authentication.getPrincipal()) ? StringUtils.EMPTY : authentication.getName();
        // 通过authentication获取登录用户信息AuthUser
        AuthUser authUser = customUserDetailService.loadUserByContent(contentJsonStr);
        // principal,credentials,authorities
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
                authUser, authentication.getCredentials(), authUser.getAuthorities()
        );
        result.setDetails(authentication.getDetails());
        return result;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}
2. 拦截器 UserInfoInterceptor
/**
 * 拦截器:用户信息本地线程存储
 */
public class UserInfoInterceptor extends HandlerInterceptorAdapter {
    /**
     * 拦截所有请求,在Controller层方法之前调用
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 判断用户是否被认证,如果没有认证不放行
        boolean isAuthenticated = request.authenticate(response);
        if (!isAuthenticated) {
            return false;
        }
        // 存储用户信息到本地线程
        Principal userPrincipal = request.getUserPrincipal();
        OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) userPrincipal;
        AuthUser ngsocUser = (AuthUser) oAuth2Authentication.getUserAuthentication().getPrincipal();
        UserInfo userInfo = ngsocUser.getUserInfo();
        UserInfoShareHolder.setUserInfo(userInfo);
        // 放行,继续执行Controller层的方法
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserInfoShareHolder.remove();
        super.afterCompletion(request, response, handler, ex);
    }
}
@Configuration
@EnableWebMvc
public class CommonWebMvcAutoConfiguration implements WebMvcConfigurer {

    @Bean
    public UserInfoInterceptor userInfoInterceptor() {
        return new UserInfoInterceptor();
    }

    @Override
    public void addInterceptors(@NonNull InterceptorRegistry registry) {
        // 拦截器会拦截所有请求,需要配置放行的请求
        registry.addInterceptor(userInfoInterceptor())
                // 放行的请求
                .excludePathPatterns("/api/v1/login");
    }
}
3. 自定义 CustomTokenExtractor
@Slf4j
public class CustomTokenExtractor extends BearerTokenExtractor {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 从请求中提取 token
     */
    @Override
    protected String extractToken(HttpServletRequest request) {
        String token = null;
        // 从cookie中获取token,确保tokenCookie.setHttpOnly(true)
        Cookie[] cookies = request.getCookies();
        if (Objects.nonNull(cookies)) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals(OAuth2AccessToken.ACCESS_TOKEN)) {
                    token = cookie.getValue();
                    break;
                }
            }
        }

        // 从header中获取token
        if (StringUtils.isEmpty(token)) {
            log.debug("Token not found in cookies. Trying request header.");
            token = extractHeaderToken(request);
        }

        // 从parameters获取token
        if (StringUtils.isEmpty(token)) {
            log.debug("Token not found in headers. Trying request parameters.");
            token = request.getParameter(OAuth2AccessToken.ACCESS_TOKEN);
        }

        if (StringUtils.isEmpty(token)) {
            log.debug("Token not found in headers and request parameters and cookie. Not an OAuth2 request.");
            return null;
        }
        request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, OAuth2AccessToken.BEARER_TYPE);

        return freshTimeAndGetRedisToken(request,token);
    }

  	// 省略延长页面自动退出登录时长逻辑.....
}

7. 源码分析 - 基于url权限表达式

1. 资源服务器配置类 ResourceServerAutoConfiguration

在资源服务器配置类中添加url权限表达式,对请求url进行访问权限控制

@Slf4j
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerAutoConfiguration extends ResourceServerConfigurerAdapter {
    
    @Autowired
    private TokenStore tokenStore;

    @Value("${spring.application.name}")
    private String appName;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId(appName);
        resources.tokenStore(tokenStore);
        resources.tokenExtractor(tokenExtractor());
    }

    @Bean
    @Primary
    public TokenExtractor tokenExtractor() {
        CustomTokenExtractor customTokenExtractor = new CustomTokenExtractor();
        return customTokenExtractor;
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // http.authorizeRequests()主要是对url进行访问权限控制,通过这个方法来实现url授权操作
        http.authorizeRequests()
                // permitAll()权限表达式
                .antMatchers("/api/v1/login", "/api/v1/token").permitAll()
                // hasAnyAuthority()权限表达式
                .antMatchers("/api/v1/doc").hasAnyAuthority("knowledgeEdit","knowledgeQuery");
        // 其他请求只要认证后的用户就可以访问
        http.authorizeRequests().anyRequest().authenticated();
        http.formLogin().disable();
        http.httpBasic().disable();
    }
}
2. 业务 knowledge 服务
@RestController
@RequestMapping("/api/v1")
public class DocController {
    
    @GetMapping("/doc")
    public String getDocList(){
        return "docList";
    }
}
3. 源码流程分析

在这里插入图片描述

我们在资源服务器中配置了用户具备knowledgeEditknowledgeQuery中的任一权限就可以访问 /api/v1/doc 地址。那我们先让用户访问/api/v1/login 请求获取令牌token,然后通过令牌访问/api/v1/doc 接口地址,然后通过源码看一下它是怎么通过权限表达式来进行权限管理的。

01. 进入 OAuth2AuthenticationProcessingFilter#doFilter

当访问/api/v1/doc 接口受限资源时,请求会被过滤器OAuth2AuthenticationProcessingFilter拦截,这个过滤器他的作用就是从请求中获取认证服务器发出去的Token,(因为在调用我们服务的时候请求中都会包含Token),获取到Token后会根据在认证服务器中Token的存储策略去对应的存储中找到Token信息,然后根据用户信息是否存在,是否有权限来判断是否能访问资源。

在这里插入图片描述

02. 进入 BearerTokenExtractor#extract 提取token返回待认证的Authentication对象

从请求request中获取令牌token,根据tokenValue获取PreAuthenticatedAuthenticationToken认证对象,该对象是待认证的Authentication对象,需要交给AuthenticationManager认证管理器完成认证。

在这里插入图片描述

03. 进入 CustomTokenExtractor#extractToken 从request中提取token

在这里插入图片描述

04. 回到 OAuth2AuthenticationProcessingFilter#doFilter

可以看到获取Authentication对象是待认证的对象,认证的主体是userId,认证的凭证为密码。

在这里插入图片描述

05. 进入 OAuth2AuthenticationManager#authenticate 完成Authentication对象的认证

调用OAuth2AuthenticationManager认证管理器的authenticate(Authentication authentication) 方法完成Authentication对象的认证,OAuth2AuthenticationManager是AuthenticationManager的实现类:

在这里插入图片描述

06. 进入 DefaultTokenServices#loadAuthentication 填充完整的Authentication认证信息

该方法就是到认证数据源中获取完整的 Authentication 认证信息。根据 accessTokenValue 到 tokenStore 中获取 OAtuth2AccessToken:

在这里插入图片描述

根据 OAuthAccessToken 到 tokenStore 中获取 OAuth2Authentication 对象:

在这里插入图片描述

07. 回到 OAuth2AuthenticationManager#authenticate

在这里插入图片描述

08. 回到 OAuth2AuthenticationProcessingFilter#doFilter

在这里插入图片描述

09. 进入 SecurityExpressionRoot#hasAnyAuthority 权限表达式

权限表达式指定的权限:knowledgeEdit,knowledgeQuery

在这里插入图片描述

10. 进入 SecurityExpressionRoot#hasAnyAuthorityName 判断用户是否具备指定的权限

在这里插入图片描述

11. 进入 SecurityExpressionRoot#getAuthoritySet 获取用户具备的所有权限

在这里插入图片描述

12. 回到 SecurityExpressionRoot#hasAnyAuthorityName

在这里插入图片描述

13. 进入 UserInfoInterceptor#preHandle

在这里插入图片描述

14. 进入 DocController#getDocList

在这里插入图片描述

结论:我们在资源服务器中配置了 antMatchers("/api/v1/doc").hasAnyAuthority("knowledgeEdit","knowledgeQuery");,使用了权限表达式hasAnyAuthority,因此会调用 SecurityExpressionRoot 类的 hasAnyAuthority 方法,在该方法中获取用户具备的所有权限并判断用户具备的权限中是否包含指定的权限,如果包含则放行否则抛出异常。

因此,当我们在资源服务器中使用了某个权限表达式时,最终就会调用 SecurityExpressionRoot 类中的权限表达式方法,比如antMatchers("/api/v1/login", "/api/v1/token").permitAll() 使用了 permitAll 这个权限表达式,那么最终就会调用 SecurityExpressionRoot 类中的 permitAll() 方法,该方法始终返回true,代表用户可以在没有任何权限的情况下访问该url接口。

7. 源码分析 - 基于注解的权限表达式

1. 资源服务器配置类 ResourceServerAutoConfiguration
@Slf4j
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerAutoConfiguration extends ResourceServerConfigurerAdapter {

    @Autowired
    private TokenStore tokenStore;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId(appName);
        resources.tokenStore(tokenStore);
        resources.tokenExtractor(tokenExtractor());
    }

    @Bean
    @Primary
    public TokenExtractor tokenExtractor() {
        CustomTokenExtractor customTokenExtractor = new CustomTokenExtractor();
        return customTokenExtractor;
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // http.authorizeRequests()主要是对url进行访问权限控制,通过这个方法来实现url授权操作
        http.authorizeRequests()
                // permitAll()权限表达式
                .antMatchers("/api/v1/login", "/api/v1/token").permitAll();
        // 其他请求只要认证后的用户就可以访问
        http.authorizeRequests().anyRequest().authenticated();
        http.formLogin().disable();
        http.httpBasic().disable();
    }
}
2. 业务 knowledge 服务
@RestController
@RequestMapping("/api/v1")
public class DocController {

    @PreAuthorize("hasAnyAuthority('knowledgeEdit','knowledgeQuery')")
    @GetMapping("/doc")
    public String getDocList(){
        return "docList";
    }
}
3. 源码流程分析

在这里插入图片描述
我们将资源服务器配置中的 url 权限表达式去掉,改为注解权限表达式,再来看下整个流程。

01. 进入OAuth2AuthenticationProcessingFilter#doFilter

在这里插入图片描述

02. 进入 BearerTokenExtractor#extract

在这里插入图片描述

03. 进入CustomTokenExtractor#extractToken:

在这里插入图片描述

在这里插入图片描述

04. 回到 BearerTokenExtractor#extract

在这里插入图片描述

05. 回到 OAuth2AuthenticationProcessingFilter#doFilter

在这里插入图片描述

将待认证的 Authentication 对象交给 AuthenticationManager 的实现类 OAuthAuthenticationManager 认证:

在这里插入图片描述

可以看到获取Authentication对象是待认证的对象,认证的主体是userId,认证的凭证为密码。

06. 进入 OAuth2AuthenticationManager#authenticate

这认证管理器中会调用DefaultTokenServices的loadAuthentication方法,到数据源中获取Authentication对象信息:

在这里插入图片描述

07. 进入DefaultTokenServices#loadAuthentication

在这里插入图片描述

08. 回到 OAuth2AuthenticationProcessingFilter#doFilter

在这里插入图片描述

在这里插入图片描述

09. 进入 UserInfoInterceptor#preHandle

在这里插入图片描述

10. 进入 SecurityExpressionRoot#hasAnyAuthority 权限表达式方法

因为我们使用了注解权限表达式:@PreAuthorize(“hasAnyAuthority(‘knowledgeEdit’,‘knowledgeQuery’)”),最终会调用 hasAnyAuthority 方法判断登录用户是否具备指定的权限中的一个,如果具备就返回true,否则返回false:

在这里插入图片描述

11. 进入 SecurityExpressionRoot#hasAnyAuthorityName 判断用户是否具备指定的权限

在这里插入图片描述

12. 进入 SecurityExpressionRoot#getAuthoritySet 获取用户具备的所有权限

在这里插入图片描述

13. 回到 SecurityExpressionRoot#hasAnyAuthorityName

在这里插入图片描述

14. 进入 DocController#getDocList

在这里插入图片描述

经过上面的分析我们可以看到url权限表达式会在注解权限表达式之前执行,那么如果我们在url权限表达式中配置了permitAll 权限表达式,在注解中配置了hasAnyAuthority权限表达式会怎么样呢?会以哪个权限表达式的执行结果为准呢?这个问题将会涉及到一个新的问题,我们放到后面的文章中去讲。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我一直在流浪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值