SpringSecurity权限管理,根据请求URL鉴权

SpringSecurity和Shiro是两大权限框架,前者属于Spring家族,功能比较强,重量级的存在,新手搞的时候可能会经常遇到坑。后者比较轻量级,上手相对比较简单。这两个我都写过权限管理的博客。

前填有个朋友让我帮他把他的一个 SpringSecurity 项目改造成通过URL检查权限,之前他在控制器每个方法上加上如下注解来实现的,该方法通常是初学者使用的,但是用于公司的大型项目肯定不行,比较蠢,代码冗余,不能扩展,不利于维护。

  1. @PreAuthorize("hasAnyAuthority('ROLE_ADMIN'")  // 指定角色权限才能操作方法

现在的目标就是剔除所有的该注解,通过拦截器来判断该用户是否有该URL的权限。

在这之前先讲一下Spring Security实现权限管理吧!

 

一、数据库设计

主要包括用户表,角色表,权限表,用户和角色关联表,角色和权限关联表

重要字段我都用红线标明了

其中权限表(t_permission)其实也充当了菜单表的作用,其中的path字段就是请求路径,如 /post,/psot/new,/post/edit/* (我们以正则表达式的方式写,后面不限字符串以*表示),到时候比对的时候用正则表达式判断

SpringSecurity权限管理,根据请求URL鉴权

 

实体类这里就不给了,这里主要还是讲核心思想

主要关注第二节的配置

 

二、核心文件

1.SpringSecurity的配置文件

WebSecurityConfig.java

  1. package com.liuyanzhao.sens.config.security;
  2. import com.liuyanzhao.sens.common.utils.SecurityUtil;
  3. import com.liuyanzhao.sens.config.security.jwt.AuthenticationFailHandler;
  4. import com.liuyanzhao.sens.config.security.jwt.AuthenticationSuccessHandler;
  5. import com.liuyanzhao.sens.config.security.jwt.JWTAuthenticationFilter;
  6. import com.liuyanzhao.sens.config.security.jwt.RestAccessDeniedHandler;
  7. import com.liuyanzhao.sens.config.security.permission.MyFilterSecurityInterceptor;
  8. import lombok.extern.slf4j.Slf4j;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.beans.factory.annotation.Value;
  11. import org.springframework.context.annotation.Configuration;
  12. import org.springframework.data.redis.core.StringRedisTemplate;
  13. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  14. import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
  15. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  16. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  17. import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
  18. import org.springframework.security.config.http.SessionCreationPolicy;
  19. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  20. import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
  21. /**
  22.  * Security 核心配置类
  23.  * 开启注解控制权限至Controller
  24.  * @author 言曌
  25.  */
  26. @Slf4j
  27. @Configuration
  28. @EnableGlobalMethodSecurity(prePostEnabled=true)
  29. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  30.  @Autowired
  31.     private IgnoredUrlsProperties ignoredUrlsProperties;
  32.     @Autowired
  33.     private UserDetailsServiceImpl userDetailsService;
  34.     @Autowired
  35.     private AuthenticationSuccessHandler successHandler;
  36.     @Autowired
  37.     private AuthenticationFailHandler failHandler;
  38.     @Autowired
  39.     private RestAccessDeniedHandler accessDeniedHandler;
  40.     @Autowired
  41.     private MyFilterSecurityInterceptor myFilterSecurityInterceptor;
  42.     @Autowired
  43.     private StringRedisTemplate redisTemplate;
  44.     @Autowired
  45.     private SecurityUtil securityUtil;
  46.     @Override
  47.     protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  48.         auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
  49.     }
  50.     @Override
  51.     protected void configure(HttpSecurity http) throws Exception {
  52.         ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http
  53.                 .authorizeRequests();
  54.         //除配置文件忽略路径其它所有请求都需经过认证和授权
  55.         for(String url:ignoredUrlsProperties.getUrls()){
  56.             registry.antMatchers(url).permitAll();
  57.         }
  58.         registry.and()
  59.                 //表单登录方式
  60.                 .formLogin()
  61.                 .loginPage("/sens/common/needLogin")
  62.                 //登录请求url
  63.                 .loginProcessingUrl("/sens/login")
  64.                 .permitAll()
  65.                 //成功处理类
  66.                 .successHandler(successHandler)
  67.                 //失败
  68.                 .failureHandler(failHandler)
  69.                 .and()
  70.                 //允许网页iframe
  71.                 .headers().frameOptions().disable()
  72.                 .and()
  73.                 .logout()
  74.                 .permitAll()
  75.                 .and()
  76.                 .authorizeRequests()
  77.                 //任何请求
  78.                 .anyRequest()
  79.                 //需要身份认证
  80.                 .authenticated()
  81.                 .and()
  82.                 //关闭跨站请求防护
  83.                 .csrf().disable()
  84.                 //前后端分离采用JWT 不需要session
  85.                 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  86.                 .and()
  87.                 //自定义权限拒绝处理类
  88.                 .exceptionHandling().accessDeniedHandler(accessDeniedHandler)
  89.                 .and() //添加自定义权限过滤器
  90.                 .addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);
  91.     }
  92. }

