当前位置:   article > 正文

SpringSecurity + JWT 实现登录认证_springsecurity + jwt 安全验证

springsecurity + jwt 安全验证

目录

1. JWT 的组成和优势

2. JWT 的工作原理

2.1 生成 JWT

2.2 传输JWT

3. SpringSecurity + JWT 实现登录认证

3.1 配置 Spring Security 安全过滤链

3.2 自定义登录认证过滤器

3.3  实现SpringSecurity用户对象

3.4 获取当前登录用户

3.5 更加简单的获取登录用户 id 


1. JWT 的组成和优势

JWT 由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)

  1. 头部(Header):包含了关于生成该 JWT 的信息以及所使用的算法类型。

  2. 载荷(Payload):包含了要传递的数据,例如身份信息和其他附属数据。

  3. 签名(Signature):使用密钥对头部和载荷进行签名,以验证其完整性。

JWT 相较于传统的 Session 认证机制,具有以下优势:

  1. 无需服务器存储状态:传统的基于 Session 认证机制需要服务器在会话中存储用户的状态信息,包括用户的登录状态、权限等。而使用 JWT,服务器无需存储任何会话状态信息,所有的认证和授权信息都包含在 JWT 中,使得系统可以更容易地进行水平扩展。

  2. 跨域支持:由于 JWT 包含了完整的认证和授权信息,因此可以轻松地在多个域之间进行传递和使用,实现跨域授权。

  3. 适应微服务架构:在微服务架构中,很多服务是独立部署并且可以横向扩展的,这就需要保证认证和授权的无状态性。使用 JWT 可以满足这种需求,每次请求携带 JWT 即可实现认证和授权。

  4. 自包含:JWT 包含了认证和授权信息,以及其他自定义的声明,这些信息都被编码在 JWT 中,在服务端解码后使用。JWT 的自包含性减少了对服务端资源的依赖,并提供了统一的安全机制。

  5. 扩展性:JWT 可以被扩展和定制,可以按照需求添加自定义的声明和数据,灵活性更高。

总结来说,使用 JWT 相较于传统的基于会话的认证机制,可以减少服务器存储开销和管理复杂性,实现跨域支持和水平扩展,并且更适应无状态和微服务架构。

2. JWT 的工作原理

JWT 工作原理包含三部分:

1. 生成 JWT

2. 传输 JWT

3. 验证 JWT

2.1 生成 JWT

在用户登录时,当服务器端验证了用户名和密码的正确性后,会根据用户的信息,如用户 ID 和用户名称,加上服务器端存储的 JWT 秘钥一起来生成一个 JWT 字符串,也就是我们所说的 Token.

  1. @Value("${jwt.secret}")
  2. private String jwtSecret;
  3. /**
  4. * 用户登录
  5. *
  6. * @return {@link ResponseEntity }
  7. */
  8. @PostMapping("/login")
  9. public ResponseEntity login(@Validated UserDto userDto, HttpServletRequest request) {
  10. // --- 验证图片验证码 ---
  11. // 验证用户名密码
  12. LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
  13. queryWrapper.eq(User::getUsername, userDto.getUsername());
  14. User user = userService.getOne(queryWrapper);
  15. if (user != null && passwordEncoder.matches(userDto.getPassword(), user.getPassword())) {
  16. // 生成 JWT
  17. HashMap<String, Object> payLoad = new HashMap<>() {{
  18. put(JWTConstant.JWT_UID_KEY, user.getUid());
  19. put(JWTConstant.JWT_USERNAME_KEY, user.getUsername());
  20. }};
  21. HashMap<String, String> result = new HashMap<>();
  22. result.put(JWTConstant.JWT_KEY, JWTUtil.createToken(payLoad, jwtSecret.getBytes()));
  23. result.put(JWTConstant.JWT_USERNAME_KEY, user.getUsername());
  24. return ResponseEntity.success(result);
  25. }
  26. return ResponseEntity.fail("用户名或密码不正确!");
  27. }
  1. jwt:
  2. secret: aicloud_springcloud_secret
  1. public class JWTConstant {
  2. public static final String JWT_KEY = "jwt";
  3. public static final String JWT_UID_KEY = "uid";
  4. public static final String JWT_USERNAME_KEY = "username";
  5. public static final String JWT_TOKEN_KEY = "Authorization";
  6. }

2.2 传输JWT

JWT 通常存储在客户端的 Cookie、LocalStorage、SessionStorage 等位置,客户端在每次请求时把 JWT 放在 Header 请求头中传递给服务器端.

