SpringBoot 结合 SpringSecurity 对于用户 登陆 和 登出 的设计思考

news/2024/9/22 7:52:48/

SpringBoot 结合 SpringSecurity 对于用户登陆和登出的设计

前题:最近自己在做一个项目,之前在网上看到或者学的基本都是使用Redis+Token来实现的安全检查机制。但是,后来在实际的开发中发现这确实是最普遍的基础做法,但是也没人说在实际的工作中是不这样的啊。首先就是一个安全的问题,重中之重。

JWT + Redis ?

JWT用来生成Token,然后在用户登陆后返回token存在Redis里。前端也要本地储存Token,对于其他API的调用就要每次用token进行验证。

  • 首先, JWT + Redis 这两者结合起来就是不太对的,我们之所以使用Token就是因为其的无状态性stateless,尤其是在当前的分布式系统中非常的重要。
  • 其次,就是JWT之所火也是因为其适合分布式的系统不用在后端储存任何的信息。因此我们这里再结合Redis其实就不符合JWT的设计了。
  • 再来,直接把Token返回给前端,前端对于Token的操作是不安全的。前端得到Token后可能需要保存到本地的localStorage,这直接的就导致了Token被恶意获取获取修改,比如常见的XSS攻击等。
  • 最后,这种方式需要在每次前端发起请求的时候都要手动的携带上Token也极其的不方便。

JWT + Cookie?

这里是我在GPT老师的帮助下获取的方式,应该算是比较安全和普遍的了。

  1. JWT 正常生成Token,但是在用户成功登陆后后端不直接把Token显示的返回给前端
  2. 可以通过利用HttpOnly 和 Secure 来保证Token的安全,就是在登陆成功后把Token通过这样的形式以Cookie的方式返回给前端。这样一来前端就不需要对Token进行任何额外的处理了。
  3. Cookie保存到本地的方式还可以再每次向其他API发起请求时自动的携带上Cookie token到后端,不用前端人员每次手动的操作。

但是这里面还有许多需要注意的点:

  • 比如前后端分离的要配置跨域访问CORS,允许凭证等。
