SpringSecurity实现前后端分离登录授权详解

news2024/11/29 6:35:24

在介绍完SpringSecurity实现前后端分离认证之后,然后就是SpringSecurity授权,在阅读本文章之前可以先了解一下作者的上一篇文章SpringSecurity认证SpringSecurity实现前后端分离登录token认证详解_山河亦问安的博客-CSDN博客。

目录

1. 授权

1.1 权限系统的作用

1.2 授权基本流程

1.3 授权实现

1.3.1 限制访问资源所需权限

1.3.2 权限校验方法

1.3.3 自定义权限校验方法

1.4 自定义失败方案

1.5 代码更改


1. 授权

1.1 权限系统的作用

例如一个学校图书馆的管理系统,如果是普通学生登录就能看到借书还书相关的功能,不可能让他看到并且去使用添加书籍信息,删除书籍信息等功能。但是如果是一个图书馆管理员的账号登录了,应该就能看到并使用添加书籍信息,删除书籍信息等功能。
总结起来就是不同的用户可以使用不同的功能。这就是权限系统要去实现的效果。
我们不能只依赖前端去判断用户的权限来选择显示哪些菜单哪些按钮。因为如果只是这样,如果有人知道了对应功能的接口地址就可)不通过前端,直接去发送请求来实现相关功能操作。
所以我们还需要在后台进行用户权限的判断,判断当前用户是否有相应的权限,必须基于所需权限才能进行相应的操作。


1.2 授权基本流程

SpringSecurity中,会使用默认的FilterSecurityInterceptor来进行权限校验。在FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication,然后获取其中的权限信息。当前用户是否拥有访问当前资源所需的权限。所以我们在项目中只需要把当前登录用户的权限信息也存入Authentication。然后设置我们的资源所需要的权限即可。
 

1.3 授权实现

1.3.1 限制访问资源所需权限

SpringSecurity为我们提供了基于注解的权限控制方案,这也是我们项目中主要采用的方式。我们可以使用注解去指定访问对应的资源所需的权限。 ​ 但是要使用它我们需要先开启相关配置。我们需要在springSecurity配置类上加下面这行代码:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
}

@EnableGlobalMethodSecurity 注解参数说明:

  • prePostEnabled = true 会解锁 @PreAuthorize 和 @PostAuthorize 两个注解。顾名思义,@PreAuthorize 注解会在方法执行前进行验证,而 @PostAuthorize 注解会在方法执行后进行验证。
  • securedEnabled = true 会解锁 @Secured 注解。@Secured 是专门用于判断是否具有角色的。能写在方法或类上。参数要以 ROLE_开头。

1.3.2 权限校验方法

hasAuthority(String)

判断用户是否具有特定的权限

    @PostMapping("/test1")
    @PreAuthorize("hasAuthority('test1')")
    public String test1(){
       return "test";
    }

以上面代码为例,按Ctrl+Alt,然后点击上面的hasAuthority进入源码打断点如下图:

 利用Apipost发送请求得到的结果如下图:

 关键代码分析:

private boolean hasAnyAuthorityName(String prefix, String... roles) {
        Set<String> roleSet = this.getAuthoritySet(); //从SecurityContextHolder.getContext()中获取用户的权限集合
        String[] var4 = roles; //这是我们测试方法中hasAuthority中的权限信息test1
        int var5 = roles.length;

        for(int var6 = 0; var6 < var5; ++var6) {
            String role = var4[var6];
            String defaultedRole = getRoleWithDefaultPrefix(prefix, role); //遍历var4数组,其中的每个权限信息比如test1加上前缀,比如pre+test1,这里pre为null,如果roleset中包含这个权限信息就会返回true,该方法就会正常进行(roleSet.contains(defaultedRole)) {
                return true;
            }
        }

        return false;
    }

hasAnyAuthority(String …)

hasAnyAuthority方法可以传入多个权限,只有用户有其中任意一个权限都可以访问对应资源。

    @PostMapping("/test")
    @PreAuthorize("hasAuthority('test1','test')")
    public String tes1t(){
       return "test";
    }

 hasRole(String)

hasRole要求有对应的角色才可以访问,但是它内部会把我们传入的参数拼接上 ROLE_ 后再去比较。所以这种情况下要用用户对应的权限也要有 ROLE_ 这个前缀才可以。

   @PreAuthorize("hasRole('test')")
    public String hello(){
        return "hello";
    }

