SpringBoot 结合 SpringSecurity 对于用户 登陆 和 登出 的设计思考
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老师的帮助下获取的方式,应该算是比较安全和普遍的了。
- JWT 正常生成Token,但是在用户成功登陆后后端不直接把Token显示的返回给前端。
- 可以通过利用HttpOnly 和 Secure 来保证Token的安全,就是在登陆成功后把Token通过这样的形式以Cookie的方式返回给前端。这样一来前端就不需要对Token进行任何额外的处理了。
- Cookie保存到本地的方式还可以再每次向其他API发起请求时自动的携带上Cookie token到后端,不用前端人员每次手动的操作。
但是这里面还有许多需要注意的点:
- 比如前后端分离的要配置跨域访问CORS,允许凭证等。
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true); // 这里一定要true
}
};
}
}
- 如果自己写了JWT Filter 还需要后端每次得到请求要解析下cookie获取token。
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
}
- 前端也要写好每次请求携带凭证。
// 我这里是React的例子
import axios from 'axios';
// 创建一个 axios 实例
const axiosInstance = axios.create({
baseURL: 'http://localhost:8080',
withCredentials: true, // 允许携带 cookie
headers: {
'Content-Type': 'application/json',
}
});
Login 部分的代码
@Override
public ResponseEntity<Result> login(User user, HttpServletResponse response) {
// use SpringSecurity's AuthenticationManager to authenticate the user
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()));
//pass the authentication
if (authentication.isAuthenticated()){
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
// user info
Map<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 token
String token = jwtUtil.generateToken(userInfo);
// 将 token 设置为 HttpOnly 和 Secure Cookie
Cookie 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"); // 这个应该是你的域名,只有在这个域名下才能访问到这个 cookie
response.addCookie(jwtCookie); // 将 cookie 添加到响应中
// return the token and user info to the client
// for stateless authentication, the server does not need to store the token
Map<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 message
return ResponseEntity.status(403).body(Result.error(403,"Password or Username is incorrect"));
}
}
Logout
既然都有login那必须要有logout,这里需要注意的就是Logout要记得清除Cookie 通过response。
@Override
public ResponseEntity<Result> logout(HttpServletRequest request, HttpServletResponse response) {
// Extract token using utility method
String 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 上有兴趣的朋友可以去看看完整的代码逻辑。
原文地址:https://blog.csdn.net/weixin_55592317/article/details/142417861
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!