addFilterBefore 作用就是当执行权限验证前执行,我们需要在这之前判断即可。

 

2.自定义拦截器

MyFilterSecurityInterceptor.java

  1. package com.liuyanzhao.sens.config.security.permission;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.security.access.SecurityMetadataSource;
  5. import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
  6. import org.springframework.security.access.intercept.InterceptorStatusToken;
  7. import org.springframework.security.web.FilterInvocation;
  8. import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
  9. import org.springframework.stereotype.Component;
  10. import javax.servlet.*;
  11. import java.io.IOException;
  12. /**
  13.  * 权限管理拦截器
  14.  * 监控用户行为
  15.  * @author 言曌
  16.  */
  17. @Slf4j
  18. @Component
  19. public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
  20.     @Autowired
  21.     private FilterInvocationSecurityMetadataSource securityMetadataSource;
  22.     @Autowired
  23.     public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
  24.         super.setAccessDecisionManager(myAccessDecisionManager);
  25.     }
  26.     @Override
  27.     public void init(FilterConfig filterConfig) throws ServletException {
  28.     }
  29.     @Override
  30.     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  31.         FilterInvocation fi = new FilterInvocation(request, response, chain);
  32.         invoke(fi);
  33.     }
  34.     public void invoke(FilterInvocation fi) throws IOException, ServletException {
  35.         InterceptorStatusToken token = super.beforeInvocation(fi);
  36.         try {
  37.             fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
  38.         } finally {
  39.             super.afterInvocation(token, null);
  40.         }
  41.     }
  42.     @Override
  43.     public void destroy() {
  44.     }
  45.     @Override
  46.     public Class<?> getSecureObjectClass() {
  47.         return FilterInvocation.class;
  48.     }
  49.     @Override
  50.     public SecurityMetadataSource obtainSecurityMetadataSource() {
  51.         return this.securityMetadataSource;
  52.     }
  53. }

 

3.自定义权限资源管理器

