带你掌握springboot集成SpringSecurity安全框架

前言:

        Spring Security 是 Spring 家族中的一个框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,系统的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。

认证:验证当前访问系统的是不是本系统的用户,用户认证一般要求用户提供用户名和密码,或者手机号和验证码等形式

授权:经过认证后判断当前用户是否有权限执行某个操作。在系统中,会对用户校色权限管理,不同的用户具有的权限是不同的。一般系统也是基于角色权限管理(RBAC),会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

一:登录流程

  1. 前端登录页面会携带用户名和密码调用后端登录接口
  2. 后端对用户名和密码进行数据库校验
  3. 校验成功后,会根据当前用户信息生成一个token
  4. 将token响应给前端
  5. 之后前端访问系统资源都需要请求头携带token访问后端
  6. 后端获取请求头token进行解析,解析用户信息。
  7. 校验用户是否有权限访问相关资源,有则访问资源响应给前端

二:Spring Security登录流程

1.过滤器链

  • SecurityContextPersistenceFilter:维护安全上下文,确保线程间的安全信息传递。
  • UsernamePasswordAuthenticationFilter:负责处理基于表单的登录请求,收集用户名和密码,调用AuthenticationManager进行验证。
  • ConcurrentSessionFilter:管理并发会话,避免同一账号多处登录。
  • ExceptionTranslationFilter:捕获并处理认证或授权失败的异常。
  • FilterSecurityInterceptor:执行访问决策,依据用户权限判断是否允许访问特定资源。

2. 认证管理器

AuthenticationManager负责处理认证请求,它接受一个Authentication对象(包含用户凭证),并返回一个经过完全填充的(已验证的或未经验证的)Authentication对象。对于基于用户名和密码的登录,Spring Security提供了DaoAuthenticationProvider,它使用UserDetailsService来检索用户信息。

3. 用户详情服务

UserDetailsService接口定义了一个方法loadUserByUsername(String username),用于根据用户名加载用户信息。开发者需要实现这个接口,通常从数据库中查询用户信息。返回的是UserDetails对象,它包含用户的用户名、密码(通常是加密的)、权限等安全相关信息。

4. 用户详情

UserDetails表示用户安全信息的核心接口,包含用户名、密码、账号是否过期、凭证是否过期、账号是否锁定以及赋予用户的权限集合。一个典型的实现org.springframework.security.core.userdetails.User。

具体步骤:

登录流程的简化步骤:

  1. 用户提交登录表单,表单中通常包含用户名和密码。

  2. 客户端发送请求到服务器,请求到达UsernamePasswordAuthenticationFilter过滤器。

  3. UsernamePasswordAuthenticationFilter过滤器会尝试从请求中提取用户名和密码,并构造一个UsernamePasswordAuthenticationToken对象,并转发给AuthenticationManager。

  4. AuthenticationManager接口的实现(通常是ProviderManager)会根据配置的AuthenticationProviders来验证这个Authentication对象。

  5. 实现UserDetailsService的服务来加载用户详情(UserDetails)使用PasswordEncoder比较提交的密码与数据库中存储的密码,验证密码是否正确。

  6. 如果验证成功,AuthenticationManager会返回一个包含用户详情的Authentication对象,否则抛出认证失败异常。

  7. 在成功验证后,SecurityContextHolder会更新安全上下文,存储认证信息。

  8. 最后,用户会被重定向到登录成功页面或获取认证后的资源。

认证失败时,抛出异常,由ExceptionTranslationFilter捕获并处理,可能重定向到登录页面显示错误消息,或响应HTTP 401 Unauthorized。

访问控制:用户携带令牌访问受保护资源时,FilterSecurityInterceptor基于用户的角色和权限进行访问决策,决定是否允许访问。

代码演示;

注意:这里演示没有引入redis,用户信息都在内存操作。

1.相关依赖

<dependencies><!--web--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--security--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!--测试使用--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--测试使用--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.8</version></dependency></dependencies>

2.登录认证

登录:

  • 自定义登录接口:调用 AuthenticationManager的方法进行认证,如果认证通过生成 jwt,把用户信息存入 redis 中(常规做法,这里我用内存)。
