【第2期】Springboot如何快速集成SpringSecurity

简单介绍

本专栏主要结合实战讲解,不过多介绍细节的概念,概念可以通过搜索引擎查找,一搜一大把,切入正题。
本专栏的实战项目是基于Springboot+SpringSecurity+RSA+JWT+VUE的全栈开发项目,每个环节都会专门讲,本期讲如何集成SpringSecurity

  • 主要讲解一下部分:

1、基于RBAC的权限系统
2、SpringSecurity核心安全配置
3、登录过滤器
4、权限校验过滤器
5、默认的登录接口

一、基于RBAC的权限系统

设计以下表,用于管理维护用户的角色和权限(表的详细设计可见第1期)

common_user: 用户表,系统的用户都存在这张表里
common_role:角色表,用于表示系统有哪些角色
common_permission:权限表,也可以理解为资源,用于表示系统中所有的资源权限,粒度可大可小
common_user_role:用户和角色的关联表,表示某个用户拥有哪几种角色
common_role_permission:角色和权限的关联表,表示某个角色拥有哪些资源的访问权限

SpringSecurity可以基于角色进行粗粒度的权限控制,也可以基于权限进行细粒度的权限控制,还可以讲二者混合使用进行复杂的权限控制

二、SpringSecurity核心安全配置

1、创建auth模块

在这里插入图片描述

2、添加SpringSecurity的依赖

这里没有指定版本号,是因为在root的pom.xml中定义了依赖管理,统一进行版本号的管理,这是常规操作

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>

3、创建一个SpringSecurity自定义配置类

public class WebSecurityConfig extends WebSecurityConfigurerAdapter{}
核心配置如下:

http.csrf:禁用跨站请求伪造。从 Spring Security4 开始 CSRF 防护默认开启。默认会拦截请求。进行 CSRF 处理。CSRF 为了保证不是其他第三方网站访问,要求访问时携带参数名为_csrf 值为 token(token 在服务端产生)的内容,如果token 和服务端的 token 匹配成功,则正常访问。
anthorizeRequests():表示后面的资源通过认证即可访问
antMatchers(“xxxx”).permitAll():SpringSecurity允许这类资源被所有人访问
addFilter:添加自定义的过滤器,这里是添加了登录过滤器和权限认证过滤器
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS):关闭session管理,前后端分离不涉及session,所以关闭
在这里插入图片描述
如上图,注释掉的几行permitAll如果放开之后,swagger页面可以打开吗?
答案是:如果没有下面的过滤器就可以打开,如果有,那就无法打开,因为会被权限认证过滤器拦截,所以这里配置了上述白名单资源无效

4、有效配置绕过双重认证的白名单

双重认证:SpringSeurity认证+JWT Token认证(这里不展开讲,后续会讲)

  • 如何绕过双重认证:在当前的安全配置类,重写configure(WebSecurity web)方法,将下面的白名单资源和接口设置为忽略认证即可
    在这里插入图片描述

三、登录过滤器

1、创建过滤器,继承UsernamePasswordAuthenticationFilter

public class UserLoginFilter extends UsernamePasswordAuthenticationFilter {
}

2、重写登录成功处理方法

这里可按照自己业务逻辑去实现登录成功以后的逻辑。
这里的逻辑是:

1、获取当前登录成功的用户
2、根据RSA私钥以及用户信息生成JWT Token,有效期设置为24小时(关于RSA安全加解密和JWT的生成和反序列化为用户信息后续为展开讲)
3、将登录用户信息写入Redis缓存,过期时间与JWT时间保持一致
4、将token放入响应header
5、组装接口响应体并响应给接口调用方

在这里插入图片描述
疑问:可能有人发现了,用户信息从哪里来的,和数据库的common_user如何关联得起来?

3、登录的用户信息从哪里来

  • 用户持久化对象实现SpringSecurity的UserDetails接口
    此接口用于获取用户的关键信息,如账号、密码、权限集合、是否没过期、是否没被锁定、是否认证没过期等等,这些逻辑可以按照业务重写
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class UserPo extends BaseEntity<Long> implements UserDetails {private String loginName;private String password;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")@JsonSerialize(using = LocalDateTimeSerializer.class)@JsonDeserialize(using = LocalDateTimeDeserializer.class)private LocalDateTime loginExpireTime;private LoginStatusEnum status;private String phone;@JsonFormat(pattern = "yyyy-MM-dd")@JsonSerialize(using = LocalDateSerializer.class)@JsonDeserialize(using = LocalDateDeserializer.class)private LocalDate born;private Integer failCount;private List<RolePo> roles;private List<PermissionPo> permissions;@JsonIgnore@Overridepublic Collection<GrantedAuthority> getAuthorities() {// 保存的角色要加上ROLE_,接口配置角色时不带ROLE_List<GrantedAuthority> authorities = Lists.newArrayList();if (roles != null) {for (RolePo rolePo : roles) {authorities.add(new SimpleGrantedAuthority("ROLE_" + rolePo.getName()));}}if (permissions != null) {for (PermissionPo perm : permissions) {authorities.add(new SimpleGrantedAuthority(perm.getName()));}}return authorities;}@Overridepublic String getPassword() {return this.password;}@JsonProperty(value = "loginName")@Overridepublic String getUsername() {return this.loginName;}@JsonIgnore@Overridepublic boolean isAccountNonExpired() {return true;}@JsonIgnore@Overridepublic boolean isAccountNonLocked() {return true;}@JsonIgnore@Overridepublic boolean isCredentialsNonExpired() {return true;}@JsonIgnore@Overridepublic boolean isEnabled() {return true;}
  • 数据库的用户信息从哪里来呢,请看用户接口及实现类
public interface UserService extends BaseService<UserPo>, UserDetailsService {
}
@Service
public class UserServiceImpl extends BaseServiceImpl<UserPo> implements UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate RoleService roleService;@AutowiredPermissionService permissionService;@Overridepublic BaseMapper<UserPo> getMapper() {return userMapper;}@Overridepublic UserDetails loadUserByUsername(String loginName) throws UsernameNotFoundException {UserPo userPo = userMapper.selectByLoginName(loginName);if (userPo == null) {throw new ForbiddenException("用户不存在");}List<RolePo> roles = roleService.queryRolesByUserId(userPo.getId());List<PermissionPo> permissions = permissionService.queryPermissionsByUserId(userPo.getId());userPo.setRoles(roles);userPo.setPermissions(permissions);return userPo;}}

从上面代码可以看出来,用户信息是通过UserDetailsService接口的loadUserByUsername加载的,而参数loginName就是登录传入的用户名
这里不仅查询了用户信息,还查询了用户的角色和权限,用于接下来的权限校验过滤器校验请求的合法性

4、登录失败处理

如果账号密码不正确,登录失败,构造无权的响应即可

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {UserPo sysUser = null;try {sysUser = new ObjectMapper().readValue(request.getInputStream(), UserPo.class);UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword());return authenticationManager.authenticate(authRequest);} catch (Exception exception) {try {log.error("用户:{}登录出现异常:{}", sysUser == null ? "未知" : sysUser.getLoginName(), exception.getMessage(),exception);response.setContentType("application/json;charset=utf-8");response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);PrintWriter out = response.getWriter();Response<String> denied = ResponseResult.denied("用户名或密码错误!");out.write(new ObjectMapper().writeValueAsString(denied));out.flush();out.close();} catch (Exception outEx) {outEx.printStackTrace();}}return null;}

四、权限校验过滤器

创建权限校验过滤器类,继承BasicAuthenticationFilter过滤器类

public class TokenVerifyFilter extends BasicAuthenticationFilter {
}
在这里插入图片描述

核心逻辑说明:

  • 1、从请求头获取token
String xAuthToken = request.getHeader(Const.Header.AUTH_KEY);
  • 2、token没传的话,交给后续其他过滤器处理(如果有的话),最后构造重新登录的响应
if (xAuthToken == null) {//没有携带token,则给用户提示请登录!chain.doFilter(request, response);this.responseReLogin(response);return;
}
  • 3、通过token从redis获取用户信息
String userInfo = redisClient.get(Const.Header.AUTH_KEY + ":" + xAuthToken);
  • 4、如果用户信息没获取到,那代表用户登录的时效过了,需要重新登录
if (StringUtils.isBlank(userInfo)) {this.responseReLogin(response);return;
}
  • 5、从jwt token中反序列化出token中的载荷信息,jwt的组成部分可以搜一下,这里传的是公钥
Payload<UserPo> payload = JwtUtils.getInfoFromToken(xAuthToken, prop.getPublicKey(), UserPo.class);
  • 6、从载荷中获取用户信息并使用用户名和用户的权限构造认证信息传给SpringSecurity进行权限的认证
UsernamePasswordAuthenticationToken authResult = new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);

五、默认的登录接口

SpringSecurity默认提供了http://127.0.0.1:8080/login的登录接口,所以很多新手包括本人最初也是困扰了很久,登录接口没有却能处理登录,最终登录的逻辑还是在登录的过滤器中。
包括退出登录接口,SpringSecurity也是提供了,如果要自定义退出的逻辑,安全设置中禁用退出即可,自定义退出登录逻辑

http.logout().disable()

自定义退出

    @Operation(tags = "用户退出")@PostMapping("/api/v1/logout")public Response<String> logout(HttpServletRequest request) {loginService.logout(request);return ResponseResult.success("成功退出");}

这里的退出逻辑很简单,就是从redis删除用户信息即可,其他退出业务逻辑也可在退出的方法中实现,比如用户退出时间、地点、本次登录时长等等。下方代码的token校验其实可以去掉的,因为退出接口也是需要鉴权的,如果执行到了这里,那说明token是有的并且是正确的没过期的。

    @Override@Transactional(rollbackFor = Exception.class)public void logout(HttpServletRequest request) {String token = request.getHeader(Const.Header.AUTH_KEY);if (token == null) {// 没有携带token,不允许退出,不是正常的操作throw new DeniedException("无权退出");}redisClient.deleteKey(Const.Header.AUTH_KEY + ":" + token);}

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

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

相关文章

C语言 文件I/O(备查)

所有案列 跳转到其他。 文件打开 FILE* fopen(const char *filename, const char *mode); 参数&#xff1a;filename&#xff1a;指定要打开的文件名&#xff0c;需要加上路径&#xff08;相对、绝对路径&#xff09;mode&#xff1a;指定文件的打开模式 返回值&#xff1a;成…

遥感图像分割系统:融合空间金字塔池化(FocalModulation)改进YOLOv8

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义 遥感图像分割是遥感技术领域中的一个重要研究方向&#xff0c;它的目标是将遥感图像中的不同地物或地物类别进行有效的分割和识别。随着遥感技术的不断发展和遥感…

2024年高效远程协同运维工具推荐

随着企业的不断发展以及变化&#xff0c;企业的内部IT环境也是日益复杂&#xff0c;一跨高效远程协同运维工具必不可少&#xff0c;不仅可以提高生产力&#xff0c;还能降低运营成本。这里就给大家推荐2024年高效远程协同运维工具。 高效远程协同运维工具应用场景 1、IT运维管…

(五)STM32 按键输入实验及 GPIO做普通 IO 的注意事项

目录 1. 按键硬件连接 2. 按键软件设计 3. 按键消抖 4. 使用 IO 口时的 注意事项&#xff08;踩坑&#xff09; 上一节我们介绍了 STM32F1 的 IO 口作为输出的使用&#xff0c;这一章&#xff0c;我们将介绍如何使用 STM32F1 的 IO 口作为输入用。在本章中&#xff0c;我们…

modbus 通信协议介绍与我的测试经验分享

1、简介 Modbus 协议是一种通信协议&#xff0c;用于工业自动化系统中的设备间通信。该协议最初由 Modicon 公司开发&#xff0c;并于 1979 年发布。 Modbus 协议通过串行通信格式进行通信&#xff0c;在物理层上支持 RS-232、RS-422 和 RS-485 等多种通信方式。在协议层面&am…

Guardrails for Amazon Bedrock 基于具体使用案例与负责任 AI 政策实现定制式安全保障(预览版)

作为负责任的人工智能&#xff08;AI&#xff09;战略的一部分&#xff0c;您现在可以使用 Guardrails for Amazon Bedrock&#xff08;预览版&#xff09;&#xff0c;实施专为您的用例和负责任的人工智能政策而定制的保障措施&#xff0c;以此促进用户与生成式人工智能应用程…

redis未授权漏洞复现

什么是redis redis就是个数据库&#xff0c;跟mysql不同的地方在于redis主要将数据存在内存中&#xff0c;读写速度非常快 redis未授权 其原因很简单&#xff0c;就是redis服务器在默认安装好不配置的情况下可以直接免密码登录&#xff0c;登录后在web目录写入一句话木马&am…

【Spark精讲】RDD特性之数据本地化

目录 首选运行位置 数据的本地化级别 谁来负责数据本地化 数据本地化执行流程 调优 代码中的设置方法 首选运行位置 上图红框为RDD的特性五&#xff1a;每个RDD的每个分区都有一组首选运行位置&#xff0c;用于标识RDD的这个分区数据最好能够在哪台主机上运行。通过RDD的…

亚信科技AntDB数据库——深入了解AntDB-M元数据锁的相关概念

AntDB-M在架构上分为两层&#xff0c;服务层和存储引擎层。元数据的并发管理集中在服务层&#xff0c;数据的存储访问在存储引擎层。为了保证DDL操作与DML操作之间的一致性&#xff0c;引入了元数据锁&#xff08;MDL&#xff09;。 AntDB-M提供了丰富的元数据锁功能&#xff…

leetcode 236. 二叉树的最近公共祖先

leetcode 236. 二叉树的最近公共祖先 题目 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽…

4G无线工业级路由器在智能制造设备互联互通中的角色

随着工业技术的不断发展和进步&#xff0c;智能制造已经成为了现代制造业的重要趋势和发展方向。而在智能制造过程中&#xff0c;设备之间的互联互通是至关重要的一环。在这个过程中&#xff0c;4G无线工业级路由器扮演着重要的角色&#xff0c;它提供了稳定可靠的网络连接&…

Vue3项目中集成mars3D简单三部曲

Vue3项目中集成mars3D简单三部曲 这里是参考网址&#xff0c;大佬可以点击一件跳转 1.安装依赖 npm install vite-plugin-mars3d --save-dev2.修改 vite.config.ts 配置文件 import { defineConfig } from vite; import { mars3dPlugin } from vite-plugin-mars3d;export d…

Go开发运维:Go服务发布到K8S集群

目录 一、实验 1.Go服务发布到k8s集群 二、问题 1.如何从Harbor拉取镜像 一、实验 1.Go服务发布到k8s集群 &#xff08;1&#xff09;linux机器安装go(基于CentOS 7系统) yum install go -y &#xff08;2&#xff09;查看版本 go version &#xff08;3&#xff09;创…

vue3 setup语法糖写法基本教程

前言 官网地址&#xff1a;Vue.js - 渐进式 JavaScript 框架 | Vue.js (vuejs.org)下面只讲Vue3与Vue2有差异的地方&#xff0c;一些相同的地方我会忽略或者一笔带过与Vue3一同出来的还有Vite&#xff0c;但是现在不使用它&#xff0c;等以后会有单独的教程使用。目前仍旧使用v…

GZ015 机器人系统集成应用技术样题3-学生赛

2023年全国职业院校技能大赛 高职组“机器人系统集成应用技术”赛项 竞赛任务书&#xff08;学生赛&#xff09; 样题3 选手须知&#xff1a; 本任务书共 26页&#xff0c;如出现任务书缺页、字迹不清等问题&#xff0c;请及时向裁判示意&#xff0c;并进行任务书的更换。参赛队…

自定义日志打印功能--C++

一、介绍 日志是计算机程序中用于记录运行时事件和状态的重要工具。通过记录关键信息和错误情况&#xff0c;日志可以帮助程序开发人员和维护人员追踪程序的执行过程&#xff0c;排查问题和改进性能。 在软件开发中&#xff0c;日志通常记录如下类型的信息&#xff1a; 事件信…

Go delve调试工具的简单应用

Delve是个啥 Delve is a debugger for the Go programming language. The goal of the project is to provide a simple, full featured debugging tool for Go. Delve should be easy to invoke and easy to use. Chances are if you’re using a debugger, things aren’t go…

openmediavault debian linux安装配置企业私有网盘(三 )——raid5与btrfs文件系统无损原数据扩容

一、适用环境 1、企业自有物理专业服务器&#xff0c;一些敏感数据不外流时&#xff0c;使用openmediavault自建NAS系统&#xff1b; 2、在虚拟化环境中自建NAS系统&#xff0c;用于内网办公&#xff0c;或出差外网办公时&#xff0c;企业内的文件共享&#xff1b; 3、虚拟化环…

数据结构-迷宫问题

文章目录 1、题目描述2、题目分析3、代码实现 1、题目描述 题目链接&#xff1a;迷宫问题 、 注意不能斜着走&#xff01; 2、题目分析 &#xff08;1&#xff09;0为可以走&#xff0c;1不能走且只有唯一一条通路 &#xff08;2&#xff09;我们可以通过判断上下左右来确定…

AI智能化办公:ChatGPT使用方法与技巧

文章目录 ChatGPT简介✨ChatGPT的使用方法✨登录与访问发送请求调整参数 ChatGPT技巧分享✨清晰的提问实验不同的温度值多轮对话 图书推荐✨AI智能化办公内容简介获取方式 AI短视频内容简介获取方式 随着人工智能技术的不断发展&#xff0c;AI助手在办公场景中扮演着越来越重要…