SpringBoot整合SpringSecurityOauth2实现鉴权-动态权限

守听  金牌会员 | 2022-6-23 14:14:09 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 629|帖子 629|积分 1887

写在前面
思考:为什么需要鉴权呢?
系统开发好上线后,API接口会暴露在互联网上会存在一定的安全风险,例如:爬虫、恶意访问等。因此,我们需要对非开放API接口进行用户鉴权,鉴权通过之后再允许调用。
 
准备
spring-boot:2.1.4.RELEASE
spring-security-oauth2:2.3.3.RELEASE(如果要使用源码,不要随意改动这个版本号,因为2.4往上的写法不一样了)
mysql:5.7
 
效果展示
这边只用了postman做测试,暂时未使用前端页面来对接,下个版本角色菜单权限分配的会有页面的展示
 
1、访问开放接口 http://localhost:7000/open/hello

 
 
2、不带token访问受保护接口 http://localhost:7000/admin/user/info

 
 
3、登录后获取token,带上token访问,成功返回了当前的登录用户信息

 
 
 
 
实现
oauth2一共有四种模式,这边就不做讲解了,网上搜一搜,千篇一律
因为现在只考虑做单方应用的,所以使用的是密码模式。
后面会出一篇SpringCloud+Oauth2的文章,网关鉴权
 
讲一下几个点吧
1、拦截器配置动态权限

 
新建一个 MySecurityFilter类,继承AbstractSecurityInterceptor,并实现Filter接口
 初始化,自定义访问决策管理器
  1. @PostConstruct
  2. public void init(){
  3.         super.setAuthenticationManager(authenticationManager);
  4.         super.setAccessDecisionManager(myAccessDecisionManager);
  5.   }   
复制代码
 
自定义 过滤器调用安全元数据源
  1. @Override
  2. public SecurityMetadataSource obtainSecurityMetadataSource() {
  3.     return this.mySecurityMetadataSource;
  4. }
复制代码
 
 
先来看一下自定义过滤器调用安全元数据源的核心代码
以下代码是用来获取到当前请求进来所需要的权限(角色)
 
  1. /**
  2.      * 获得当前请求所需要的角色
  3.      * @param object
  4.      * @return
  5.      * @throws IllegalArgumentException
  6.      */
  7.     @Override
  8.     public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
  9.         String requestUrl = ((FilterInvocation) object).getRequestUrl();
  10.         if (IS_CHANGE_SECURITY) {
  11.             loadResourceDefine();
  12.         }
  13.         if (requestUrl.indexOf("?") > -1) {
  14.             requestUrl = requestUrl.substring(0, requestUrl.indexOf("?"));
  15.         }
  16.         UrlPathMatcher matcher = new UrlPathMatcher();
  17.         List<Object> list = new ArrayList<>();  //无需权限的,直接返回
  18.         list.add("/oauth/**");
  19.         list.add("/open/**");
  20.         if(matcher.pathsMatchesUrl(list,requestUrl))
  21.             return null;
  22.         Set<String> roleNames = new HashSet();
  23.         for (Resc resc: resources) {
  24.             String rescUrl = resc.getResc_url();
  25.             if (matcher.pathMatchesUrl(rescUrl, requestUrl)) {
  26.                 if(resc.getParent_resc_id() != null && resc.getParent_resc_id().intValue() == 1){   //默认权限的则只要登录了,无需权限匹配都可访问
  27.                     roleNames = new HashSet();
  28.                     break;
  29.                 }
  30.                 Map map = new HashMap();
  31.                 map.put("resc_id", resc.getResc_id());
  32.                 // 获取能访问该资源的所有权限(角色)
  33.                 List<RoleRescDTO> roles = roleRescMapper.findAll(map);
  34.                 for (RoleRescDTO rr : roles)
  35.                     roleNames.add(rr.getRole_name());
  36.             }
  37.         }
  38.         Set<ConfigAttribute> configAttributes = new HashSet();
  39.         for(String roleName:roleNames)
  40.             configAttributes.add(new SecurityConfig(roleName));
  41.         log.debug("【所需的权限(角色)】:" + configAttributes);
  42.         return configAttributes;
  43.     }
复制代码
 
