相关依赖
首先需要创建一个基础SpringBoot工程项目
代码片段...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.3</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<scope>provided</scope>
</dependency>
</dependencies>Security和JWT依赖单独列在这里
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 引入3个JWT依赖jjwt-api,jjwt-impl,jjwt-jackson -->
<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>配置JWT
写一个jwt工具类,用于生成token,验证等操作
package com.chens.common.security;
import io.jsonwebtoken.*;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.Date;
/**
* JWT工具类 jwtTokenUtils
* @description 用于生成和验证token令牌
* @author chensh
* @date 2025/08/01 16:22
*/
@Component
public class JwtTokenUtils {
@Value("${app.jwt-secret}")
private String jwtSecret;
@Value("${app.jwt-expiration-ms}")
private int jwtExpirationMs;
public String generateToken(Authentication authentication) {
UserDetails userPrincipal = (UserDetails) authentication.getPrincipal();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpirationMs);
return Jwts.builder()
.setSubject(userPrincipal.getUsername())
.setIssuedAt(new Date())
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
public String getUsernameFromJWT(String token) {
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
public boolean validateToken(String authToken) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
return true;
} catch (SignatureException ex) {
// Invalid JWT signature
} catch (MalformedJwtException ex) {
// Invalid JWT token
} catch (ExpiredJwtException ex) {
// Expired JWT token
} catch (UnsupportedJwtException ex) {
// Unsupported JWT token
} catch (IllegalArgumentException ex) {
// JWT claims string is empty
}
return false;
}
public String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}有两个参数需要配置,到配置文件中配置,配置完再进行下一步
`jwtSecret`: token生成的密钥(需要自己生成)
`jwtExpirationMs`: token有效事件
jwtSecret生成工具类:
/**
* 生成JWT密钥
*/
public class KeyGeneratorUtil {
public static void main(String[] args) {
SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[256]; // 256位密钥
secureRandom.nextBytes(key);
String base64Key = Base64.getEncoder().encodeToString(key);
System.out.println("JWT Secret: " + base64Key);
}
}配置Security
需要配置核心的五个类:
过滤器:用于对请内的token进行解析,创建认证对象,交给Security上下文处理
未授权请求处理器:处理没有权限的请求,并自定义响应体
认证入口类:处理未认证/没有token请求/token失效,自定义响应体
自定义用户查询类:用于查询数据库的用户,提供给Security上下文
SpringSecurity配置类:用于配置SpringSecurity的安全策略、认证方式、授权方式等
1、过滤器 JwtAuthenticationFilter
package com.chens.common.filter;
import com.chens.common.exception.UnauthorizedException;
import com.chens.common.security.JwtTokenUtils;
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.authentication.UsernamePasswordAuthenticationToken;
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.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
/**
* JWT认证过滤器
* @description 用于所有请求进来之前,从请求头中解析JWT令牌并进行认证
* @author chensh
* @date 2025/08/01 16:22
*/
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenUtils jwtTokenUtils;
private final UserDetailsService userDetailsService;
@Autowired
public JwtAuthenticationFilter(JwtTokenUtils jwtTokenUtils, UserDetailsService userDetailsService) {
this.jwtTokenUtils = jwtTokenUtils;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 从 request 获取 JWT token
String jwt = jwtTokenUtils.getJwtFromRequest(request);
if (StringUtils.hasText(jwt)) {
// 验证 JWT token
if (!jwtTokenUtils.validateToken(jwt)) {
throw new UnauthorizedException("Invalid or expired JWT token");
}
// 从 JWT token 中获取用户名
String username = jwtTokenUtils.getUsernameFromJWT(jwt);
// 从数据库中查询用户信息
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 创建认证对象
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
// 设置认证详情
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 将认证对象设置到 SecurityContextHolder 中
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
}2、未授权请求处理器 CustomAccessDeniedHandler
package com.chens.common.security;
import com.chens.payload.ApiResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import java.io.IOException;
/**
* 自定义访问拒绝处理器
* @description 用于处理未授权请求(接口没有权限),返回403禁止访问状态码和JSON格式的错误信息
* @author chenshun
* @date 2023/08/10 16:22
*/
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
ApiResponse<?> apiResponse = ApiResponse.error(HttpStatus.FORBIDDEN.value(), "未授权");
response.getWriter().write(new ObjectMapper().writeValueAsString(apiResponse));
}
}3、认证入口类 CustomAuthenticationEntryPoint
package com.chens.common.security;
import com.chens.payload.ApiResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import java.io.IOException;
/**
* 自定义认证入口点
* @description 用于处理未认证请求(未登录/token失效),返回401未授权状态码和JSON格式的错误信息(
* @author chensh
* @date 2025/08/01 16:22
*/
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpStatus.UNAUTHORIZED.value());
ApiResponse<?> apiResponse = ApiResponse.error(HttpStatus.UNAUTHORIZED.value(), "认证失败:" + authException.getMessage());
response.getWriter().write(new ObjectMapper().writeValueAsString(apiResponse));
}
}4、自定义用户查询类 CustomUserDetailsService
package com.chens.common.security;
import com.chens.modules.system.model.User;
import com.chens.modules.system.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
/**
* 自定义用户详情服务
* @description 用于根据用户名或邮箱加载用户详情信息,并返回UserDetails对象,提供给SpringSecurity上下文使用
* @author chenshun
* @date 2023/08/10 16:22
*/
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
UserRepository userRepository;
@Override
@Transactional
public UserDetails loadUserByUsername(String usernameOrEmail) throws UsernameNotFoundException {
User user = userRepository.findByUsername(usernameOrEmail)
.orElseThrow(() -> new UsernameNotFoundException("User not found with username or email : " + usernameOrEmail));
return new org.springframework.security.core.userdetails.User(user.getUsername(),
user.getPassword(),
Collections.singletonList(new SimpleGrantedAuthority(user.getRole())));
}
}5、SpringSecurity配置类 SpringSecurityConfig
package com.chens.common.security;
import com.chens.common.filter.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.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
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;
/**
* SpringSecurity配置类
* @description 用于配置SpringSecurity的安全策略、认证方式、授权方式等
* @author chensh
* @date 2025/08/01 16:22
*/
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SpringSecurityConfig {
/**
* 自定义用户详情服务
*/
private final CustomUserDetailsService userDetailsService;
/**
* JWT工具类
*/
private final JwtTokenUtils jwtTokenUtils;
/**
* 自定义认证失败处理器
*/
private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
/**
* 自定义授权失败处理器
*/
private final CustomAccessDeniedHandler customAccessDeniedHandler;
@Autowired
public SpringSecurityConfig(CustomUserDetailsService userDetailsService, JwtTokenUtils jwtTokenUtils, CustomAuthenticationEntryPoint customAuthenticationEntryPoint, CustomAccessDeniedHandler customAccessDeniedHandler) {
this.userDetailsService = userDetailsService;
this.jwtTokenUtils = jwtTokenUtils;
this.customAuthenticationEntryPoint = customAuthenticationEntryPoint;
this.customAccessDeniedHandler = customAccessDeniedHandler;
}
/**
* 密码编码器
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
/**
* 认证管理器
*/
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
/**
* Security 配置安全过滤链
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// 由于使用的是JWT,我们这里不需要csrf
http.csrf(csrf -> csrf.disable());
// 禁用session
http.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
// 添加自定义未授权和未登录结果返回
http.exceptionHandling(customizer -> customizer
// 处理未登录
.authenticationEntryPoint(customAuthenticationEntryPoint)
// 处理未授权
.accessDeniedHandler(customAccessDeniedHandler));
// 配置请求授权
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/login").permitAll()
.requestMatchers("/api/uploads/**").permitAll()
.anyRequest().authenticated()
);
// 添加认证提供者
// http.authenticationProvider(authenticationProvider());
// 添加JWT认证过滤器,在Jwt过滤器中获取认证信息,然后将认证信息设置到SecurityContextHolder中
http.addFilterBefore(new JwtAuthenticationFilter(jwtTokenUtils, userDetailsService)
,UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}总结:核心配置类是 SpringSecurity配置类,然后其他4个类是自定义配置,过滤器是处理解析token的,剩下3各类,按照项目需要更改,如修改未认证,未授权异常情况的响应体,修改查询系统用户的逻辑。
评论区