在采用基于Token的认证机制(如JWT,JSON Web Token)时,Spring Security 6 提供了灵活的方式来进行配置和集成。以下是详细的步骤和关键类、方法,帮助你实现一个基于Token的安全认证机制。
1. 添加依赖
首先,确保你的项目中包含必要的依赖。对于使用JWT的场景,通常需要添加以下依赖:
<!-- 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> <!-- or jjwt-gson if Gson is preferred -->
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
2. 配置SecurityFilterChain
接下来,在Spring Security配置类中设置SecurityFilterChain
,以支持基于Token的认证机制。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception {
http
.csrf().disable() // 禁用CSRF保护
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 设置为无状态会话
.and()
.authorizeHttpRequests(authorize -> authorize
.antMatchers("/auth/**").permitAll() // 公开路径,例如登录和注册
.anyRequest().authenticated() // 其他所有请求都需要认证
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); // 添加自定义JWT过滤器
return http.build();
}
}
3. 实现JWT工具类
创建一个工具类来生成和解析JWT令牌。
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtUtil {
private String secret = "yourSecretKey"; // 应该存储在一个安全的地方,并且不要硬编码
// 从token中提取用户名
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
// 从token中提取过期时间
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
// 提取单个声明
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
// 解析token的所有声明
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
// 检查token是否过期
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
// 生成token
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
// 创建token
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10小时过期
.signWith(SignatureAlgorithm.HS256, secret).compact();
}
// 验证token
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
4. 实现JWT认证过滤器
创建一个自定义的过滤器来处理JWT验证逻辑。
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
5. 实现用户认证服务
确保你有一个实现了UserDetailsService
的类来加载用户信息。
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 {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 这里可以替换为从数据库或其他数据源加载用户信息的逻辑
return new org.springframework.security.core.userdetails.User(
username,
"password",
AuthorityUtils.createAuthorityList("ROLE_USER")
);
}
}
6. 控制器示例
创建一个控制器来生成JWT令牌。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.*;
@RestController
public class AuthController {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UserDetailsService userDetailsService;
@PostMapping("/auth/login")
public String createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {
final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
if (!userDetails.getPassword().equals(authenticationRequest.getPassword())) {
throw new Exception("Invalid credentials");
}
final String jwt = jwtUtil.generateToken(userDetails);
return jwt;
}
}
class AuthenticationRequest {
private String username;
private String password;
// Getters and Setters
}
总结
通过上述步骤,你可以成功地在Spring Security 6中实现基于JWT的认证机制。主要步骤包括:
- 添加依赖:引入必要的依赖。
- 配置SecurityFilterChain:禁用CSRF保护并设置为无状态会话。
- 实现JWT工具类:用于生成和解析JWT令牌。
- 实现JWT认证过滤器:处理JWT验证逻辑。
- 实现用户认证服务:加载用户信息。
- 创建控制器:提供生成JWT令牌的接口。
这样,你的应用程序就可以使用JWT进行安全认证和授权了。