MySecurityMetadataSource.java

  1. package com.liuyanzhao.sens.config.security.permission;
  2. import com.liuyanzhao.sens.common.constant.CommonConstant;
  3. import com.liuyanzhao.sens.modules.base.entity.Permission;
  4. import com.liuyanzhao.sens.modules.base.service.PermissionService;
  5. import cn.hutool.core.util.StrUtil;
  6. import lombok.extern.slf4j.Slf4j;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.security.access.ConfigAttribute;
  9. import org.springframework.security.access.SecurityConfig;
  10. import org.springframework.security.web.FilterInvocation;
  11. import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
  12. import org.springframework.stereotype.Component;
  13. import org.springframework.util.AntPathMatcher;
  14. import org.springframework.util.PathMatcher;
  15. import java.util.*;
  16. /**
  17.  * 权限资源管理器
  18.  * 为权限决断器提供支持
  19.  *
  20.  * @author 言曌
  21.  */
  22. @Slf4j
  23. @Component
  24. public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
  25.     @Autowired
  26.     private PermissionService permissionService;
  27.     private Map<String, Collection<ConfigAttribute>> map = null;
  28.     /**
  29.      * 加载权限表中所有操作请求权限
  30.      */
  31.     public void loadResourceDefine() {
  32.         map = new HashMap<>(16);
  33.         Collection<ConfigAttribute> configAttributes;
  34.         ConfigAttribute cfg;
  35.         // 获取启用的权限操作请求
  36.         List<Permission> permissions = permissionService.findByTypeAndStatusOrderBySortOrder(CommonConstant.PERMISSION_OPERATION, CommonConstant.STATUS_NORMAL);
  37.         for (Permission permission : permissions) {
  38.             if (StrUtil.isNotBlank(permission.getTitle()) && StrUtil.isNotBlank(permission.getPath())) {
  39.                 configAttributes = new ArrayList<>();
  40.                 cfg = new SecurityConfig(permission.getTitle());
  41.                 //作为MyAccessDecisionManager类的decide的第三个参数
  42.                 configAttributes.add(cfg);
  43.                 //用权限的path作为map的key,用ConfigAttribute的集合作为value
  44.                 map.put(permission.getPath(), configAttributes);
  45.             }
  46.         }
  47.     }
  48.     /**
  49.      * 判定用户请求的url是否在权限表中
  50.      * 如果在权限表中,则返回给decide方法,用来判定用户是否有此权限
  51.      * 如果不在权限表中则放行
  52.      *
  53.      * @param o
  54.      * @return
  55.      * @throws IllegalArgumentException
  56.      */
  57.     @Override
  58.     public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
  59.         if (map == null) {
  60.             loadResourceDefine();
  61.         }
  62.         //Object中包含用户请求request
  63.         String url = ((FilterInvocation) o).getRequestUrl();
  64.         PathMatcher pathMatcher = new AntPathMatcher();
  65.         Iterator<String> iterator = map.keySet().iterator();
  66.         while (iterator.hasNext()) {
  67.             String resURL = iterator.next();
  68.             if (StrUtil.isNotBlank(resURL) && pathMatcher.match(resURL, url)) {
  69.                 return map.get(resURL);
  70.             }
  71.         }
  72.         return null;
  73.     }
  74.     @Override
  75.     public Collection<ConfigAttribute> getAllConfigAttributes() {
  76.         return null;
  77.     }
  78.     @Override
  79.     public boolean supports(Class<?> aClass) {
  80.         return true;
  81.     }
  82. }

 

4.自定义角色决断器

MyAccessDecisionManager.java

  1. package com.liuyanzhao.sens.config.security.permission;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.springframework.security.access.AccessDecisionManager;
  4. import org.springframework.security.access.AccessDeniedException;
  5. import org.springframework.security.access.ConfigAttribute;
  6. import org.springframework.security.authentication.InsufficientAuthenticationException;
  7. import org.springframework.security.core.Authentication;
  8. import org.springframework.security.core.GrantedAuthority;
  9. import org.springframework.stereotype.Component;
  10. import java.util.Collection;
  11. import java.util.Iterator;
  12. /**
  13.  * 权限管理决断器
  14.  * 判断用户拥有的权限或角色是否有资源访问权限
  15.  * @author 言曌
  16.  */
  17. @Slf4j
  18. @Component
  19. public class MyAccessDecisionManager implements AccessDecisionManager {
  20.     @Override
  21.     public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
  22.         if(configAttributes==null){
  23.             return;
  24.         }
  25.         Iterator<ConfigAttribute> iterator = configAttributes.iterator();
  26.         while (iterator.hasNext()){
  27.             ConfigAttribute c = iterator.next();
  28.             String needPerm = c.getAttribute();
  29.             for(GrantedAuthority ga : authentication.getAuthorities()) {
  30.                 // 匹配用户拥有的ga 和 系统中的needPerm
  31.                 if(needPerm.trim().equals(ga.getAuthority())) {
  32.                     return;
  33.                 }
  34.             }
  35.         }
  36.         throw new AccessDeniedException("抱歉,您没有访问权限");
  37.     }
  38.     @Override
  39.     public boolean supports(ConfigAttribute configAttribute) {
  40.         return true;
  41.     }
  42.     @Override
  43.     public boolean supports(Class<?> aClass) {
  44.         return true;
  45.     }
  46. }

 