hasAnyAuthority(String …)

hasAnyRole 有任意的角色就可以访问。它内部也会把我们传入的参数拼接上 ROLE_ 后再去比较。所以这种情况下要用用户对应的权限也要有 ROLE_ 这个前缀才可以。

1.3.3 自定义权限校验方法

​ 我们也可以定义自己的权限校验方法,在@PreAuthorize注解中使用我们的方法。代码如下:

@Component("ex")
public class SGExpressionRoot {
    public boolean hasAuthority(String authority){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        MyUser myUser = (MyUser) authentication.getPrincipal();
        List<String> permissions = myUser.getTbUser().getPermissions();
        return permissions.contains(authority);
    }
}

 在SPEL表达式中使用 @ex相当于获取容器中bean对象。然后再调用这个对象的hasAuthority方法,代码如下:

    @PostMapping("/test")
    @PreAuthorize("@ex.hasAuthority('test')")
    public TbUser test(){
        TbUser tbUser = tbUserMapper.selectById(1);
        return tbUser;
    }


1.4 自定义失败方案

我们还希望在认证失败或者是授权失败的情况下也能和我们的接口一样返回相同结构的json,这样可以让前端能对响应进行统一的处理。要实现这个功能我们需要知道SpringSecurity的异常处理机制。 在SpringSecurity中,如果我们在认证或者授权的过程中出现了异常会ExceptionTranslationFilter捕获到。在ExceptionTranslationFilter中会去判断是认证失败还是授权失败出现的异常。 ​

如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理。  代码如下:

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        Result result = Result.error("401", "用户认证失败,请重新登录");
        response.setStatus(200);
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        response.getWriter().print(result.toString());
    }
}

如果是授权过程中出现的异常会被封装成AccessDeniedException,然后调用AccessDeniedHandler对象的方法去进行异常处理。 代码如下:

@Component
public class AccessDenieHandleImpl implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        Result result = Result.error("403", "用户权限不足");
        response.setStatus(200);
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        response.getWriter().print(result.toString());
    }
}


​ 所以如果我们需要自定义异常处理,我们只需要自定义AuthenticationEntryPoint和AccessDeniedHandler然后配置给SpringSecurity即可。配置代码如下:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
  
    @Autowired
    private AccessDenieHandleImpl accessDenieHandle;

    @Autowired
    private AuthenticationEntryPointImpl authenticationEntryPoint;

http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint) //配置认证失败处理器
                    .accessDeniedHandler(accessDenieHandle); //配置授权失败处理器
      http.cors() //允许跨域

    }

}

1.5 代码更改

在认证的基础上添加授权,为了方便这里的权限信息设计写死,在真正的项目中每个用户的权限应该从数据库中进行查询。代码设计如下:

 JwtAuthenticationTokenFilter类代码如下:
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    RedisTemplate redisTemplate;

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        //获取请求头中的token
        String token = httpServletRequest.getHeader("token");
        //如果token为空直接放行,由于用户信息没有存放在SecurityContextHolder.getContext()中所以后面的过滤器依旧认证失败符合要求
        if(!StringUtils.hasText(token)){
            filterChain.doFilter(httpServletRequest,httpServletResponse);
            return;
        }
        Long userId;
        try {
            //通过jwt工具类解析token获得userId,如果token过期或非法就会抛异常
            DecodedJWT decodedJWT = JwtUtil.decodeToken(token);
            userId = decodedJWT.getClaim("userId").asLong();
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException("token非法");
        }
        //根据userId从redis中获取用户信息,如果没有该用户就代表该用户没有登录过
        TbUser myUser = (TbUser) redisTemplate.opsForValue().get(String.valueOf(userId));
        if(Objects.isNull(myUser)){
            throw new RuntimeException("用户未登录");
        }
        MyUser myUser1=new MyUser(myUser);
        //将用户信息存放在SecurityContextHolder.getContext(),后面的过滤器就可以获得用户信息了。
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(myUser1,null,myUser1.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }
}
UserDetailServiceImpl类
@Service
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    TbUserMapper tbUserMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<TbUser> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("username",username);
        TbUser tbUser = tbUserMapper.selectOne(queryWrapper);
        List<String> list = Arrays.asList("test");
        tbUser.setPermissions(list);
        if(tbUser==null){
            throw new RuntimeException("用户名或者密码错误");
        }
        return new MyUser(tbUser);
    }
}

