码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • SpringSecurityOauth实现鉴权-动态权限


    写在前面

    思考:为什么需要鉴权呢?

    系统开发好上线后,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一共有四种模式,这边就不做讲解了
    因为现在只考虑做单方应用的,所以使用的是密码模式。

    讲一下几个点吧

    1、拦截器配置动态权限

    新建一个 MySecurityFilter类,继承AbstractSecurityInterceptor,并实现Filter接口

    初始化,自定义访问决策管理器

    @PostConstruct public void init(){        super.setAuthenticationManager(authenticationManager);        super.setAccessDecisionManager(myAccessDecisionManager);  }   
    

    自定义 过滤器调用安全元数据源

    @Overridepublic SecurityMetadataSource obtainSecurityMetadataSource() {    return this.mySecurityMetadataSource;}
    

    先来看一下自定义过滤器调用安全元数据源的核心代码

    以下代码是用来获取到当前请求进来所需要的权限(角色)

    /**     * 获得当前请求所需要的角色     * @param object     * @return     * @throws IllegalArgumentException     */    @Override    public Collection getAttributes(Object object) throws IllegalArgumentException {        String requestUrl = ((FilterInvocation) object).getRequestUrl();        if (IS_CHANGE_SECURITY) {            loadResourceDefine();        }        if (requestUrl.indexOf("?") > -1) {            requestUrl = requestUrl.substring(0, requestUrl.indexOf("?"));        }        UrlPathMatcher matcher = new UrlPathMatcher();        List list = new ArrayList<>();  //无需权限的,直接返回        list.add("/oauth/**");        list.add("/open/**");        if(matcher.pathsMatchesUrl(list,requestUrl))            return null;        Set roleNames = new HashSet();        for (Resc resc: resources) {            String rescUrl = resc.getResc_url();            if (matcher.pathMatchesUrl(rescUrl, requestUrl)) {                if(resc.getParent_resc_id() != null && resc.getParent_resc_id().intValue() == 1){   //默认权限的则只要登录了,无需权限匹配都可访问                    roleNames = new HashSet();                    break;                }                Map map = new HashMap();                map.put("resc_id", resc.getResc_id());                // 获取能访问该资源的所有权限(角色)                List roles = roleRescMapper.findAll(map);                for (RoleRescDTO rr : roles)                    roleNames.add(rr.getRole_name());            }        }        Set configAttributes = new HashSet();        for(String roleName:roleNames)            configAttributes.add(new SecurityConfig(roleName));        log.debug("【所需的权限(角色)】:" + configAttributes);        return configAttributes;    }
    

    再来看一下自定义访问决策管理器核心代码,这段代码主要是判断当前登录用户(当前登录用户所拥有的角色会在最后一项写到)是否拥有该权限角色

    @Override    public void decide(Authentication authentication, Object o, Collection configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {        if(configAttributes == null){   //属于白名单的,不需要权限            return;        }        Iterator iterator = configAttributes.iterator();        while (iterator.hasNext()){            ConfigAttribute configAttribute = iterator.next();            String needPermission = configAttribute.getAttribute();            for (GrantedAuthority ga: authentication.getAuthorities()) {                if(needPermission.equals(ga.getAuthority())){   //有权限,可访问                    return;                }            }        }        throw new AccessDeniedException("没有权限访问");    }
    

    2、自定义鉴权异常返回通用结果

    为什么需要这个呢,如果不配置这个,对于前端,后端来说都很难去理解鉴权失败返回的内容,还不能统一解读,废话不多说,先看看不配置和配置了的返回情况

    (1)未自定义前,没有携带token去访问受保护的API接口时,返回的结果是这样的

    (2)我们规定一下,鉴权失败的接口返回接口之后,变成下面这种了,是不是更利于我们处理和提示用户

    好了,来看一下是在哪里去配置的吧

    我们资源服务器OautyResourceConfig,重写下下面这部分的代码,来自定义鉴权异常返回的结果

    @Override    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {        resources.authenticationEntryPoint(authenticationEntryPoint)    //token失效或没携带token时                .accessDeniedHandler(requestAccessDeniedHandler);   //权限不足时    }
    

    3、获取当前登录用户

    第一种:使用JWT携带用户信息,拿到token后再解析

    暂不做解释

    第二种:写一个SecurityUser实现UserDetails接口(这个工程中使用的是这一种)

    原来的只有UserDetails接口只有username和password,这里我们加上我们系统中的User

    protected User user;    public SecurityUser(User user) {        this.user = user;    }    public User getUser() {        return user;    }
    

    在BaseController,每个Controller都会继承这个的,在里面写给getUser()的方法,只要用户带了token来访问,我们可以直接获取当前登录用户的信息了

    protected User getUser() {        try {            SecurityUser userDetails = (SecurityUser) SecurityContextHolder.getContext().getAuthentication()                    .getPrincipal();            User user = userDetails.getUser();            log.debug("【用户:】:" + user);            return user;        } catch (Exception e) {        }        return null;    }
    

    那么用户登录成功后,如何去拿到用户的角色集合等呢,这里面就要实现UserDetailsService接口了

    @Servicepublic class TokenUserDetailsService implements UserDetailsService{    @Autowired    private LoginService loginService;    @Override    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {        User user = loginService.loadUserByUsername(username);  //这个我们拎出来处理        if(Objects.isNull(user))            throw new UsernameNotFoundException("用户名不存在");        return new SecurityUser(user);    }}
    

    然后在我们的安全配置类中设置UserDetailsService为上面的我们自己写的就行

    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception {        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());    }
    

    最后我们只需要在loginService里面实现我们的方法就好,根据我们的实际业务处理判断该用户是否存在等

    @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 authorities = merge(userRoles);            user.setAuthorities(authorities);            return user;        }        return null;    }
    

    大功告成啦,赶紧动起手来吧!

  • 相关阅读:
    iOS代码混淆教程
    Redis网络模型
    力扣(LeetCode)106. 从中序与后序遍历序列构造二叉树(C++)
    MMDeploy部署实战系列【第五章】:Windows下Release x64编译mmdeploy c++SDK,对TensorRT模型进行推理
    Spring AOP原来是这样实现的
    【Java】数组定义和访问及数组原理内存图
    【JavaEE基础与高级 第56章】Java中的打印流、属性集、IO流异常的处理详细使用介绍
    超算基础概念
    格式化之 %d,%2d, %02d
    台达PLC出现通信错误或通信超时或下载时提示机种不符的解决办法总结
  • 原文地址:https://blog.csdn.net/weixin_62421895/article/details/126280292
    • 最新文章
    • 攻防演习之三天拿下官网站群
      数据安全治理学习——前期安全规划和安全管理体系建设
      企业安全 | 企业内一次钓鱼演练准备过程
      内网渗透测试 | Kerberos协议及其部分攻击手法
      0day的产生 | 不懂代码的"代码审计"
      安装scrcpy-client模块av模块异常,环境问题解决方案
      leetcode hot100【LeetCode 279. 完全平方数】java实现
      OpenWrt下安装Mosquitto
      AnatoMask论文汇总
      【AI日记】24.11.01 LangChain、openai api和github copilot
    • 热门文章
    • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
      奉劝各位学弟学妹们,该打造你的技术影响力了!
      五年了,我在 CSDN 的两个一百万。
      Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
      面试官都震惊,你这网络基础可以啊!
      你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
      心情不好的时候,用 Python 画棵樱花树送给自己吧
      通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
      13 万字 C 语言从入门到精通保姆级教程2021 年版
      10行代码集2000张美女图,Python爬虫120例,再上征途
    Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
    正则表达式工具 cron表达式工具 密码生成工具

    京公网安备 11010502049817号