三、基本整合

1.UserDetailsServiceImpl.java

  1. package com.liuyanzhao.sens.config.security;
  2. import com.liuyanzhao.sens.modules.base.entity.User;
  3. import com.liuyanzhao.sens.common.exception.LoginFailLimitException;
  4. import com.liuyanzhao.sens.modules.base.service.UserService;
  5. import cn.hutool.core.util.StrUtil;
  6. import lombok.extern.slf4j.Slf4j;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.data.redis.core.StringRedisTemplate;
  9. import org.springframework.security.core.userdetails.UserDetails;
  10. import org.springframework.security.core.userdetails.UserDetailsService;
  11. import org.springframework.security.core.userdetails.UsernameNotFoundException;
  12. import org.springframework.stereotype.Component;
  13. import java.util.concurrent.TimeUnit;
  14. /**
  15.  * @author 言曌
  16.  */
  17. @Slf4j
  18. @Component
  19. public class UserDetailsServiceImpl implements UserDetailsService{
  20.     @Autowired
  21.     private StringRedisTemplate redisTemplate;
  22.     @Autowired
  23.     private UserService userService;
  24.     @Override
  25.     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  26.         String flagKey = "loginFailFlag:" + username;
  27.         String value = redisTemplate.opsForValue().get(flagKey);
  28.         Long timeRest = redisTemplate.getExpire(flagKey, TimeUnit.MINUTES);
  29.         if(StrUtil.isNotBlank(value)){
  30.             //超过限制次数
  31.             throw new LoginFailLimitException("登录错误次数超过限制,请"+timeRest+"分钟后再试");
  32.         }
  33.         User user = userService.findByUsername(username);
  34.         return new SecurityUserDetails(user);
  35.     }
  36. }

 