java">@Configuration
public class CorsConfig implements WebMvcConfigurer {@Beanpublic WebMvcConfigurer corsConfigurer() {return new WebMvcConfigurer() {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOrigins("http://localhost:3000").allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS").allowedHeaders("*").allowCredentials(true); // 这里一定要true}};}
}
  • 如果自己写了JWT Filter 还需要后端每次得到请求要解析下cookie获取token。
java">public String extractTokenFromCookies(HttpServletRequest request) {Cookie[] cookies = request.getCookies();if (cookies != null) {for (Cookie cookie : cookies) {if (cookie.getName().equals("httpOnlyToken")) {return cookie.getValue(); // Return the token from the cookie}}}return null; // Return null if the token is not found
}
  • 前端也要写好每次请求携带凭证。
javascript">// 我这里是React的例子import axios from 'axios';// 创建一个 axios 实例
const axiosInstance = axios.create({baseURL: 'http://localhost:8080',withCredentials: true,  // 允许携带 cookieheaders: {'Content-Type': 'application/json',}
});

Login 部分的代码

java">@Override
public ResponseEntity<Result> login(User user, HttpServletResponse response) {// use SpringSecurity's AuthenticationManager to authenticate the userAuthentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()));//pass the authenticationif (authentication.isAuthenticated()){LoginUser loginUser = (LoginUser) authentication.getPrincipal();// user infoMap<String, Object> userInfo = new HashMap<>();userInfo.put("roles", loginUser.getRoles());userInfo.put("username", loginUser.getUser().getUsername());userInfo.put("email", loginUser.getUser().getEmail());userInfo.put("permissions", loginUser.getPermissions());userInfo.put("userId", loginUser.getUser().getId());// Generate a tokenString token = jwtUtil.generateToken(userInfo);// 将 token 设置为 HttpOnly 和 Secure CookieCookie jwtCookie = new Cookie("httpOnlyToken", token);jwtCookie.setHttpOnly(true); // 设置 HttpOnly 防止 XSS 攻击jwtCookie.setSecure(true); // 设置 Secure 确保只在 HTTPS 中传输jwtCookie.setMaxAge(24 * 60 * 60); // 设置有效期为 1 天(单位:秒)jwtCookie.setPath("/"); // 适用于整个应用
//            jwtCookie.setDomain("localhost"); // 这个应该是你的域名,只有在这个域名下才能访问到这个 cookieresponse.addCookie(jwtCookie); // 将 cookie 添加到响应中// return the token and user info to the client// for stateless authentication, the server does not need to store the tokenMap<String, Object> responseData = new HashMap<>();responseData.put("user", userInfo);responseData.put("message", "Login successful");return ResponseEntity.ok(Result.success(responseData));}else {// If the authentication fails, return an error messagereturn ResponseEntity.status(403).body(Result.error(403,"Password or Username is incorrect"));}}

Logout

既然都有login那必须要有logout,这里需要注意的就是Logout要记得清除Cookie 通过response。

java">@Override
public ResponseEntity<Result> logout(HttpServletRequest request, HttpServletResponse response) {// Extract token using utility methodString token = jwtUtil.extractTokenFromCookies(request);if (token != null) {// 让cookie实效!Cookie cookie = new Cookie("httpOnlyToken", null);cookie.setHttpOnly(true);cookie.setSecure(true);cookie.setPath("/");cookie.setMaxAge(0);response.addCookie(cookie);return ResponseEntity.ok(Result.success("Logout successful"));} else {return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Result.error(400, "Token not found in cookies"));}}

总结:

这也是我个人在做项目时的思考和经验,希望对大家有所帮助!如有不对或者值得改进的地方希望有大佬也可以指出来,让我多学些学习。

最后,我项目的代码在 GitHub 上有兴趣的朋友可以去看看完整的代码逻辑。


http://www.ppmy.cn/news/1528733.html

相关文章

(done) 声音信号处理基础知识(5) (Types of Audio Features for Machine Learning)

参考&#xff1a;https://www.youtube.com/watch?vZZ9u1vUtcIA 声学特征描述了声音&#xff0c;不同特征捕捉声音的不同方面性质 声学特征有助于我们构建智能声学系统 声学特征分类有&#xff1a; 1.抽象等级 2.时域视野 3.音乐的部分 4.信号域 5.机器学习方法 如下图展示…

pg入门18—如何使用pg gis

1. 下载postgre gis镜像 2. 运行镜像 docker run -p 15432:5432 -d -e POSTGRES_PASSWORDAb123456! postgis/postgis:12-3.4-alpine 3. 使用gis # 进入容器&#xff0c;登录pgdocker exec -it bash# 登录数据库psql -U postgres# 创建数据库CREATE DATABASE mygeotest;# 使用…

配置docker的proxy指向

因为网络原因&#xff0c;现在无法直接下载docker hub上的镜像&#xff0c;需设置通过proxy进行访问。 一、测试环境 在笔记本上启用了proxy软件&#xff0c;笔记本IP为192.168.253.1&#xff0c;proxy端口8888。搭建一台Linux虚拟机用于下载docker镜像&#xff0c;虚拟机IP&…

SQL进阶技巧:火车票相邻座位预定一起可能情况查询算法 ?

目录 0 场景描述 1 数据准备 2 问题分析 2.1 分析函数法 2.2 自关联求解 3 小结

大数据-137 - ClickHouse 集群 表引擎详解2 - MergeTree 存储结构 一级索引 跳数索引

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

window下idea中scala的配置

目录 Scala安装步骤&#xff1a; 1.下载scala安装包 2.配置环境变量&#xff1a; 3.检查scala是否安装成功&#xff1a; 4.idea安装scala插件 5.导入scala-sdk 6.新建scala文件 Scala安装步骤&#xff1a; 1.下载scala安装包 访问Scala官网&#xff1a;https://www.sca…

用户态缓存:链式缓冲区(Chain Buffer)

目录 链式缓冲区&#xff08;Chain Buffer&#xff09;简介 为什么选择链式缓冲区&#xff1f; 代码解析 1. 头文件与类型定义 2. 结构体定义 3. 宏定义与常量 4. 环形缓冲区的基本操作 5. 其他辅助函数 6. 数据读写操作的详细实现 7. 总结 8. 结合之前的内容 9. 具…

DevOps -分布式追踪与监控

DevOps中的分布式追踪与监控 在当今的DevOps环境中&#xff0c;随着微服务架构和云原生应用的广泛采用&#xff0c;系统复杂性显著增加&#xff0c;传统的监控方式已经无法满足现代分布式系统的需求。为了提高系统可观测性&#xff0c;分布式追踪和监控成为现代DevOps实践中至…