SpringSecurity5(10-动态权限管理)

打印 上一主题 下一主题

主题 1921|帖子 1921|积分 5763

授权流程


SpringSecurity 的授权流程如下:

  • 拦截哀求,已认证用户访问受掩护的 web 资源将被 SecurityFilterChain 中的 FilterSecurityInterceptor 的子类拦截。
  • 获取资源访问策略,FilterSecurityInterceptor 会从 SecurityMetadataSource 的子类 DefaultFilterInvocationSecurityMetadataSource 获取要访问当前资源所需要的权限 Collection < ConfigAttribute >。SecurityMetadataSource 其实就是读取访问策略的抽象,而读取的内容其实就是我们配置的访问规则, 读取访问策略如下:
  1. http.authorizeRequests()
  2.         .antMatchers("/r/r1").hasAuthority("p1")
  3.         .antMatchers("/r/r2").hasAuthority("p2")
  4.         ...
复制代码

  • FilterSecurityInterceptor 会调用 AccessDecisionManager 举行授权决策,若决策通过,则允许访问资源,否则将禁止访问



相干组件

权限资源

要实现动态的权限验证,首先要有对应的访问权限资源,Spring Security 是通过 SecurityMetadataSource 来加载访问时所需要的具体权限
  1. public interface SecurityMetadataSource extends AopInfrastructureBean {
  2.     //根据提供的受保护对象的信息(URI),获取该 URI 配置的所有角色
  3.     Collection<ConfigAttribute> getAttributes(Object var1) throws IllegalArgumentException;
  4.    
  5.     //获取全部角色,如果返回了所有定义的权限资源,Spring Security 会在启动时
  6.     //校验每个 ConfigAttribute 是否配置正确,不需要校验直接返回 null
  7.     Collection<ConfigAttribute> getAllConfigAttributes();
  8.    
  9.     //对特定的安全对象是否提供 ConfigAttribute 支持
  10.     //web 项目一般使用 FilterInvocation 来判断,或者直接返回 true
  11.     boolean supports(Class<?> var1);
  12. }
复制代码
SecurityMetadataSource 是一个接口,同时另有一个接口 FilterInvocationSecurityMetadataSource 继续于它,但其只是一个标识接口,对应于 FilterInvocation,本身并无任何内容
  1. public interface FilterInvocationSecurityMetadataSource extends SecurityMetadataSource {
  2. }
复制代码
权限决策管理器

有了权限资源,知道了当前访问的 url 需要的具体权限,接下来就是决策当前的访问是否能通过权限验证了,AccessDecisionManager 中包含的一系列 AccessDecisionVoter 将会被用来对 Authentication 是否有权访问受掩护对象举行投票,AccessDecisionManager 根据投票结果做出最终决策
  1. public interface AccessDecisionManager {
  2.     // 决策主要通过其持有的 AccessDecisionVoter 来进行投票决策
  3.     void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException;
  4.    
  5.     // 以确定 AccessDecisionManager 是否可以处理传递的 ConfigAttribute
  6.     boolean supports(ConfigAttribute var1);
  7.        
  8.     // 以确保配置的 AccessDecisionManager 支持安全拦截器将呈现的安全 object 类型。
  9.     boolean supports(Class<?> var1);
  10. }
复制代码

  • authentication:要访问资源的访问者的身份
  • object:要访问的受掩护资源,web 哀求对应的 FilterInvocation
  • configAttributes:受掩护资源的访问策略,通过 SecurityMetadataSource 获取
Spring Security 内置了三个基于投票的 AccessDecisionManager 实现类如下,它们分别是 AffirmativeBased、ConsensusBased 和 UnanimousBased
AffirmativeBased

基于肯定的决策器,用户持有一个同意访问的角色就能通过

  • 只要有 AccessDecisionVoter 的投票为 ACCESS_GRANTED 则同意用户举行访问;
  • 如果全部弃权也表示通过;
  • 如果没有一个人投赞成票,但是有人投反对票,则将抛出 AccessDeniedException。 Spring Security 默认使用的是 AffirmativeBased
ConsensusBased

基于共识的决策器,用户持有同意的角色数量多于禁止的角色数量

  • 如果赞成票多于反对票则表示通过;如果反对票多于赞成票则将抛出 AccessDeniedException
  • 如果赞成票与反对票相同且不即是 0,并且属性 allowIfEqualGrantedDeniedDecisions 的值为 true(默以为 true),则表示通过,否则将抛出非常 AccessDeniedException
  • 如果所有的 AccessDecisionVoter 都弃权了,则将视参数 allowIfAllAbstainDecisions 的值而定(默以为 false),如果该值为 true 则表示通过,否则将抛出非常 AccessDeniedException
UnanimousBased

基于一致的决策器,用户持有的所有角色都同意访问才能放行

  • 如果受掩护对象配置的某一个 ConfigAttribute 被任意的 AccessDecisionVoter 反对了,则将抛出 AccessDeniedException。
  • 如果没有反对票,但是有赞成票,则表示通过。
  • 如果全部弃权了,则将视参数 allowIfAllAbstainDecisions 的值而定,true 则通过,false 则抛出 AccessDeniedException。
权限决策投票器
  1. public interface AccessDecisionVoter<S> {
  2.     // 赞成
  3.     int ACCESS_GRANTED = 1;
  4.     // 弃权
  5.     int ACCESS_ABSTAIN = 0;
  6.     // 否决
  7.     int ACCESS_DENIED = -1;
  8.     // 用于判断对于当前 ConfigAttribute 访问规则是否支持
  9.     boolean supports(ConfigAttribute attribute);
  10.     // 用于判断该类是否支持
  11.     boolean supports(Class<?> clazz);
  12.     // 如果支持的情况下,vote 方法对其进行判断投票返回对应的授权结果
  13.     int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);
  14. }
复制代码
WebExpressionVoter

最常用的,也是 SpringSecurity 中默认的 FilterSecurityInterceptor 实例中 AccessDecisionManager 默认的投票器,它其实就是 http.authorizeRequests()基于 Spring-EL 举行控制权限的授权决策类。
AuthenticatedVoter

AuthenticatedVoter 针对的是 ConfigAttribute#getAttribute() 中配置为 IS_AUTHENTICATED_FULLY 、IS_AUTHENTICATED_REMEMBERED、IS_AUTHENTICATED_ANONYMOUSLY 权限标识时的授权决策。因此,其投票策略比较简单
PreInvocationAuthorizationAdviceVoter

用于处理基于注解 @PreFilter 和 @PreAuthorize 生成的 PreInvocationAuthorizationAdvice,来处理授权决策的实现
RoleVoter

角色投票器。用于 ConfigAttribute#getAttribute() 中配置为角色的授权决策。其默认前缀为 ROLE_,可以自界说,也可以设置为空,直接使用角色标识举行判断。这就意味着,任何属性都可以使用该投票器投票,也就偏离了该投票器的本意,是不可取的。
RoleHierarchyVoter

基于 RoleVoter,唯一的不同就是该投票器中的角色是附带上下级关系的。也就是说,角色 A 包含角色 B,角色 B 包含 角色 C,此时,如果用户拥有角色 A,那么理论上可以同时拥有角色 B、角色 C 的全部资源访问权限.
自界说动态权限控制

实现逻辑


  • 自界说 FilterSecurityInterceptor,可仿写 FilterSecurityInterceptor,实现抽象类 AbstractSecurityInterceptor 以及 Filter 接口,其主要的是把自界说的 SecurityMetadataSource 与自界说 accessDecisionManager 配置到自界说 FilterSecurityInterceptor 的拦截器中
  • 自界说 SecurityMetadataSource,实现接口 FilterInvocationSecurityMetadataSource,实现从数据库或者其他数据源中加载 ConfigAttribute(便是从数据库或者其他数据源中加载资源权限)
  • 自界说 AccessDecisionManager,可使用基于 AccessDecisionVoter 实现权限认证的官方 UnanimousBased
  • 自界说 AccessDecisionVoter
  • 自界说 MyFilterSecurityInterceptor


  • 加载自界说的 SecurityMetadataSource 到自界说的 FilterSecurityInterceptor 中;
  • 加载自界说的 AccessDecisionManager 到自界说的 FilterSecurityInterceptor 中;
  • 重写 invoke 方法