2.SecurityUserDetails.java

  1. package com.liuyanzhao.sens.config.security;
  2. import com.liuyanzhao.sens.common.constant.CommonConstant;
  3. import com.liuyanzhao.sens.modules.base.entity.Permission;
  4. import com.liuyanzhao.sens.modules.base.entity.Role;
  5. import com.liuyanzhao.sens.modules.base.entity.User;
  6. import cn.hutool.core.util.StrUtil;
  7. import lombok.extern.slf4j.Slf4j;
  8. import org.springframework.security.core.GrantedAuthority;
  9. import org.springframework.security.core.authority.SimpleGrantedAuthority;
  10. import org.springframework.security.core.userdetails.UserDetails;
  11. import java.util.ArrayList;
  12. import java.util.Collection;
  13. import java.util.List;
  14. /**
  15.  * @author 言曌
  16.  */
  17. @Slf4j
  18. public class SecurityUserDetails extends User implements UserDetails {
  19.     private static final long serialVersionUID = 1L;
  20.     public SecurityUserDetails(User user) {
  21.         if(user!=null) {
  22.             this.setUsername(user.getUsername());
  23.             this.setPassword(user.getPassword());
  24.             this.setStatus(user.getStatus());
  25.             this.setRoles(user.getRoles());
  26.             this.setPermissions(user.getPermissions());
  27.         }
  28.     }
  29.     /**
  30.      * 添加用户拥有的权限和角色
  31.      * @return
  32.      */
  33.     @Override
  34.     public Collection<? extends GrantedAuthority> getAuthorities() {
  35.         List<GrantedAuthority> authorityList = new ArrayList<>();
  36.         List<Permission> permissions = this.getPermissions();
  37.         // 添加请求权限
  38.         if(permissions!=null&&permissions.size()>0){
  39.             for (Permission permission : permissions) {
  40.                 if(CommonConstant.PERMISSION_OPERATION.equals(permission.getType())
  41.                         &&StrUtil.isNotBlank(permission.getTitle())
  42.                         &&StrUtil.isNotBlank(permission.getPath())) {
  43.                     authorityList.add(new SimpleGrantedAuthority(permission.getTitle()));
  44.                 }
  45.             }
  46.         }
  47.         // 添加角色
  48.         List<Role> roles = this.getRoles();
  49.         if(roles!=null&&roles.size()>0){
  50.             // lambda表达式
  51.             roles.forEach(item -> {
  52.                 if(StrUtil.isNotBlank(item.getName())){
  53.                     authorityList.add(new SimpleGrantedAuthority(item.getName()));
  54.                 }
  55.             });
  56.         }
  57.         return authorityList;
  58.     }
  59.     /**
  60.      * 账户是否过期
  61.      * @return
  62.      */
  63.     @Override
  64.     public boolean isAccountNonExpired() {
  65.         return true;
  66.     }
  67.     /**
  68.      * 是否禁用
  69.      * @return
  70.      */
  71.     @Override
  72.     public boolean isAccountNonLocked() {
  73.         return CommonConstant.USER_STATUS_LOCK.equals(this.getStatus()) ? false : true;
  74.     }
  75.     /**
  76.      * 密码是否过期
  77.      * @return
  78.      */
  79.     @Override
  80.     public boolean isCredentialsNonExpired() {
  81.         return true;
  82.     }
  83.     /**
  84.      * 是否启用
  85.      * @return
  86.      */
  87.     @Override
  88.     public boolean isEnabled() {
  89.         return CommonConstant.USER_STATUS_NORMAL.equals(this.getStatus()) ? true : false;
  90.     }
  91. }

 

四、匿名访问的URL通过application.yml配置

上面 WebSecurityConfig 中我们通过读取 application.yml 中的配置,允许匿名访问这些路径。

公司通常也是这样做的。

IgnoredUrlsProperties.java

  1. package com.liuyanzhao.sens.config.security;
  2. import lombok.Data;
  3. import org.springframework.boot.context.properties.ConfigurationProperties;
  4. import org.springframework.context.annotation.Configuration;
  5. import java.util.ArrayList;
  6. import java.util.List;
  7. /**
  8.  * @author 言曌
  9.  */
  10. @Data
  11. @Configuration
  12. @ConfigurationProperties(prefix = "ignored")
  13. public class IgnoredUrlsProperties {
  14.     private List<String> urls = new ArrayList<>();
  15. }

 

application.yml

  1. # 忽略鉴权url
  2. ignored:
  3.   urls:
  4.     - /editor-app/**
  5.     - /sens/act/**
  6.     - /sens/dictData/getByType/**
  7.     - /sens/email/sendResetCode
  8.     - /sens/email/resetByEmail
  9.     - /sens/file/view/**
  10.     - /sens/social/**
  11.     - /sens/ws/**
  12.     - /sens/user/regist
  13.     - /sens/user/smsLogin
  14.     - /sens/user/resetByMobile
  15.     - /sens/common/**
  16.     - /druid/**
  17.     - /swagger-ui.html
  18.     - /swagger-resources/**
  19.     - /swagger/**
  20.     - /**/v2/api-docs
  21.     - /**/*.js
  22.     - /**/*.css
  23.     - /**/*.png
  24.     - /**/*.ico
  25.     - /test/**

 

 

  • 微信
  • 交流学习,有偿服务
  • weinxin
  • 博客/Java交流群
  • 资源分享,问题解决,技术交流。群号:590480292
  • weinxin
言曌

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: