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

在介绍完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进入源码打断点如下图:

9bd7d37f1bc5429294e1e1a6cbf52c1f.png

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

96929de8dfc44b19bd00ca7a701346d1.png

 关键代码分析:

private boolean hasAnyAuthorityName(String prefix, String... roles) {Set<String> roleSet = this.getAuthoritySet(); //从SecurityContextHolder.getContext()中获取用户的权限集合String[] var4 = roles; //这是我们测试方法中hasAuthority中的权限信息test1int 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 {@Overridepublic 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 {@Overridepublic 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 {@Autowiredprivate AccessDenieHandleImpl accessDenieHandle;@Autowiredprivate AuthenticationEntryPointImpl authenticationEntryPoint;http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint) //配置认证失败处理器.accessDeniedHandler(accessDenieHandle); //配置授权失败处理器http.cors() //允许跨域}}

1.5 代码更改

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

   

MyUser类代码如下:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MyUser implements UserDetails, Serializable {private TbUser tbuser;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {List<SimpleGrantedAuthority> collect = tbuser.getPermissions().stream().map(SimpleGrantedAuthority::new).distinct().collect(Collectors.toList());return collect;}@Overridepublic String getPassword() {return sysUser.getPassword();}@Overridepublic String getUsername() {return sysUser.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

 JwtAuthenticationTokenFilter类代码如下:
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@AutowiredRedisTemplate redisTemplate;@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {//获取请求头中的tokenString 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 {@AutowiredTbUserMapper tbUserMapper;@Overridepublic 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验证认证授权就此结束。

c5e3c06a3245493aa030b1369212cca1.gif

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ERR! code ERR_SOCKET_TIMEOUT

问题 安装npm包&#xff0c;终端报错ERR! code ERR_SOCKET_TIMEOUT ERR! code ERR_SOCKET_TIMEOUT详细问题 笔者运行以下命令重新安装依赖项&#xff1a; npm install控制台报错&#xff0c;具体报错信息如下 npm ERR! code ERR_SOCKET_TIMEOUT npm ERR! network Socket t…

360手机刷机 360手机Xposed框架安装 360手机EdXposed、LSP 360手机xposed模块

360手机刷机 360手机Xposed框架安装 360手机EdXposed、LSP 360手机xposed模块 参考&#xff1a;360手机-360刷机360刷机包twrp、root 360刷机包360手机刷机&#xff1a;360rom.github.io 【前言】 手机须Twrp或root后&#xff0c;才可使用与操作Xposed安装后&#xff0c;重启…

Spring Boot 中的 @Transactional 注解是什么,原理,如何使用

Spring Boot 中的 Transactional 注解是什么&#xff0c;原理&#xff0c;如何使用 简介 在 Spring Boot 中&#xff0c;Transactional 注解是非常重要的一个注解&#xff0c;用于实现数据库事务的管理。通过使用 Transactional 注解&#xff0c;我们可以很方便地对事务进行控…

java + opencv对比图片不同

1&#xff0c;去官网下载opencv&#xff0c;下载的时候需要注册一个 Oracle 账户&#xff0c;分分钟就能注册。然后安装。我下的是4.7的。 2&#xff0c;找到jar包放进项目里 3&#xff0c;项目结构&#xff0c;比较简单 4&#xff0c;把下载的文件放进C盘 5&#xff0c;主类代…

GitLab无法提交大文件的问题

GitLab无法提交大文件的问题 问题描述 GitLab 当提交大文件时遇到如下报错 MYOPS001MYOPS001 MINGW64 /e/work/GitLab/system-deploy (main) $ git push Enumerating objects: 91, done. Counting objects: 100% (91/91), done. Delta compression using up to 16 threads C…

西安石油大学期末考试C++真题解析

1、一、类型、返回值类型 二、参数表、函数重载 2、一、实例化 二、实例化的类型或类类是对象的蓝图&#xff0c;对象是类的实例化 3、const 4、一个 两个 5、一、公有继承 二、私有继承、保护继承 6、抽象类、实例化对象 7、函数模板、类模板 8、try、catch、throw 9、…

selenium之鼠标操作

首先导入ActionChains类&#xff0c;该类可以完成鼠标移动&#xff0c;鼠标点击事件&#xff0c;键盘输入、内容菜单交互等交互行为。 from selenium.webdriver.common.action_chains import ActionChains 操作语法&#xff1a; 第一步&#xff1a;初始化ActionChains类&…

python【爬虫】【批量下载】年报抓取

python年报爬取更新 本人测试发现&#xff0c;ju chao网的年报爬取距离我上一篇博客并没有啥变化&#xff0c;逻辑没变&#xff0c;应好多朋友的需要&#xff0c;这里补充代码 import json import osimport requestsweb_url 改成网站的域名&#xff0c;因为csdn屏蔽 def load…

HOT20-旋转图像

leetcode原题链接&#xff1a;旋转图像 题目描述 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像&#xff0c;这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 示例 1&#xff1a; 输入&am…

Android JetPack 深入分析ViewModel源码

文章目录 前言源码分析ViewModel是如何创建的&#xff1f;ViewModelProvider(this)做了什么&#xff1f;小结 get(MyViewModel::class.java)做了什么&#xff1f;小结 ViewModel是如何实现配置更改后数据恢复的&#xff1f;整体时序图 结语 前言 本文主要分析ViewModel相关源码…

JUC之LockSupport和中断

文章目录 1 线程中断机制1.1 什么是线程中断机制1.2 三大中断方法1.3 如何中断运行中的线程1.3.1 通过volatile变量实现1.3.1 通过AtomicBoolean实现1.3.1 通过interrupt和isInterrupted api实现 2 LockSupport2.1 为什么会出现LockSupport2.2 两道面试题 参考材料 1 线程中断机…