存储 JWT 到 LocalStorage 

  1. $.ajax({
  2. url: '/user/login',
  3. type: 'POST',
  4. data: field,
  5. success: function (res) {
  6. if (res.code === 200) {
  7. layui.data(localStorage_jwt_key, {
  8. // 将生成的 jwt 存储到 LocalStorage
  9. key: jwt_token_key,
  10. value: res.data.jwt,
  11. });
  12. layui.data(localStorage_username_key, {
  13. // 存储 username
  14. key: login_username_key,
  15. value: res.data.username,
  16. });
  17. // 刷新父页面 (登录页是个弹窗)
  18. window.parent.location.href = window.parent.location.href
  19. } else {
  20. layer.msg("登录失败:" + res.msg);
  21. }
  22. }
  23. });
  1. var localStorage_username_key = "login_username_key"
  2. var localStorage_jwt_key = "jwt_token_key"
  3. var login_username_key = "username"
  4. var jwt_token_key = "authorization"

 登录成功后,其他请求的 header 中带上 token

  1. function authAjax($, url, data, callback) {
  2. $.ajax({
  3. url: url,
  4. type: 'POST',
  5. headers: {
  6. 'Authorization': layui.data("jwt_token_key").authorization
  7. },
  8. data: data,
  9. success: callback,
  10. });
  11. }

3. SpringSecurity + JWT 实现登录认证

3.1 配置 Spring Security 安全过滤链

  1. @Configuration
  2. @EnableWebSecurity
  3. public class SecurityConfig {
  4. @Resource
  5. private LoginAuthenticationFilter loginAuthenticationFilter;
  6. /**
  7. * 加盐加密
  8. *
  9. * @return {@link PasswordEncoder }
  10. */
  11. @Bean
  12. public PasswordEncoder passwordEncoder() {
  13. return new BCryptPasswordEncoder();
  14. }
  15. /**
  16. * 配置 Spring Security 安全过滤链
  17. *
  18. * @param http
  19. * @return {@link SecurityFilterChain }
  20. * @throws Exception
  21. */
  22. @Bean
  23. public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
  24. return http
  25. // 禁用明文验证
  26. .httpBasic(AbstractHttpConfigurer::disable)
  27. // 禁用 CSRF 验证
  28. .csrf(AbstractHttpConfigurer::disable)
  29. // 禁用默认登录页
  30. .formLogin(AbstractHttpConfigurer::disable)
  31. // 禁用默认 header,支持 iframe 访问页面
  32. .headers(AbstractHttpConfigurer::disable)
  33. // 禁用默认注销页
  34. .logout(AbstractHttpConfigurer::disable)
  35. // 禁用 Session (默认使用 JWT 认证)
  36. .sessionManagement(session ->
  37. session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
  38. .authorizeHttpRequests(auth -> auth
  39. // 允许访问的资源
  40. .requestMatchers(
  41. "/layui/**",
  42. "/js/**",
  43. "/image/**",
  44. "/login.html",
  45. "/index.html",
  46. "/reg.html",
  47. "/user/login",
  48. "/user/reg",
  49. "/captcha/create",
  50. "/discuss/delete",
  51. "/discuss/detail",
  52. "/kafka/**",
  53. "/swagger-ui/**",
  54. "/v3/**",
  55. "/doc.html",
  56. "/webjars/**"
  57. ).permitAll()
  58. // 其他请求都需要认证拦截
  59. .anyRequest().authenticated()
  60. )
  61. // 添加自定义认证过滤器
  62. .addFilterBefore(loginAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
  63. .build();
  64. }
  65. }

3.2 自定义登录认证过滤器

  1. @Component
  2. public class LoginAuthenticationFilter extends OncePerRequestFilter {
  3. @Value("${jwt.secret}")
  4. private String jwtSecret;
  5. @Override
  6. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
  7. // 1.获取 JWT 令牌
  8. String token = request.getHeader(JWTConstant.JWT_TOKEN_KEY);
  9. if (!StringUtils.isBlank(token)) {
  10. // 2.判断 JWT 令牌正确性
  11. if (JWTUtil.verify(token, jwtSecret.getBytes())) {
  12. // 3.获取用户信息,存储 Security 中
  13. JWT jwt = JWTUtil.parseToken(token);
  14. if (ObjectUtil.isNotNull(jwt) && jwt.getPayload(JWTConstant.JWT_UID_KEY) != null
  15. && jwt.getPayload(JWTConstant.JWT_USERNAME_KEY) != null) {
  16. Long uid = Long.parseLong(jwt.getPayload(JWTConstant.JWT_UID_KEY).toString());
  17. String username = jwt.getPayload(JWTConstant.JWT_USERNAME_KEY).toString();
  18. // 4.创建用户对象
  19. SecurityUserDetails userDetails = new SecurityUserDetails(uid, username, EMPTY);
  20. UsernamePasswordAuthenticationToken authentication =
  21. new UsernamePasswordAuthenticationToken(userDetails,
  22. null,
  23. userDetails.getAuthorities());
  24. // 绑定 request 对象
  25. authentication.setDetails(new WebAuthenticationDetailsSource()
  26. .buildDetails(request));
  27. SecurityContextHolder.getContext().setAuthentication(authentication);
  28. }
  29. }
  30. }
  31. filterChain.doFilter(request, response);
  32. }
  33. }