基于用户的权限控制
  1. @Service
  2. public class UmsAdminServiceImpl implements UserDetailsService {
  3.    
  4.     @Override
  5.     public UserDetails loadUserByUsername(String username){
  6.         // 数据库获取用户信息
  7.         UmsAdmin admin = getAdminByUsername(username);
  8.         if (admin != null) {
  9.             // 获取用户权限
  10.             List<UmsPermission> permissionList = getPermissionList(admin.getId());
  11.             return new AdminUserDetails(admin,permissionList);
  12.         }
  13.         throw new UsernameNotFoundException("用户名或密码错误");
  14.     }
  15. }
复制代码
Spring Security 把用户拥有的权限值和接口上注解界说的权限值举行比对,如果包含则可以访问,反之就不可以访问;但是这样做会带来一些问题,我们需要在每个接口上都界说好访问该接口的权限值,而且只能挨个控制接口的权限,无法批量控制
基于路径的动态权限控制

动态权限数据源
  1. /**
  2. * 动态权限相关业务类
  3. */
  4. public interface DynamicSecurityService {
  5.     /**
  6.      * 加载资源 ANT 通配符和资源对应 MAP
  7.      */
  8.     Map<String, ConfigAttribute> loadDataSource();
  9. }
复制代码
  1. @Configuration
  2. @EnableGlobalMethodSecurity(prePostEnabled = true)
  3. public class MallSecurityConfig {
  4.     // 后台获取资源服务类(自定义)
  5.     @Autowired
  6.     private UmsResourceService resourceService;
  7.     @Bean
  8.     public DynamicSecurityService dynamicSecurityService() {
  9.         return new DynamicSecurityService() {
  10.             @Override
  11.             public Map<String, ConfigAttribute> loadDataSource() {
  12.                 Map<String, ConfigAttribute> map = new ConcurrentHashMap<>();
  13.                 List<UmsResource> resourceList = resourceService.listAll();
  14.                 for (UmsResource resource : resourceList) {
  15.                     map.put(resource.getUrl(), new org.springframework.security.access.SecurityConfig(resource.getName()));
  16.                 }
  17.                 return map;
  18.             }
  19.         };
  20.     }
  21. }
复制代码
  1. /**
  2. * 动态权限数据源,用于获取动态权限规则
  3. */
  4. public class DynamicSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
  5.    
  6.     private static Map<String, ConfigAttribute> configAttributeMap = null;
  7.     @Autowired
  8.     private DynamicSecurityService dynamicSecurityService;
  9.     @PostConstruct
  10.     public void loadDataSource() {
  11.         configAttributeMap = dynamicSecurityService.loadDataSource();
  12.     }
  13.     public void clearDataSource() {
  14.         configAttributeMap.clear();
  15.         configAttributeMap = null;
  16.     }
  17.     @Override
  18.     public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
  19.         if (configAttributeMap == null) {
  20.             this.loadDataSource();
  21.         }
  22.         List<ConfigAttribute> configAttributes = new ArrayList<>();
  23.         //获取当前访问的路径
  24.         String url = ((FilterInvocation) o).getRequestUrl();
  25.         String path = URLUtil.getPath(url);
  26.         PathMatcher pathMatcher = new AntPathMatcher();
  27.         Iterator<String> iterator = configAttributeMap.keySet().iterator();
  28.         //获取访问该路径所需资源
  29.         while (iterator.hasNext()) {
  30.             String pattern = iterator.next();
  31.             if (pathMatcher.match(pattern, path)) {
  32.                 configAttributes.add(configAttributeMap.get(pattern));
  33.             }
  34.         }
  35.         // 未设置操作请求权限,返回空集合
  36.         return configAttributes;
  37.     }
  38.     @Override
  39.     public Collection<ConfigAttribute> getAllConfigAttributes() {
  40.         return null;
  41.     }
  42.     @Override
  43.     public boolean supports(Class<?> aClass) {
  44.         return true;
  45.     }
  46. }
