Spring Security + JWT 简单集成

18 阅读4分钟

1、认证与授权

  • 认证Authentication):验证用户身份

  • 授权Authorization):允许用户访问那些资源,验证用户权限

2、JWT无状态认证

JWT(JSON Web Token):无状态认证,是一种紧凑安全的开放标准,用于在网络应用环境间传递声明(claims)。

JWT = Header +Payload + Signature

  • Header:头部,包含令牌类型(通常是 "JWT")和签名算法(如 HMAC SHA256)。
  • Payload:有效载荷,包含用户信息和其他元数据(如过期时间等)。
  • Signature:签名部分,用于验证token的完整性。

服务端不保存Session,Token包含用户信息,客户端每次请求携带。

  • Token 通常指的是整个 JWT 字符串,它是由头部、负载和签名组成,经过编码并用点 (.) 分隔开
  • Http请求中:
(授权)Authorization: Bearer <token>

3、Spring Security + JWT 集成

3.1 添加依赖 pom.xml

<!-- Spring Security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- JWT 相关 -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

3.2 JWT 相关配置

JWT 工具类:生成和解析Token

package com.example.utils;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Component
public class JwtUtil {

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

    @Value("${jwt.expiration:86400000}")
    private Long expiration;  // 默认1天

    private Key getSigningKey() {
        return Keys.hmacShaKeyFor(secret.getBytes()); //生成 HMAC SHA 密钥
    }

    /**
     * 生成JWT Token
     */
    public String generateToken(Integer userId, String username) {
        Map<String, Object> claims = new HashMap<>();
        //claims映射,存储用户信息
        claims.put("userId", userId);
        claims.put("username", username);
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                .compact();
    }

    /**
     * 从Token中获取用户ID
     */
    public Integer getUserIdFromToken(String token) {
        //创建解析器,设置签名密钥,解析token,获取claims
        Claims claims = Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token) //解析Token
                .getBody();
        return claims.get("userId", Integer.class);
    }

    /**
     * 从Token中获取用户名
     */
    public String getUsernameFromToken(String token) {
        Claims claims = Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
        return claims.getSubject();
    }

    /**
     验证Token是否有效
     * 验证给定的JWT是否有效,通过解析和验证 Token 的签名来实现这一点。
     */
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            return false;
        }
    }
}

3.3 Spring Security 相关配置

自定义UserDetailsService:实现基于数据库的用户认证与授权。

允许应用程序灵活地从任何数据源(如数据库)加载用户信息,并确保在用户登录时能够正确地验证身份及授权访问权限。

package com.example.security;

import com.example.entity.User;
import com.example.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 根据用户名查询用户
        User user = userMapper.selectOne(
                new LambdaQueryWrapper<User>()
                        .eq(User::getUsername, username)
        );
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        // 返回Spring Security的User对象
        return org.springframework.security.core.userdetails.User
                .withUsername(username)
                .password(user.getPassword())  // 注意:密码应为加密后的
                .authorities(user.getRole())           // 权限
                .build();
    }
}

3.4 Spring Security + JWT 集成

创建JWT认证过滤器(JwtAuthenticationFilter):JWT认证通常是通过Authorization header传递的,需要自定义过滤器来拦截请求并验证JWT

package com.example.security;

import com.example.utils.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        //从请求中获取token
        String token = getTokenFromRequest(request);
        if (token != null && jwtUtil.validateToken(token)) {
            //提取用户名并加载用户信息
            String username = jwtUtil.getUsernameFromToken(token);
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            //创建认证对象并设置到安全上下文
            //创建认证token
            UsernamePasswordAuthenticationToken authentication =
                    new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            //设置认证详情
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            //设置Security上下文        
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(request, response);
    }

    private String getTokenFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

配置Spring Security:SecurityConfig

  • 认证管理器AuthenticationManager:处理认证请求,登录接口使用
  • 安全过滤器链SecurityFilterChain:配置请求权限,CSRF设置
  • 密码加密PasswordEncoder:使用Bcrypt加密
package com.example.config;

import com.example.security.CustomUserDetailsService;
import com.example.security.JwtAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;

    //安全过滤器链
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                //禁用CSRF(跨站请求伪造)保护
                .csrf().disable()
                //配置为无状态会话
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                //配置请求的授权规则
                .authorizeRequests()
                .antMatchers("/api/users/login", "/api/users/register").permitAll()
                .antMatchers("/doc.html", "/webjars/**", "/swagger-resources/**", "/v2/api-docs", "/v3/api-docs").permitAll()
                .anyRequest().authenticated() //其他接口需要认证
                .and()
                .userDetailsService(userDetailsService) //指定自定义的用户信息服务
                //将自定义的 JWT 过滤器添加到 UsernamePasswordAuthenticationFilter 之前
                // 确保在处理用户名和密码的认证之前先进行 JWT 的验证。
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    //认证管理器AuthenticationManager:负责处理身份验证逻辑
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception{
        return authenticationConfiguration.getAuthenticationManager();
    }

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

修改UserServiceImpl,密码加密

// 在UserServiceImpl中添加
@Autowired
private PasswordEncoder passwordEncoder;

@Override
@Transactional
public boolean register(User user) {
    // 检查用户名是否已存在
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(User::getUsername, user.getUsername());
    if (userMapper.selectCount(wrapper) > 0) {
        return false;
    }
    // 密码加密
    user.setPassword(passwordEncoder.encode(user.getPassword()));
    int rows = userMapper.insert(user);
    return rows > 0;
}

修改控制器controller:处理用户登录、注册等请求并生成JWT

// 在UserController中添加
//Spring Security 认证管理器,验证用户的凭证(如用户名和密码)是否有效
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtil jwtUtil;

@PostMapping("/login")
public Result<Map<String, Object>> login(@RequestBody Map<String, String> loginInfo) {
    String username = loginInfo.get("username");
    String password = loginInfo.get("password");
    
    // 使用Spring Security进行认证
    //基于用户名,密码的身份认证请求
    UsernamePasswordAuthenticationToken authToken = 
            new UsernamePasswordAuthenticationToken(username, password);
    //验证用户凭据
    Authentication authentication = authenticationManager.authenticate(authToken);
    //设置认证上下文
    SecurityContextHolder.getContext().setAuthentication(authentication);
    
    // 生成JWT令牌
    User user = userService.findByUsername(username); // 需要新增该方法
    String token = jwtUtil.generateToken(user.getId(), username);
    
    Map<String, Object> data = new HashMap<>();
    data.put("token", token);
    data.put("userId", user.getId());
    data.put("username", user.getUsername());
    return Result.success(data);
}