3.3  实现SpringSecurity用户对象

  1. @Data
  2. @Builder
  3. public class SecurityUserDetails implements UserDetails {
  4. @Serial
  5. private static final long serialVersionUID = -829716430599304080L;
  6. private Long uid;
  7. private String username;
  8. private String password;
  9. public SecurityUserDetails(Long uid, String username, String password) {
  10. this.uid = uid;
  11. this.username = username;
  12. this.password = password;
  13. }
  14. /**
  15. * 权限
  16. *
  17. * @return {@link Collection }<{@link ? } {@link extends } {@link GrantedAuthority }>
  18. */
  19. @Override
  20. public Collection<? extends GrantedAuthority> getAuthorities() {
  21. return null;
  22. }
  23. /**
  24. * 密码
  25. *
  26. * @return {@link String }
  27. */
  28. @Override
  29. public String getPassword() {
  30. return "";
  31. }
  32. /**
  33. * 用户名
  34. *
  35. * @return {@link String }
  36. */
  37. @Override
  38. public String getUsername() {
  39. return "";
  40. }
  41. /**
  42. * 账户是否过期
  43. *
  44. * @return boolean
  45. */
  46. @Override
  47. public boolean isAccountNonExpired() {
  48. return false;
  49. }
  50. /**
  51. * 账号是否锁定
  52. *
  53. * @return boolean
  54. */
  55. @Override
  56. public boolean isAccountNonLocked() {
  57. return false;
  58. }
  59. /**
  60. * 密码是否过期
  61. *
  62. * @return boolean
  63. */
  64. @Override
  65. public boolean isCredentialsNonExpired() {
  66. return false;
  67. }
  68. /**
  69. * 账号是否可用
  70. *
  71. * @return boolean
  72. */
  73. @Override
  74. public boolean isEnabled() {
  75. return true;
  76. }
  77. }

3.4 获取当前登录用户

  1. public class SecurityUtil {
  2. /**
  3. * 获取当前登录用户
  4. *
  5. * @return {@link SecurityUserDetails }
  6. */
  7. public static SecurityUserDetails getCurrentUser() {
  8. SecurityUserDetails userDetails = null;
  9. try {
  10. userDetails = (SecurityUserDetails) SecurityContextHolder.getContext()
  11. .getAuthentication().getPrincipal();
  12. } catch (Exception e) {
  13. }
  14. return userDetails;
  15. }
  16. }

得到当前登录对象后,想要获取当前登录对象的 uid,就可以通过如下代码来获取:

Long uid = ObjectUtil.isNotNull(userDetails) ? userDetails.getUid() : NumberUtils.LONG_ZERO;

3.5 更加简单的获取登录用户 id 

使用 ThreadLocal 可以更加简单的存取当前用户的 id,并且客户端的一次请求用的是同一个线程 id,多次请求线程 id 也不同,也不会出现线程安全问题。

① 创建 ThreadLocal 工具类 BaseContext

  1. public class BaseContext {
  2. public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
  3. public static void setCurrentId(Long id) {
  4. threadLocal.set(id);
  5. }
  6. public static Long getCurrentId() {
  7. return threadLocal.get();
  8. }
  9. public static void removeCurrentId() {
  10. threadLocal.remove();
  11. }
  12. }

② 在登录认证过滤器,令牌通过后,存储 uid

  1. @Component
  2. public class LoginAuthenticationFilter extends OncePerRequestFilter {
  3. @Value("${jwt.secret}")
  4. private String jwtSecret;
  5. @Override
  6. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
  7. // 1.获取 JWT 令牌
  8. String token = request.getHeader(JWTConstant.JWT_TOKEN_KEY);
  9. if (!StringUtils.isBlank(token)) {
  10. // 2.判断 JWT 令牌正确性
  11. if (JWTUtil.verify(token, jwtSecret.getBytes())) {
  12. // 3.获取用户信息,存储 Security 中
  13. JWT jwt = JWTUtil.parseToken(token);
  14. if (ObjectUtil.isNotNull(jwt) && jwt.getPayload(JWTConstant.JWT_UID_KEY) != null
  15. && jwt.getPayload(JWTConstant.JWT_USERNAME_KEY) != null) {
  16. Long uid = Long.parseLong(jwt.getPayload(JWTConstant.JWT_UID_KEY).toString());
  17. String username = jwt.getPayload(JWTConstant.JWT_USERNAME_KEY).toString();
  18. // --------- 使用 ThreadLocal 存储当前用户 ID ---------
  19. BaseContext.setCurrentId(uid);
  20. }
  21. }
  22. }
  23. filterChain.doFilter(request, response);
  24. }
  25. }

③ 在需要使用 uid 的地方获取 uid

Long uid = BaseContext.getCurrentId(); // 使用 ThreadLocal 获取身份 id

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/989879
推荐阅读
  

闽ICP备14008679号