复制代码
流程如下:

  • 从数据库中查询出来所有的菜单,然后再过滤找到满足当前哀求 URL 的,只要满足前面匹配的都需要权限控制
  • 由于我们的后台资源规则被缓存在了一个 Map 对象之中,所以当后台资源发生变化时,我们需要清空缓存的数据,然后下次查询时就会被重新加载进来,需要调用 clearDataSource 方法来清空缓存的数据
  • 之后我们需要实现 AccessDecisionManager 接口来实现权限校验,对于没有配置资源的接口我们直接允许访问,对于配置了资源的接口,我们把访问所需资源和用户拥有的资源举行比对,如果匹配则允许访问
注意:菜单权限是每次都要全量查询数据库,如果数据多的话,可能会影响性能,这里改造读取缓存,但是新增修改菜单时,记得更新缓存数据
动态权限决策管理器
  1. /**
  2. * 动态权限决策管理器,用于判断用户是否有访问权限
  3. */
  4. public class DynamicAccessDecisionManager implements AccessDecisionManager {
  5.     @Override
  6.     public void decide(Authentication authentication, Object object,
  7.                        Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
  8.         // 当接口未被配置资源时直接放行
  9.         if (CollUtil.isEmpty(configAttributes)) {
  10.             return;
  11.         }
  12.         Iterator<ConfigAttribute> iterator = configAttributes.iterator();
  13.         while (iterator.hasNext()) {
  14.             ConfigAttribute configAttribute = iterator.next();
  15.             //将访问所需资源或用户拥有资源进行比对
  16.             String needAuthority = configAttribute.getAttribute();
  17.             for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
  18.                 if (needAuthority.trim().equals(grantedAuthority.getAuthority())) {
  19.                     return;
  20.                 }
  21.             }
  22.         }
  23.         throw new AccessDeniedException("抱歉,您没有访问权限");
  24.     }
  25.     @Override
  26.     public boolean supports(ConfigAttribute configAttribute) {
  27.         return true;
  28.     }
  29.     @Override
  30.     public boolean supports(Class<?> aClass) {
  31.         return true;
  32.     }
  33. }
复制代码
JWT 登录授权
  1. /**
  2. * JWT 登录授权过滤器
  3. */
  4. public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
  5.     private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);
  6.     @Autowired
  7.     private UserDetailsService userDetailsService;
  8.     @Autowired
  9.     private JwtTokenUtil jwtTokenUtil;
  10.     @Value("${jwt.tokenHeader}")
  11.     private String tokenHeader;
  12.     @Value("${jwt.tokenHead}")
  13.     private String tokenHead;
  14.     @Override
  15.     protected void doFilterInternal(HttpServletRequest request,
  16.                                     HttpServletResponse response,
  17.                                     FilterChain chain) throws ServletException, IOException {
  18.         String authHeader = request.getHeader(this.tokenHeader);
  19.         if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
  20.             String authToken = authHeader.substring(this.tokenHead.length());// The part after "Bearer "
  21.             String username = jwtTokenUtil.getUserNameFromToken(authToken);
  22.             LOGGER.info("checking username:{}", username);
  23.             if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
  24.                 UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
  25.                 if (jwtTokenUtil.validateToken(authToken, userDetails)) {
  26.                     UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
  27.                     authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
  28.                     LOGGER.info("authenticated user:{}", username);
  29.                     SecurityContextHolder.getContext().setAuthentication(authentication);
  30.                 }
  31.             }
  32.         }
  33.         chain.doFilter(request, response);
  34.     }
  35. }