至此SpringSecurity实现前后端分离token验证认证授权就此结束。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/646387.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

线程间的通信机制与生产者消费者案例

线程间的通信机制与生产者消费者案例 线程间通信 为什么要处理线程间通信&#xff1a; 当我们需要多个线程来共同完成一件任务&#xff0c;并且我们希望他们有规律的执行&#xff0c;那么多线程之间需要一些通信机制&#xff0c;可以协调他们的工作&#xff0c;以此实现多线…

中空百叶玻璃隔断怎么组装

以下是中空百叶玻璃隔断的组装步骤&#xff1a; 1. 准备材料&#xff1a;中空百叶玻璃、接头、U型槽、挂件、固定螺钉等。 2. 根据实际需要&#xff0c;将中空百叶玻璃按照尺寸进行切割。 3. 在地面上铺上一张软垫&#xff0c;将切好的玻璃放置在垫子上&#xff0c;然后在两侧标…

自动化部署工具 drone 部署文档 (使用于 vue java 部署 其他自行研究)

1.准备好docker环境 drone 官网 Drone CI – Automate Software Testing and Delivery 推荐使用宝塔傻瓜式一键安装宝塔面板 - 简单好用的Linux/Windows服务器运维管理面板 安装好docker环境之后开始部署容器 这里用到的git版本软件是gogs,其他自己研究 docker版的gogs和实…

Linux内存管理 —— 文件系统缓存和匿名页的交换

转自&#xff1a;https://www.cnblogs.com/wangshaowei/p/14089132.html 文件页 内存回收&#xff0c;也就是系统释放掉可以回收的内存&#xff0c;比如缓存和缓冲区&#xff0c;就属于可回收内存。它们在内存管理中&#xff0c;通常被叫做文件页&#xff08;File-backed Pag…

B045-jQuery案例实战BootStrap

目录 一、BootStrap概述二、BootStrap使用1.起步1.下载或放入BootStrap文件到本地工程里2.在html代码中引入1个css文件和两个js文件3.复制模板代码到本地html文件里会自动应用成bootStrap图样中的效果0.实战案例-环境准备0.实战案例-基本模板 2.布局容器3.栅格系统4.常用组件表…

echarts里type为custom,自定义配置renderItem为柱状形状

echarts里type为custom&#xff0c;自定义配置renderItem为柱状形状 echarts里type为custom&#xff0c;自定义配置renderItem为柱状形状 echarts里type为custom&#xff0c;自定义配置renderItem为柱状形状 主要功能&#xff1a;文本超过柱状形状就隐藏否则就显示&#xff0c;…

风口上的AIGC,技术人才动不动就年薪百万?

2023年&#xff0c;职场人都在讨论什么&#xff1f; 自今年3月以来&#xff0c;随着ChatGPT应用持续走俏&#xff0c;AIGC领域抢人大战盛况空前。随之而来的便是“AI取代人类”“10亿打工人被革命”&#xff0c;AI的发展速度和步伐&#xff0c;超乎我们预期&#xff0c;也影响…

MySQL - 第0节 - MySQL在Centos 7环境安装

1.安装前说明 • 安装与卸载过程中&#xff0c;用户全部切换成为root&#xff0c;一旦安装&#xff0c;普通用户也能够使用。 • 初期练习&#xff0c;mysql不进行用户管理&#xff0c;全部使用root进行&#xff0c;后面学了用户管理&#xff0c;再考虑新建普通用户。 注&#…

互联网医院资质申请条件|互联网医院申请流程

互联网医院是医疗行业应用互联网技术的一种创新形态&#xff0c;它为患者提供便捷的医疗服务&#xff0c;改善了医疗资源的不足和分散情况。在中国&#xff0c;互联网医院的发展也越来越受到重视&#xff0c;互联网医院牌照的申请流程和需要的资料也成为了关注的焦点。 一、互…

SpringCloud 中各微服务使用 springcloud-config 获取配置文件时,配置信息无法实时刷新

文章目录 1、引入actuator监控2、修改 yml3、添加注解 RefreshScope4、业务操作5、发送通知&#xff0c;即可生效6、思考 使用 springcloud-config 作为 服务配置 管理时&#xff0c; 当对 git 上的配置进行修改后&#xff0c; 各微服务客户端 配置无法 实时刷新&#xff0c;需…