再来看一下自定义访问决策管理器核心代码,这段代码主要是判断当前登录用户(当前登录用户所拥有的角色会在最后一项写到)是否拥有该权限角色
  1. @Override
  2.     public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
  3.         if(configAttributes == null){   //属于白名单的,不需要权限
  4.             return;
  5.         }
  6.         Iterator<ConfigAttribute> iterator = configAttributes.iterator();
  7.         while (iterator.hasNext()){
  8.             ConfigAttribute configAttribute = iterator.next();
  9.             String needPermission = configAttribute.getAttribute();
  10.             for (GrantedAuthority ga: authentication.getAuthorities()) {
  11.                 if(needPermission.equals(ga.getAuthority())){   //有权限,可访问
  12.                     return;
  13.                 }
  14.             }
  15.         }
  16.         throw new AccessDeniedException("没有权限访问");
  17.     }
复制代码
 
2、自定义鉴权异常返回通用结果
为什么需要这个呢,如果不配置这个,对于前端,后端来说都很难去理解鉴权失败返回的内容,还不能统一解读,废话不多说,先看看不配置和配置了的返回情况
(1)未自定义前,没有携带token去访问受保护的API接口时,返回的结果是这样的
 
 (2)我们规定一下,鉴权失败的接口返回接口之后,变成下面这种了,是不是更利于我们处理和提示用户

 
 
 
好了,来看一下是在哪里去配置的吧
我们资源服务器OautyResourceConfig,重写下下面这部分的代码,来自定义鉴权异常返回的结果
大伙可以参考下这个 https://blog.csdn.net/Pastxu/article/details/124538364
  1. @Override
  2.     public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
  3.         resources.authenticationEntryPoint(authenticationEntryPoint)    //token失效或没携带token时
  4.                 .accessDeniedHandler(requestAccessDeniedHandler);   //权限不足时
  5.     }
复制代码
 

 
3、获取当前登录用户
第一种:使用JWT携带用户信息,拿到token后再解析
暂不做解释
第二种:写一个SecurityUser实现UserDetails接口(这个工程中使用的是这一种)
原来的只有UserDetails接口只有username和password,这里我们加上我们系统中的User
  1. protected User user;
  2.     public SecurityUser(User user) {
  3.         this.user = user;
  4.     }
  5.     public User getUser() {
  6.         return user;
  7.     }
复制代码
 
在BaseController,每个Controller都会继承这个的,在里面写给getUser()的方法,只要用户带了token来访问,我们可以直接获取当前登录用户的信息了
  1. protected User getUser() {
  2.         try {
  3.             SecurityUser userDetails = (SecurityUser) SecurityContextHolder.getContext().getAuthentication()
  4.                     .getPrincipal();
  5.             User user = userDetails.getUser();
  6.             log.debug("【用户:】:" + user);
  7.             return user;
  8.         } catch (Exception e) {
  9.         }
  10.         return null;
  11.     }
复制代码
 
那么用户登录成功后,如何去拿到用户的角色集合等呢,这里面就要实现UserDetailsService接口了

 
  1. @Service
  2. public class TokenUserDetailsService implements UserDetailsService{
  3.     @Autowired
  4.     private LoginService loginService;
  5.     @Override
  6.     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  7.         User user = loginService.loadUserByUsername(username);  //这个我们拎出来处理
  8.         if(Objects.isNull(user))
  9.             throw new UsernameNotFoundException("用户名不存在");
  10.         return new SecurityUser(user);
  11.     }
  12. }
复制代码
 
然后在我们的安全配置类中设置UserDetailsService为上面的我们自己写的就行
  1. @Override
  2.     protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  3.         auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
  4.     }
复制代码
 
最后我们只需要在loginService里面实现我们的方法就好,根据我们的实际业务处理判断该用户是否存在等
[code]@Override    public User loadUserByUsername(String username){        log.debug(username);        Map map = new HashMap();        map.put("username",username);        map.put("is_deleted",-1);        User user = userMapper.findByUsername(map);        if(user != null){            map = new HashMap();            map.put("user_id",user.getUser_id());            //查询用户的角色            List userRoles = userRoleMapper.findAll(map);            user.setRoles(listRoles(userRoles));            //权限集合            Collection

本帖子中包含更多资源

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

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

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

本版积分规则

守听

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表