复制代码
  1. public class JwtTokenUtil {
  2.     private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);
  3.     private static final String CLAIM_KEY_USERNAME = "sub";
  4.     private static final String CLAIM_KEY_CREATED = "created";
  5.     @Value("${jwt.secret}")
  6.     private String secret;
  7.     @Value("${jwt.expiration}")
  8.     private Long expiration;
  9.     @Value("${jwt.tokenHead}")
  10.     private String tokenHead;
  11.     /**
  12.      * 根据负责生成 JWT 的 token
  13.      */
  14.     private String generateToken(Map<String, Object> claims) {
  15.         return Jwts.builder()
  16.                 .setClaims(claims)
  17.                 .setExpiration(generateExpirationDate())
  18.                 .signWith(SignatureAlgorithm.HS512, secret)
  19.                 .compact();
  20.     }
  21.     /**
  22.      * 从 token 中获取 JWT 中的负载
  23.      */
  24.     private Claims getClaimsFromToken(String token) {
  25.         Claims claims = null;
  26.         try {
  27.             claims = Jwts.parser()
  28.                     .setSigningKey(secret)
  29.                     .parseClaimsJws(token)
  30.                     .getBody();
  31.         } catch (Exception e) {
  32.             LOGGER.info("JWT格式验证失败:{}", token);
  33.         }
  34.         return claims;
  35.     }
  36.     /**
  37.      * 生成 token 的过期时间
  38.      */
  39.     private Date generateExpirationDate() {
  40.         return new Date(System.currentTimeMillis() + expiration * 1000);
  41.     }
  42.     /**
  43.      * 从 token 中获取登录用户名
  44.      */
  45.     public String getUserNameFromToken(String token) {
  46.         String username;
  47.         try {
  48.             Claims claims = getClaimsFromToken(token);
  49.             username = claims.getSubject();
  50.         } catch (Exception e) {
  51.             username = null;
  52.         }
  53.         return username;
  54.     }
  55.     /**
  56.      * 验证 token 是否还有效
  57.      *
  58.      * @param token       客户端传入的 token
  59.      * @param userDetails 从数据库中查询出来的用户信息
  60.      */
  61.     public boolean validateToken(String token, UserDetails userDetails) {
  62.         String username = getUserNameFromToken(token);
  63.         return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
  64.     }
  65.     /**
  66.      * 判断 token 是否已经失效
  67.      */
  68.     private boolean isTokenExpired(String token) {
  69.         Date expiredDate = getExpiredDateFromToken(token);
  70.         return expiredDate.before(new Date());
  71.     }
  72.     /**
  73.      * 从 token 中获取过期时间
  74.      */
  75.     private Date getExpiredDateFromToken(String token) {
  76.         Claims claims = getClaimsFromToken(token);
  77.         return claims.getExpiration();
  78.     }
  79.     /**
  80.      * 根据用户信息生成 token
  81.      */
  82.     public String generateToken(UserDetails userDetails) {
  83.         Map<String, Object> claims = new HashMap<>();
  84.         claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
  85.         claims.put(CLAIM_KEY_CREATED, new Date());
  86.         return generateToken(claims);
  87.     }
  88.     /**
  89.      * 当原来的 token 没过期时是可以刷新的
  90.      *
  91.      * @param oldToken 带 tokenHead 的 token
  92.      */
  93.     public String refreshHeadToken(String oldToken) {
  94.         if(StrUtil.isEmpty(oldToken)){
  95.             return null;
  96.         }
  97.         String token = oldToken.substring(tokenHead.length());
  98.         if(StrUtil.isEmpty(token)){
  99.             return null;
  100.         }
  101.         //token 校验不通过
  102.         Claims claims = getClaimsFromToken(token);
  103.         if(claims==null){
  104.             return null;
  105.         }
  106.         //如果 token 已经过期,不支持刷新
  107.         if(isTokenExpired(token)){
  108.             return null;
  109.         }
  110.         //如果 token 在 30 分钟之内刚刷新过,返回原 token
  111.         if(tokenRefreshJustBefore(token,30*60)){
  112.             return token;
  113.         }else{
  114.             claims.put(CLAIM_KEY_CREATED, new Date());
  115.             return generateToken(claims);
  116.         }
  117.     }
  118.     /**
  119.      * 判断 token 在指定时间内是否刚刚刷新过
  120.      * @param token 原 token
  121.      * @param time 指定时间(秒)
  122.      */
  123.     private boolean tokenRefreshJustBefore(String token, int time) {
  124.         Claims claims = getClaimsFromToken(token);
  125.         Date created = claims.get(CLAIM_KEY_CREATED, Date.class);
  126.         Date refreshDate = new Date();
  127.         //刷新时间在创建时间的指定时间内
  128.         if(refreshDate.after(created)&&refreshDate.before(DateUtil.offsetSecond(created,time))){
  129.             return true;
  130.         }
  131.         return false;
  132.     }
  133. }