【Flutter】Flutter 切换语言 当前页面不刷新

文章目录 一、前言二、Flutter 多语言支持的基础知识三、如何在 Flutter 中设置语言四、如何在 Flutter 中切换语言五、代码示例六、完整代码七、总结 一、前言 大家好&#xff0c;今天我们要探讨的主题是如何在 Flutter 中切换语言&#xff0c;而不需要刷新当前页面。这是一个…

vue-element-admin - 最新完美解决项目是英文的问题,将英文变成中文的汉化处理详细教程(克隆完项目后不是中文的解决方法)

前言 网上的教程(并且都是好几年前的了)克隆运行后界面文字全都是英文的,如果您想要中文汉语版本请使用本文的方案。 本文 解决了克隆运行 vue-element-admin 项目后,默认是英文的问题!将语言设置为中文的详细教程! 官方文档说的很晦涩,您可以按照本教程步骤进行操作即…

log4j配置说明

目录&#xff1a; 1、 引入jar包使用 2、 使用 3、 配置文件 4、 配置 5、 说明 说明&#xff1a;2015年9月&#xff0c;Apache软件基金业宣布&#xff0c;Log4j不在维护&#xff0c;建议所有相关项目升级到Log4j2。 1.引入jar包使用 <dependency><groupI…

vscode 调试

目录 准备 GDB 调试方法 问题 准备 然后点击 文件-打开文件夹&#xff0c;找到创建的代码路径&#xff0c;确定后&#xff0c;在左侧的资源管理器可以看到代码文件。 第一次运行需要安装 c 的扩展&#xff0c;在扩展页面中&#xff0c;安装 C/C 编译注意一定要加上 -g 指令…

SpringBoot项目配置方式及优先级

说明&#xff1a;SpringBoot支持以下五种方式配置方式&#xff0c;例如将项目的Tomcat端口从8080&#xff0c;更改为9000&#xff0c;可以使用如下方式配置 【方式一】命令行参数 在启动窗口&#xff0c;鼠标右键&#xff0c;选择“Edit Configurations”&#xff0c;在弹出来…

开会糟透了?试试无声会议吧

引言 无声会议(Silent Meeting) 是一种已被成功实践但却鲜有公开资料的开会方法&#xff0c;本文会探寻下它的有趣历史&#xff0c;并介绍如何通过它的小小改变让会议变得不再糟糕。 文档是无声会议的核心基础&#xff0c;本文会引出 Coda 和 Mojidoc(妙记多) 这两款让你的会…

泛微数字化国有资产管理平台,以智能增效能

2021年国务院发布的《行政事业性国有资产管理条例》中要求应当建立健全行政事业性国有资产管理机制&#xff0c;加强对本级行政事业性国有资产的管理。 随着数字化转型的深入&#xff0c;越来越多的行政事业单位、国有企业运用数字化的理念、思路、方法&#xff0c;以智能增效…

AI工具助力创意与写作:19岁小伙每月赚3万,学会这些你也能轻松做到!

导语&#xff1a;在数字时代&#xff0c;人工智能为我们提供了许多创新的工具和资源&#xff0c;助力我们在各个领域实现创意和写作的目标。本文将介绍五款热门的AI工具&#xff1a;Copyai、Flikiai、Notion、TomeAl和Writesonic&#xff0c;它们分别在写作、视频配音、文本编辑…

Java调用Midjourney进行AI画图原生版抓包实现支持中文

用途介绍 Midjourney是一个目前优秀的AI画图工具&#xff0c;不挂梯无法直接访问 本代码主要用于搭建镜像站使用 适合人群 本代码不适合新手&#xff0c;建议使用过okhttp、且具有二开能力的同学使用~ 实现原理 通过调用发送信息接口发送请求&#xff0c;通过轮询房间消息…

【2023最全教程】Web自动化测试怎么做?Web自动化测试的详细流程和步骤

一、什么是web自动化测试 自动化&#xff08;Automation&#xff09;是指机器设备、系统或过程&#xff08;生产、管理过程&#xff09;在没有人或较少人的直接参与下&#xff0c;按照人的要求&#xff0c;经过自动检测、信息处理、分析判断、操纵控制&#xff0c;实现预期的目…