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 获取当前登录用户


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.

@Value("${jwt.secret}")
private String jwtSecret;

/**
 * 用户登录
 *
 * @return {@link ResponseEntity }
 */
@PostMapping("/login")
public ResponseEntity login(@Validated UserDto userDto, HttpServletRequest request) {
    // --- 验证图片验证码 ---
   
    // 验证用户名密码
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(User::getUsername, userDto.getUsername());
    User user = userService.getOne(queryWrapper);
    if (user != null && passwordEncoder.matches(userDto.getPassword(), user.getPassword())) {
        // 生成 JWT
        HashMap<String, Object> payLoad = new HashMap<>() {{
            put(JWTConstant.JWT_UID_KEY, user.getUid());
            put(JWTConstant.JWT_USERNAME_KEY, user.getUsername());
        }};
        HashMap<String, String> result = new HashMap<>();
        result.put(JWTConstant.JWT_KEY, JWTUtil.createToken(payLoad, jwtSecret.getBytes()));
        result.put(JWTConstant.JWT_USERNAME_KEY, user.getUsername());
        return ResponseEntity.success(result);
    }
    return ResponseEntity.fail("用户名或密码不正确!");
}
jwt:
  secret: aicloud_springcloud_secret
public class JWTConstant {

    public static final String JWT_KEY = "jwt";

    public static final String JWT_UID_KEY = "uid";

    public static final String JWT_USERNAME_KEY = "username";

    public static final String JWT_TOKEN_KEY = "Authorization";
}

2.2 传输JWT

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

存储 JWT 到 LocalStorage 

$.ajax({
    url: '/user/login',
    type: 'POST',
    data: field,
    success: function (res) {
        if (res.code === 200) {
            layui.data(localStorage_jwt_key, {
                // 将生成的 jwt 存储到 LocalStorage
                key: jwt_token_key,
                value: res.data.jwt,
            });
            layui.data(localStorage_username_key, {
                // 存储 username
                key: login_username_key,
                value: res.data.username,
            });
            // 刷新父页面 (登录页是个弹窗)
            window.parent.location.href = window.parent.location.href
        } else {
            layer.msg("登录失败:" + res.msg);
        }
    }
});
var localStorage_username_key = "login_username_key"
var localStorage_jwt_key = "jwt_token_key"
var login_username_key = "username"
var jwt_token_key = "authorization"

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

function authAjax($, url, data, callback) {
    $.ajax({
        url: url,
        type: 'POST',
        headers: {
            'Authorization': layui.data("jwt_token_key").authorization
        },
        data: data,
        success: callback,
    });
}

3. SpringSecurity + JWT 实现登录认证