复制代码
动态权限过滤器
  1. /**
  2. * 动态权限过滤器,用于实现基于路径的动态权限过滤
  3. */
  4. public class DynamicSecurityFilter extends AbstractSecurityInterceptor implements Filter {
  5.     @Autowired
  6.     private DynamicSecurityMetadataSource dynamicSecurityMetadataSource;
  7.     @Autowired
  8.     private IgnoreUrlsConfig ignoreUrlsConfig;
  9.     @Autowired
  10.     private DynamicAccessDecisionManager dynamicAccessDecisionManager;
  11.     @Autowired
  12.     public void setMyAccessDecisionManager() {
  13.         super.setAccessDecisionManager(dynamicAccessDecisionManager);
  14.     }
  15.     @Override
  16.     public void init(FilterConfig filterConfig) throws ServletException {
  17.     }
  18.     @Override
  19.     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  20.         HttpServletRequest request = (HttpServletRequest) servletRequest;
  21.         FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
  22.         //OPTIONS 请求直接放行
  23.         if(request.getMethod().equals(HttpMethod.OPTIONS.toString())){
  24.             fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
  25.             return;
  26.         }
  27.         //白名单请求直接放行
  28.         PathMatcher pathMatcher = new AntPathMatcher();
  29.         for (String path : ignoreUrlsConfig.getUrls()) {
  30.             if(pathMatcher.match(path,request.getRequestURI())){
  31.                 fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
  32.                 return;
  33.             }
  34.         }
  35.         //此处会调用 AccessDecisionManager 中的 decide 方法进行鉴权操作
  36.         InterceptorStatusToken token = super.beforeInvocation(fi);
  37.         try {
  38.             fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
  39.         } finally {
  40.             super.afterInvocation(token, null);
  41.         }
  42.     }
  43.     @Override
  44.     public void destroy() {
  45.     }
  46.     @Override
  47.     public Class<?> getSecureObjectClass() {
  48.         return FilterInvocation.class;
  49.     }
  50.     @Override
  51.     public SecurityMetadataSource obtainSecurityMetadataSource() {
  52.         return dynamicSecurityMetadataSource;
  53.     }
  54. }
复制代码
自界说登录处理器
  1. /**
  2. * 自定义返回结果:未登录或登录过期
  3. */
  4. public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
  5.     @Override
  6.     public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
  7.         response.setHeader("Access-Control-Allow-Origin", "*");
  8.         response.setHeader("Cache-Control","no-cache");
  9.         response.setCharacterEncoding("UTF-8");
  10.         response.setContentType("application/json");
  11.         response.getWriter().println(JSONUtil.parse(CommonResult.unauthorized(authException.getMessage())));
  12.         response.getWriter().flush();
  13.     }
  14. }
复制代码
当前端跨域访问没有权限的接口时,会出现跨域问题,只需要在没有权限访问的处理类 RestfulAccessDeniedHandler 中添加允许跨域访问的响应头即可
  1. /**
  2. * 自定义返回结果:没有权限访问时
  3. */
  4. public class RestfulAccessDeniedHandler implements AccessDeniedHandler{
  5.     @Override
  6.     public void handle(HttpServletRequest request,
  7.                        HttpServletResponse response,
  8.                        AccessDeniedException e) throws IOException, ServletException {
  9.         response.setHeader("Access-Control-Allow-Origin", "*");
  10.         response.setHeader("Cache-Control","no-cache");
  11.         response.setCharacterEncoding("UTF-8");
  12.         response.setContentType("application/json");
  13.         response.getWriter().println(JSONUtil.parse(CommonResult.forbidden(e.getMessage())));
  14.         response.getWriter().flush();
  15.     }
  16. }