//登录接口
public interface ILoginService {/** 登录 */Result login(User user);/** 退出登录 */Result loginOut();
}
@Service
public class LoginServiceImpl implements ILoginService {@Autowiredprivate AuthenticationManager authenticationManager;private static Map<String, Object> cache = new HashMap();//登录@Overridepublic Result login(User user) {/** 用户名和密码的认证请求,并通过AuthenticationManager进行用户认证。 */UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword());Authentication authenticate = authenticationManager.authenticate(authenticationToken);// 判空if (Objects.isNull(authenticate)) {throw new RuntimeException("用户名或密码有误");}//从认证成功的Authentication对象中获取principal,转换为LoginUser类型,进而得到用户账号userName。LoginUser loginUser = (LoginUser) authenticate.getPrincipal();String userName= loginUser.getUser().getUsername();// 通过 hutool-Jwt工具类 使用 userName生成tokenHashMap<String, Object> payload = new HashMap<>();payload.put("name", userName);String token = JWTUtil.createToken(payload, "123456".getBytes());// 将token存入内存(这一步常规做法存redis)CacheUser.CACHE.put(name, loginUser);// 将token响应给前端HashMap<String, Object> map = new HashMap<>();map.put("token", token);return new Result(200, "登录成功", map);}//退出登录@Overridepublic Result loginOut() {// 从存储权限的集合SecurityContextHolder内将获取到Authentication对象。里面包含已认证的用户信息,权限集合等。Authentication authentication = SecurityContextHolder.getContext().getAuthentication();// 从 获取到的对象内 取出 已认证的主体LoginUser loginUser = (LoginUser) authentication.getPrincipal();// 从被认证的主体内取出用户名String userName= loginUser.getUser().getUsername();// 根据键为用户名userAccount删除对应的用户信息cache.remove(userName);return new Result(200, "退出成功", "");}
}

  • 自定义 CustomUserDetailsService,这个实现类中去查询数据库(常规做法,这里我用内存)。

基础实体:

//登录用户@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {private User user;public LoginUser(User user) {this.user = user;}// 存储Security所需要的权限信息的集合private List<GrantedAuthority> authorities;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}//用户
@Data
public class User {//用户名private String username;//密码private String password;
}

 实现UserDetailsService 接口:

@Service
public class CustomUserDetailsService implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 使用用户名获取用户全部信息(这一步常规做法,查数据库)User user = new User();user.setUsername(username);user.setPassword("$2a$10$GL84UYDv2.kQgREcw6wNQ.0eQWIAOno.gNnoI7UUSpYT6JdJ1QH2i");// 查询不到用户信息则抛出异常if (Objects.isNull(user)) {throw new RuntimeException("用户名或密码有误");}//(这一步常规做法,查权限添加到 LoginUser)return new LoginUser(user);}
}

校验:

  • 定义 Jwt 认证过滤器,获取 token,解析 token 获取用户信息,从 redis 中获取用户信息(常规做法,这里我用内存),存入 SecurityContextHolder。

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {// 从请求头获取tokenString token = request.getHeader("token");// 检查获取到的token是否为空或空白字符串if (!StringUtils.hasText(token)) {// 如果token为空,则直接放行请求到下一个过滤器,不做进一步处理并结束当前方法,不继续执行下面代码。filterChain.doFilter(request, response);return;}// 解析tokenString userName;try {userName = (String) JWTUtil.parseToken(token).getPayloads().get("name");} catch (Exception e) {e.printStackTrace();throw new RuntimeException("token非法");}// 从内存获取用户(这一步常规做法从redis中获取用户信息)LoginUser loginUser = CacheUser.CACHE.get(userName);if (Objects.isNull(loginUser)) {throw new RuntimeException("用户未登录");}//将用户信息存入 SecurityConText//UsernamePasswordAuthenticationToken 存储用户名 密码 权限的集合UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());//SecurityContextHolder是Spring Security用来存储当前线程安全的认证信息的容器。//将用户名 密码 权限的集合存入SecurityContextHolderSecurityContextHolder.getContext().setAuthentication(authenticationToken);//放行filterChain.doFilter(request, response);}
}

3.配置类:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;@Overrideprotected void configure(HttpSecurity http) throws Exception {//后端测试要关闭csrf()http.csrf().disable()//不通过session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 登录接口公开访问.antMatchers("/sys/login").anonymous()// 除上面公开的接口外,所有的请求都需要鉴定认证.anyRequest().authenticated().and()// 添加 过滤器.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);// 允许跨域http.cors();}/*** AuthenticationManager 认证*/@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}//密码加密@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}

测试:

@RequestMapping("/sys")
@RestController
public class UserController {@Resourceprivate ILoginService loginService;@PostMapping("/login")public Result login(@RequestBody User user){return loginService.login(user);}@PostMapping("/logout")public Result logout(){return new Result(200,"成功");}
}

执行结果:

登录:

登出:

权限这边的测试大家可以自己演示一遍,博主这里就不演示了,将改点告诉大家

  •  在登录用户实体加上权限字段
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {private User user;// 存储权限信息private List<String> permissions;public LoginUser(User user, List<String> permissions) {this.user = user;this.permissions = permissions;}// 存储 Security 所需要的权限信息的集合@JSONField(serialize = false)private List<GrantedAuthority> authorities;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {if (authorities != null) {return authorities;}// 将 permissions 内字符串类型的权限信息转换为 GrantedAuthority 对象存入 authoritiesauthorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());return authorities;}
}
  • 查询数据库这一步,通过之后查询权限
@Service
public class CustomUserDetailsService implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 使用用户名获取用户全部信息User user = new User();user.setUsername(username);user.setPassword("$2a$10$GL84UYDv2.kQgREcw6wNQ.0eQWIAOno.gNnoI7UUSpYT6JdJ1QH2i");// 查询不到用户信息则抛出异常if (Objects.isNull(user)) {throw new RuntimeException("用户名或密码有误");}// TODO 在授权时返回此处。 根据用户查询权限信息,再添加到 LoginUser 中。return new LoginUser(user,"权限");}
}

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

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

相关文章

【2024最新】基于springboot+vue的闲一品交易平台lw+ppt

作者&#xff1a;计算机搬砖家 开发技术&#xff1a;SpringBoot、php、Python、小程序、SSM、Vue、MySQL、JSP、ElementUI等&#xff0c;“文末源码”。 专栏推荐&#xff1a;SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;Java精选实战项…

修改yolo格式的labels类别、删除yolo格式的labels类别

1、项目中&#xff0c;数据集的类别顺序有时会改变&#xff0c;例如a项目类别1是b项目的类别3&#xff0c;&#xff0c;需要用a项目的数据集&#xff0c;只需要改类别就行&#xff0c;就不需要重新标注了&#xff0c;例如a项目的classes是 b项目的classes是 a项目的数据可以用…

Java项目实战II基于微信小程序的个人行政复议在线预约系统微信小程序(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 基于微信小…

Kubernetes在容器编排中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 Kubernetes在容器编排中的应用 Kubernetes在容器编排中的应用 Kubernetes在容器编排中的应用 引言 Kubernetes 概述 定义与原理 …

D67【python 接口自动化学习】- python基础之数据库

day67 Python操作MySQL基础使用 学习日期&#xff1a;20241113 学习目标&#xff1a;MySQL数据库-- 136 Python操作MySQL基础使用 学习笔记&#xff1a; pymysql 创建MySQL的数据库链接 执行sql语句 总结 Python中使用第三方库&#xff1a;pymysql来操作MySQL&#xff0c;…

GitLab基于Drone搭建持续集成(CI/CD)

本文介绍了如何为 Gitee 安装 Drone 服务器。服务器打包为在 DockerHub 上分发的最小 Docker 映像。 1. 准备工作 创建OAuth应用 创建 GitLab OAuth 应用。Consumer Key 和 Consumer Secret 用于授权访问极狐GitLab 资源。 ps:授权回调 URL 必须与以下格式和路径匹配&…

python实战(八)——情感识别(多分类)

一、任务目标 本文使用的是来自Kaggle的一个情感识别数据集&#xff0c;这个数据集的总数据量是5934条&#xff0c;标签为anger、fear、joy三种情感的其中一种&#xff0c;很明显是一个多分类任务。这里&#xff0c;我们将使用微调技巧进行深度学习建模&#xff0c;同时我们会比…

【学习笔记】数据结构(七)

图 文章目录 图7.1 图的定义和术语7.2 图的存储结构7.2.1 数组表示法 - 邻接矩阵&#xff08;Adjacency Matrix)7.2.2 邻接表 -&#xff08;链式&#xff09;表示法(Adjacency List&#xff09;7.2.3 十字链表(Orthogonal List)7.2.4 邻接多重表(Adjacent MultiList) 7.3 图的遍…

scrapy爬取中信证券销售金融产品信息

import scrapyclass CsProductSpider(scrapy.Spider):name "cs_product"allowed_domains ["www.cs.ecitic.com"]start_urls ["http://www.cs.ecitic.com/newsite/cpzx/jrcpxxgs/zgcp/index.html"]def parse(self, response):# 提取数据的逻辑…

power bi中的related函数解析

在Power BI中&#xff0c;RELATED函数是一种用于检索相关表中数据的函数。它用于在一个表中检索与当前行相关联的另一个表中的数据。 销售成本 [销售数量]*related(商品表[进价])

Ollama的安装以及大模型下载教程

简介 Ollama是一个开源的大型语言模型服务工具&#xff0c;它帮助用户快速在本地运行大模型。通过简单的安装指令&#xff0c;用户可以执行一条命令就在本地运行开源大型语言模型&#xff0c; Ollama极大地简化了在Docker容器内部署和管理LLM的过程&#xff0c;使得用户能够快…

算法演练----24点游戏

给定4个整数&#xff0c;数字范围在1~13之间任意使用-*/&#xff08;&#xff09;&#xff0c;构造出一个表达式&#xff0c;使得最终结果为24&#xff0c; 方法一 算法分析&#xff1a;加括号和取出重复表达式 # 导入精确除法模块&#xff0c;使得在Python2中除法运算的行为更…

YUM 的使用

YUM 是一个用于 Fedora 和 Red Hat 以及 CentOS 操作系统的前端软件包管理器&#xff0c;它可以自动处理依赖关系并一次性安装所有必需的软件包。 镜像站点选择 1. 备份原有的镜像源配置文件 系统默认的 yum 镜像源配置文件存储在 /etc/yum.repos.d/ 目录下&#xff0c;可以…

第三十六章 Vue之路由重定向/404页面设置/路径模式设置

目录 一、路由重定向 1.1. 使用方式 1.2. 完整代码 1.2.1. main.js 1.2.2. App.vue 1.2.3. index.js 1.2.4. Search.vue 1.2.5. Home.vue 1.3. 运行效果 二、设定404错误页面 2.1. 使用方式 2.2. 完整代码 2.2.1. index.js 2.2.2. NotFound.vue 2.2.3. 运行效…

鸿蒙进阶篇-属性动画-animateTo转场动画

大家好啊&#xff0c;这里是鸿蒙开天组&#xff0c;今天我们来学习属性动画-animateTo&转场动画&#xff0c;咱们先来学习属性动画-animateTo 属性动画-animateTo 属性动画 animation是作为属性使用&#xff0c;而animateTo显示动画是一个系统的内置函数&#xff0c;可以…

[CKS] K8S ServiceAccount Set Up

最近准备花一周的时间准备CKS考试&#xff0c;在准备考试中发现有一个题目关于Rolebinding的题目。 ​ 专栏其他文章: [CKS] Create/Read/Mount a Secret in K8S-CSDN博客[CKS] Audit Log Policy-CSDN博客 -[CKS] 利用falco进行容器日志捕捉和安全监控-CSDN博客[CKS] K8S Netwo…

Autosar CP DDS规范导读

Autosar CP DDS 主要用途 数据通信 中间件协议&#xff1a;作为一种中间件协议&#xff0c;DDS实现了应用程序之间的高效数据通信&#xff0c;能够在不同的软件组件和ECU之间传输数据&#xff0c;确保数据的实时性和可靠性。跨平台通信&#xff1a;支持在AUTOSAR CP平台上的不同…

wafw00f源码详细解析

声明 本人菜鸟一枚&#xff0c;为了完成作业&#xff0c;发现网上所有的关于wafw00f的源码解析都是这抄那那抄这的&#xff0c;没有新东西&#xff0c;所以这里给出一个详细的源码解析&#xff0c;可能有错误&#xff0c;如果有大佬发现错误&#xff0c;可以在评论区平和的指出…

字节、快手、Vidu“打野”升级,AI视频小步快跑

文&#xff5c;白 鸽 编&#xff5c;王一粟 继9月份版本更新之后&#xff0c;光锥智能从生数科技联合创始人兼CEO唐家渝朋友圈获悉&#xff0c;Vidu大模型将于本周再次进行版本升级&#xff0c;Vidu-1.5版本即将上线。 此版本更新方向仍是重点延伸大模型的泛化能力和主体…

LeetCode【0036】有效的数独

本文目录 1 中文题目2 求解方法&#xff1a;python内置函数set2.1 方法思路2.2 Python代码2.3 复杂度分析 3 题目总结 1 中文题目 请根据以下规则判断一个 9 x 9 的数独是否有效。 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线…