Spring Boot 3.3.7整合Spring Security+JWT认证实战教程
在现代Web开发中,身份认证和授权是保障系统安全的关键环节。Spring Security作为Spring生态中的安全框架,提供了强大的安全功能,而JWT(JSON Web Token)则是一种轻量级的无状态认证机制。本文将详细介绍如何在Spring Boot 3.3.7项目中整合Spring Security和JWT,实现安全的认证与授权功能。
一、项目初始化
1. 创建Spring Boot项目
- 创建一个Spring Boot项目。
- 选择以下依赖项:
Spring Web
Spring Security
Lombok
commons-lang3
jjwt-jackson
jjwt-impl
jjwt-api
2. 引入依赖
在pom.xml
中添加以下依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<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>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
二、JWT工具类
1. 创建JwtUtil
类
JwtUtil
类用于生成和解析JWT Token。以下是关键代码:
private static final String SECRET_KEY = "1234567890123456789012345678901234567890";
public static String getUsername(String token) {
try{
String username = extractClaim(token, Claims::getSubject);
return username;
}catch (Exception e){
e.printStackTrace();
return "";
}
}
public static Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public static <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
public static Claims extractAllClaims(String token) {
byte[] keyBytes = SECRET_KEY.getBytes(StandardCharsets.UTF_8);
SecretKey key = Keys.hmacShaKeyFor(keyBytes);
return Jwts.parserBuilder()
.setSigningKey(key) // 使用 SecretKey 进行签名验证
.build()
.parseClaimsJws(token)
.getBody();
}
private static Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public static String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, username);
}
public static String createToken(Map<String, Object> claims, String subject) {
byte[] keyBytes = SECRET_KEY.getBytes(StandardCharsets.UTF_8);
SecretKey key = Keys.hmacShaKeyFor(keyBytes);
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 )) // 设置过期时间为1小时后
.signWith(key, SignatureAlgorithm.HS256) // 使用 SecretKey 和指定的签名算法
.compact();
}
三、自定义用户详情服务
1. 创建CustomUserDetailsService
类
CustomUserDetailsService
类实现了UserDetailsService
接口,用于加载用户信息。以下是关键代码:
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 示例:硬编码用户(实际应从数据库读取)
if ("admin".equals(username)) {
// 构建并返回一个 UserDetails 对象,包含用户名、加密后的密码和角色信息
return User.builder()
.username("admin")
.password(passwordEncoder.encode("123456"))
.roles("ADMIN")
.build();
}
// 如果用户名不存在,抛出 UsernameNotFoundException 异常
throw new UsernameNotFoundException("用户不存在");
}
}
四、JWT过滤器
1. 创建JwtFilter
类
JwtFilter
类继承自OncePerRequestFilter
,用于拦截请求并验证JWT Token。以下是关键代码:
public class JwtFilter extends OncePerRequestFilter {
private static final String TOKEN_HEADER = "token";
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain)
throws ServletException, IOException {
// 1. 获取请求头中的Token
String requestToken = request.getHeader(TOKEN_HEADER);
String requestURI = request.getRequestURI();
if ("/login".equals(requestURI)){
chain.doFilter(request, response);
return;
}
if(StringUtils.isBlank(requestToken)){
// 如果Token为空,返回401未授权状态码和错误信息
response.setContentType("application/json;charset=UTF-8");
response.setStatus(401);
response.getWriter().write("""
{
"code": 401,
"msg": "token为空",
"timestamp": %d
}
""".formatted( System.currentTimeMillis()));
return;
}
// 从Token中解析出用户名
String username = JwtUtil.getUsername(requestToken);
if(StringUtils.isNoneBlank(username)){
List<String> roles = List.of("ADMIN","USER");
List<SimpleGrantedAuthority> list = roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)).toList();
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username,null,list);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
chain.doFilter(request, response);
}else {
// 如果Token无效,返回401未授权状态码和错误信息
log.info("token错误");
response.setContentType("application/json;charset=UTF-8");
response.setStatus(401);
response.getWriter().write("""
{
"code": 401,
"msg": "token错误",
"timestamp": %d
}
""".formatted( System.currentTimeMillis()));
}
}
}
五、安全配置
1. 创建SecurityConfig
类
SecurityConfig
类用于配置Spring Security的安全规则。以下是关键代码:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// 禁用CSRF保护
.csrf(AbstractHttpConfigurer::disable)
// 配置请求的授权规则
.authorizeHttpRequests(auth -> auth
.requestMatchers("/get").hasRole("ADMIN") // 只有ADMIN角色可以访问/get路径
.requestMatchers("/login").permitAll() // 允许所有用户访问/login路径
.anyRequest().authenticated() // 其他所有请求都需要认证
)
// 在认证流程前添加JwtFilter过滤器
.addFilterBefore(
new JwtFilter(),
UsernamePasswordAuthenticationFilter.class
)
// 禁用表单登录
.formLogin(AbstractHttpConfigurer::disable)
// 禁用会话管理
.sessionManagement(SessionManagementConfigurer::disable)
// 配置异常处理
.exceptionHandling(exception -> exception
.authenticationEntryPoint(new CustomAuthenticationEntryPoint()) // 处理认证异常
.accessDeniedHandler(new CustomAccessDeniedHandler()) // 处理授权异常
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
六、认证入口与异常处理
1. 创建CustomAuthenticationEntryPoint
类
CustomAuthenticationEntryPoint
类用于处理认证失败的逻辑。以下是关键代码:
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException ex)
throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(401);
response.getWriter().write("""
{
"code": 401,
"msg": "身份认证失败: %s",
"timestamp": %d
}
""".formatted(ex.getMessage(), System.currentTimeMillis()));
}
}
2. 创建CustomAccessDeniedHandler
类
CustomAccessDeniedHandler
类用于处理权限不足的逻辑。以下是关键代码:
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException ex)
throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(403);
response.getWriter().write("""
{
"code": 403,
"msg": "权限不足: %s",
"requiredRole": "ADMIN", // 示例动态字段
"timestamp": %d
}
""".formatted(ex.getMessage(), System.currentTimeMillis()));
}
七、登录控制器
1. 创建LoginController
类
LoginController
类提供登录接口,生成JWT Token。以下是关键代码:
@RestController
public class LoginController {
private final AuthenticationManager authenticationManager;
public LoginController(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@PostMapping("/login")
public ResponseEntity<String> login(@RequestBody LoginRequest request) {
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getUsername(),
request.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
String token = JwtUtil.generateToken(request.getUsername());
return ResponseEntity.ok("登录成功:"+token);
} catch (Exception e) {
return ResponseEntity.status(401).body("认证失败: " + e.getMessage());
}
}
@GetMapping("get")
public String get(){
return "success";
}
// 登录请求参数类
@Data
public static class LoginRequest {
private String username;
private String password;
}
}
八、运行与测试
1. 启动项目
- 启动
SkyApplication
,访问http://localhost:8080/login
进行登录测试。 - 示例请求体:
{ "username": "admin", "password": "123456" }
- 登录成功后,返回JWT Token。
2. 访问受保护资源
- 示例:访问
http://localhost:8080/get
,需要在请求头中添加token
字段。
九、总结
本文详细介绍了如何在Spring Boot 3.3.7中整合Spring Security和JWT,实现了安全的认证与授权功能。通过自定义用户详情服务、JWT过滤器和安全配置,我们构建了一个灵活且安全的认证系统。希望本文对你有所帮助!
源码地址:
https://download.youkuaiyun.com/download/Crazy7426/90448031