复制代码
配置类
  1. /**
  2. * 用于配置白名单资源路径
  3. */
  4. @Getter
  5. @Setter
  6. @ConfigurationProperties(prefix = "secure.ignored")
  7. public class IgnoreUrlsConfig {
  8.     private List<String> urls = new ArrayList<>();
  9. }
复制代码
  1. /**
  2. * SpringSecurity 通用配置
  3. * 包括通用 Bean、Security 通用 Bean 及动态权限通用 Bean
  4. */
  5. @Configuration
  6. public class CommonSecurityConfig {
  7.     @Bean
  8.     public PasswordEncoder passwordEncoder() {
  9.         return new BCryptPasswordEncoder();
  10.     }
  11.     @Bean
  12.     public IgnoreUrlsConfig ignoreUrlsConfig() {
  13.         return new IgnoreUrlsConfig();
  14.     }
  15.     @Bean
  16.     public JwtTokenUtil jwtTokenUtil() {
  17.         return new JwtTokenUtil();
  18.     }
  19.     @Bean
  20.     public RestfulAccessDeniedHandler restfulAccessDeniedHandler() {
  21.         return new RestfulAccessDeniedHandler();
  22.     }
  23.     @Bean
  24.     public RestAuthenticationEntryPoint restAuthenticationEntryPoint() {
  25.         return new RestAuthenticationEntryPoint();
  26.     }
  27.     @Bean
  28.     public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){
  29.         return new JwtAuthenticationTokenFilter();
  30.     }
  31.     @Bean
  32.     public DynamicAccessDecisionManager dynamicAccessDecisionManager() {
  33.         return new DynamicAccessDecisionManager();
  34.     }
  35.     @Bean
  36.     public DynamicSecurityMetadataSource dynamicSecurityMetadataSource() {
  37.         return new DynamicSecurityMetadataSource();
  38.     }
  39.     @Bean
  40.     public DynamicSecurityFilter dynamicSecurityFilter(){
  41.         return new DynamicSecurityFilter();
  42.     }
  43. }
复制代码
  1. @Configuration
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  3.     @Autowired
  4.     private IgnoreUrlsConfig ignoreUrlsConfig;
  5.     @Autowired
  6.     private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
  7.     @Autowired
  8.     private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
  9.     @Autowired
  10.     private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
  11.     @Autowired
  12.     private DynamicSecurityService dynamicSecurityService;
  13.     @Autowired
  14.     private DynamicSecurityFilter dynamicSecurityFilter;
  15.     @Override
  16.     protected void configure(HttpSecurity httpSecurity) throws Exception {
  17.         ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
  18.         // 不需要保护的资源路径允许访问
  19.         for (String url : ignoreUrlsConfig().getUrls()) {
  20.             registry.antMatchers(url).permitAll();
  21.         }
  22.         // 允许跨域的 OPTIONS 请求
  23.         registry.antMatchers(HttpMethod.OPTIONS)
  24.                 .permitAll();
  25.         // 其他任何请求都需要身份认证
  26.         registry.and()
  27.                 .authorizeRequests()
  28.                 .anyRequest()
  29.                 .authenticated()
  30.                 // 关闭跨站请求防护及不使用 session
  31.                 .and()
  32.                 .csrf()
  33.                 .disable()
  34.                 .sessionManagement()
  35.                 .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  36.                 // 自定义权限拒绝处理类
  37.                 .and()
  38.                 .exceptionHandling()
  39.                 .accessDeniedHandler(restfulAccessDeniedHandler)
  40.                 .authenticationEntryPoint(restAuthenticationEntryPoint)
  41.                 // 自定义权限拦截器 JWT 过滤器
  42.                 .and()
  43.                 .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
  44.         //有动态权限配置时添加动态权限校验过滤器
  45.         if(dynamicSecurityService!=null){
  46.             registry.and().addFilterBefore(dynamicSecurityFilter, FilterSecurityInterceptor.class);
  47.         }
  48.     }
  49. }
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

忿忿的泥巴坨

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表