在现代 web 应用程序中,安全性尤为重要。Spring Boot 3 和 Spring Security 6 提供了强大的安全功能,通过 JWT(JSON Web Token)和 Redis 的结合,可以实现高效的身份认证与授权。本文将介绍如何基于 Spring Boot 3、Spring Security 6、JWT 和 Redis 实现用户登录及 token 身份认证。
1. 项目依赖
首先,我们需要在 pom.xml
中添加相关的依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2. 配置 Redis
在 application.yml
中,我们需要配置 Redis 连接信息:
spring:
redis:
host: localhost
port: 6379
3. JWT 工具类
接下来,我们创建一个 JWT 工具类,用于生成和解析 token。
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;
@Component
public class JwtUtil {
private final String SECRET_KEY = "your_secret_key"; // 密钥
private final long EXPIRATION_TIME = 86400000; // 1天
// 生成 token
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, username);
}
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() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
// 验证 token
public boolean validateToken(String token, String username) {
final String extractedUsername = extractUsername(token);
return (extractedUsername.equals(username) && !isTokenExpired(token));
}
public String extractUsername(String token) {
return extractAllClaims(token).getSubject();
}
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
private boolean isTokenExpired(String token) {
return extractAllClaims(token).getExpiration().before(new Date());
}
}
4. 用户登录控制器
实现一个控制器,处理用户的登录请求。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UserService userService; // 假设存在一个服务类处理用户逻辑
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
// 验证用户
User user = userService.validateUser(loginRequest.getUsername(), loginRequest.getPassword());
if (user != null) {
String token = jwtUtil.generateToken(user.getUsername());
return ResponseEntity.ok(new AuthResponse(token));
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("无效的用户名或密码");
}
}
}
5. Security 配置
最后,我们需要配置 Spring Security,以支持 JWT 的身份认证。
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.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtRequestFilter jwtRequestFilter; // 自定义的过滤器
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("/api/auth/login").permitAll()
.anyRequest().authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
6. 自定义 JWT 过滤器
我们还需要创建一个 JWT 过滤器,以便在请求中解析和验证 token。
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.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;
public class JwtRequestFilter 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 = userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails.getUsername())) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
总结
通过以上步骤,我们成功地实现了一个基于 Spring Boot 3、Spring Security 6、JWT 和 Redis 的身份认证示例。用户可以通过登录接口获取 token,后续请求只需携带该 token 即可完成身份验证。这种方式不仅安全可靠,而且性能优越,适合于现代分布式系统中的身份认证场景。
请注意,以上代码仅为示例,实际应用中需要根据具体需求进行相应的调整和完善。同时,不要忘记进行安全性加固,例如对密码进行加密存储、设置适当的 token 过期时间等。