3.1 配置 Spring Security 安全过滤链

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Resource
    private LoginAuthenticationFilter loginAuthenticationFilter;

    /**
     * 加盐加密
     *
     * @return {@link PasswordEncoder }
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置 Spring Security 安全过滤链
     *
     * @param http
     * @return {@link SecurityFilterChain }
     * @throws Exception
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                // 禁用明文验证
                .httpBasic(AbstractHttpConfigurer::disable)
                // 禁用 CSRF 验证
                .csrf(AbstractHttpConfigurer::disable)
                // 禁用默认登录页
                .formLogin(AbstractHttpConfigurer::disable)
                // 禁用默认 header,支持 iframe 访问页面
                .headers(AbstractHttpConfigurer::disable)
                // 禁用默认注销页
                .logout(AbstractHttpConfigurer::disable)
                // 禁用 Session (默认使用 JWT 认证)
                .sessionManagement(session ->
                        session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(auth -> auth
                        // 允许访问的资源
                        .requestMatchers(
                                "/layui/**",
                                "/js/**",
                                "/image/**",
                                "/login.html",
                                "/index.html",
                                "/reg.html",
                                "/user/login",
                                "/user/reg",
                                "/captcha/create",
                                "/discuss/delete",
                                "/discuss/detail",
                                "/kafka/**",
                                "/swagger-ui/**",
                                "/v3/**",
                                "/doc.html",
                                "/webjars/**"
                        ).permitAll()
                        // 其他请求都需要认证拦截
                        .anyRequest().authenticated()
                )
                // 添加自定义认证过滤器
                .addFilterBefore(loginAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                .build();
    }
}

3.2 自定义登录认证过滤器

@Component
public class LoginAuthenticationFilter extends OncePerRequestFilter {

    @Value("${jwt.secret}")
    private String jwtSecret;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 1.获取 JWT 令牌
        String token = request.getHeader(JWTConstant.JWT_TOKEN_KEY);
        if (!StringUtils.isBlank(token)) {
            // 2.判断 JWT 令牌正确性
            if (JWTUtil.verify(token, jwtSecret.getBytes())) {
                // 3.获取用户信息,存储 Security 中
                JWT jwt = JWTUtil.parseToken(token);
                if (ObjectUtil.isNotNull(jwt) && jwt.getPayload(JWTConstant.JWT_UID_KEY) != null
                        && jwt.getPayload(JWTConstant.JWT_USERNAME_KEY) != null) {
                    Long uid = Long.parseLong(jwt.getPayload(JWTConstant.JWT_UID_KEY).toString());
                    String username = jwt.getPayload(JWTConstant.JWT_USERNAME_KEY).toString();
                    // 4.创建用户对象
                    SecurityUserDetails userDetails = new SecurityUserDetails(uid, username, EMPTY);
                    UsernamePasswordAuthenticationToken authentication =
                            new UsernamePasswordAuthenticationToken(userDetails,
                                    null,
                                    userDetails.getAuthorities());
                    // 绑定 request 对象
                    authentication.setDetails(new WebAuthenticationDetailsSource()
                            .buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        filterChain.doFilter(request, response);
    }
}

3.3  实现SpringSecurity用户对象

@Data
@Builder
public class SecurityUserDetails implements UserDetails {
    @Serial
    private static final long serialVersionUID = -829716430599304080L;
    
    private Long uid;
    private String username;
    private String password;

    public SecurityUserDetails(Long uid, String username, String password) {
        this.uid = uid;
        this.username = username;
        this.password = password;
    }

    /**
     * 权限
     *
     * @return {@link Collection }<{@link ? } {@link extends } {@link GrantedAuthority }>
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    /**
     * 密码
     *
     * @return {@link String }
     */
    @Override
    public String getPassword() {
        return "";
    }

    /**
     * 用户名
     *
     * @return {@link String }
     */
    @Override
    public String getUsername() {
        return "";
    }

    /**
     * 账户是否过期
     *
     * @return boolean
     */
    @Override
    public boolean isAccountNonExpired() {
        return false;
    }

    /**
     * 账号是否锁定
     *
     * @return boolean
     */
    @Override
    public boolean isAccountNonLocked() {
        return false;
    }

    /**
     * 密码是否过期
     *
     * @return boolean
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return false;
    }

    /**
     * 账号是否可用
     *
     * @return boolean
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}

3.4 获取当前登录用户

public class SecurityUtil {

    /**
     * 获取当前登录用户
     *
     * @return {@link SecurityUserDetails }
     */
    public static SecurityUserDetails getCurrentUser() {
        SecurityUserDetails userDetails = null;
        try {
            userDetails = (SecurityUserDetails) SecurityContextHolder.getContext()
                    .getAuthentication().getPrincipal();
        } catch (Exception e) {
        }
        return userDetails;
    }
}

相关推荐

  1. SpringSecurity + JWT 实现登录认证

    2024-07-18 06:32:02       16 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-18 06:32:02       66 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-18 06:32:02       70 阅读
  3. 在Django里面运行非项目文件

    2024-07-18 06:32:02       57 阅读
  4. Python语言-面向对象

    2024-07-18 06:32:02       68 阅读

热门阅读

  1. vue路由的钩子函数

    2024-07-18 06:32:02       24 阅读
  2. Socket、WebSocket 和 MQTT 的区别

    2024-07-18 06:32:02       22 阅读
  3. 深入探讨SQL Server端口设置:理论与实践

    2024-07-18 06:32:02       24 阅读
  4. kafka判断生产者是否向kafka集群成功发送消息

    2024-07-18 06:32:02       24 阅读
  5. mysql 安装配置 next 按钮为什么置灰点击不了

    2024-07-18 06:32:02       22 阅读