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

写在前面

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

系统开发好上线后,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

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

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

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

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

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

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

实现

oauth2一共有四种模式,这边就不做讲解了,网上搜一搜,千篇一律

因为现在只考虑做单方应用的,所以使用的是密码模式。

后面会出一篇SpringCloud+Oauth2的文章,网关鉴权

讲一下几个点吧

1、拦截器配置动态权限

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

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

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

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

@Override
public 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)我们规定一下,鉴权失败的接口返回接口之后,变成下面这种了,是不是更利于我们处理和提示用户

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

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

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

大伙可以参考下这个 https://blog.csdn.net/Pastxu/article/details/124538364

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

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

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接口了

SpringBoot整合SpringSecurityOauth2实现鉴权-动态权限
@Service
public 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为上面的我们自己写的就行

SpringBoot整合SpringSecurityOauth2实现鉴权-动态权限
@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));
            //权限集合
            Collectionextends GrantedAuthority> authorities = merge(userRoles);
            user.setAuthorities(authorities);
            return user;
        }
        return null;

    }

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

附上源码地址:https://gitee.com/jae_1995/spring-boot-oauth2

数据库文件在这

点个小赞呗

Original: https://www.cnblogs.com/jae-tech/p/16381881.html
Author: Jae1995
Title: SpringBoot整合SpringSecurityOauth2实现鉴权-动态权限

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/621598/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Spring依赖循环

    Spring依赖循环 DefaultSingletonBeanRegistry源码 源码注释: public class DefaultSingletonBeanRegistry …

    Java 2023年5月30日
    067
  • (转)CUDA软件架构—网格(Grid)、线程块(Block)和线程(Thread)的组织关系以及线程索引的计算公式

    网格(Grid)、线程块(Block)和线程(Thread)的最大数量 CUDA中可以创建的网格数量跟GPU的计算能力有关,可创建的Grid、Block和Thread的最大数量参看…

    Java 2023年5月29日
    064
  • java面试——垃圾回收机制

    垃圾回收机制:——GC 初学java时。最经典的一句话是”java不像C,需要担心处理不用的内存,他有自己的垃圾回收,会自己处理的”,这是当时老师上课提过的…

    Java 2023年6月9日
    094
  • Spring 常见面试题总结 | JavaGuide

    首发于 JavaGuide 在线网站:Spring 常见面试题总结 最近在对 JavaGuide 的内容进行重构完善,同步一下最新更新,希望能够帮助你。 Spring 基础 什么是…

    Java 2023年6月9日
    068
  • 【SpringBoot】使用属性文件给成员变量配置值对final成员变量是无效的,只对普通成员有效

    【结论】 使用属性文件给成员变量配置值对final成员变量是无效的,只对普通成员有效。 【证明】 待注入类Test: 属性文件application.properties: 测试类…

    Java 2023年5月29日
    069
  • 删除重复值的结点

    删除重复值的结点 问题重述: 给定一个无序单链表的头节点head,删除其中值重复出现的结点 问题分析: 这道题要删除重复值的结点,我们可以想到哈希表,因为哈希表是无序不重复的,我们…

    Java 2023年6月7日
    052
  • 使用Swing的GUI编程

    Swing AWT:抽象窗口工具包,提供了一套与本地图形界面进行交互的接口,是Java提供的用来建立和设置Java的图形用户界面的基本工具 Swing以AWT为基础的,尽管Swin…

    Java 2023年6月6日
    058
  • Linux下mysql的彻底卸载

    1、查看mysql的安装情况 rpm -qa | grep -i mysql 2、删除上图安装的软件 rpm -ev mysql-community-libs-5.7.27-1.e…

    Java 2023年6月7日
    075
  • Java NIO全面详解(看这篇就够了)

    很多技术框架都使用NIO技术,学习和掌握Java NIO技术对于高性能、高并发网络的应用是非常关键的@mikechen NIO简介 NIO 中的 N 可以理解为 Non-block…

    Java 2023年6月15日
    074
  • 第十六届全国大学生智能汽车竞赛电磁越野组参赛技术总结

    第十六届全国大学生智能汽车竞赛电磁越野组参赛技术总结 写在前面 前期准备 * 熟悉开发库 机械结构 技术报告 中期搭建 * 滤波归一 – 中值滤波 均值滤波 归一化 传…

    Java 2023年6月9日
    0104
  • flink dataStream count

    背景: 批(有界流)模式下,需要统计处理总数据条数如果使用 DataSetApi,可通过 .count()直接获取:本文主要描述 DataStream如何获取总条数 思路: 当 D…

    Java 2023年6月16日
    084
  • Spring核心之Ioc容器

    spring框架 Spring框架是由于软件开发的复杂性而创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅仅限于服…

    Java 2023年6月5日
    068
  • JAVA方法中的参数用final来修饰的原因

    JAVA方法中的参数用final来修饰的原因 很多人都说在JAVA中用final来修饰方法参数的原因是防止方法参数在调用时被篡改,其实也就是这个原因,但理解起来可能会有歧义,有的人…

    Java 2023年5月29日
    068
  • (转) MySQL中的意向锁

    详解 MySql InnoDB 中意向锁的作用 posted on2022-09-29 21:54 茶倌 阅读(9 ) 评论() 编辑 Original: https://www….

    Java 2023年6月8日
    078
  • Dijkstra算法(三)之 Java详解

    迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个节点到其他节点的最短路径。它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止。 基本…

    Java 2023年5月29日
    076
  • 基础知识,不是很了解的

    Java中方法参数传递方式是按值传递。对于基本类型(int a, long b),参数传递时传递的是值,例如int a = 5,传递的就是5。如果是引用类型,传递是指向具体对象内存…

    Java 2023年6月14日
    079
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球