2026 年,Spring Security 6 已成为 Java 后端安全的绝对标配。但许多开发者仍在使用过时的
WebSecurityConfigurerAdapter,或对 JWT 的无状态校验、OAuth2 的授权码流程一知半解。本文将从零搭建一个生产级认证中心,涵盖 Spring Security 6 新配置范式(Lambda DSL)、JWT 令牌生成与解析、RBAC 权限控制、OAuth2 三方登录四大模块,代码完整可运行。
一、为什么需要认证中心?
在微服务架构中,每个服务都独立做身份验证是灾难性的。认证中心(Authorization Server) 集中处理登录、颁发令牌,各业务服务通过令牌校验身份,从而实现 SSO(单点登录) 和统一权限管理。
Spring Security 6 与 Spring Boot 3.x 配合,弃用了旧版 WebSecurityConfigurerAdapter,全面拥抱 Lambda DSL + 组件化配置。我们将基于 Spring Boot 3.4.2 + Spring Security 6.4.1 构建。

二、项目初始化与依赖
使用 Spring Initializr 创建项目,依赖如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.2</version>
</parent>
<dependencies>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Web 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JWT 操作 (jjwt 0.12.x) -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<!-- 数据库与 JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
注意:jjwt 0.12.x 的 API 与 0.11.x 有较大变化,
parserBuilder()方法已被移除,需使用新的Jwts.parser()构建方式。
三、用户存储与认证逻辑
3.1 用户实体与 Repository
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String username;
private String password; // 存储 BCrypt 哈希
private String email;
private String roles; // 如 "ROLE_USER,ROLE_ADMIN"
// getters & setters
}
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}
3.2 自定义 UserDetailsService
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
List<GrantedAuthority> authorities = Arrays.stream(user.getRoles().split(","))
.map(role -> new SimpleGrantedAuthority(role.trim()))
.collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
authorities
);
}
}
四、Spring Security 6 核心配置(Lambda DSL)
Spring Security 6 的最大变化是移除了 WebSecurityConfigurerAdapter,改用 SecurityFilterChain Bean + Lambda DSL 进行配置。
旧版(Spring Security 5.x)已废弃的方式:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated();
}
}
新版(Spring Security 6.x)Lambda DSL 方式:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity // 替代 @EnableGlobalMethodSecurity
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http,
CustomUserDetailsService userDetailsService,
PasswordEncoder passwordEncoder) throws Exception {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder);
return http.authenticationProvider(authProvider).getSharedObject(AuthenticationManager.class);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http,
JwtAuthenticationFilter jwtFilter) throws Exception {
http
// Lambda DSL:csrf() 和 cors() 现在需要显式配置
.csrf(csrf -> csrf.disable())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
// 新的 authorizeHttpRequests() 替代 authorizeRequests()
.authorizeHttpRequests(authz -> authz
.requestMatchers("/auth/login", "/auth/register", "/oauth2/**").permitAll() // antMatchers → requestMatchers
.anyRequest().authenticated()
)
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
关键变化总结:
| 旧版(Spring Security 5.x) | 新版(Spring Security 6.x) |
|---|---|
extends WebSecurityConfigurerAdapter | @Bean SecurityFilterChain |
authorizeRequests() | authorizeHttpRequests() |
antMatchers() | requestMatchers() |
链式 .and() 调用 | Lambda DSL |
@EnableGlobalMethodSecurity | @EnableMethodSecurity |
五、JWT 工具类(jjwt 0.12.x)
jjwt 0.12.x 的 API 发生了重大变化,parserBuilder() 已被移除,需使用新的 Jwts.parser() 构建方式。
@Component
public class JwtUtils {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expirationMs}")
private int expirationMs;
// 生成 JWT
public String generateToken(String username, List<String> roles) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expirationMs);
return Jwts.builder()
.subject(username)
.claim("roles", roles)
.issuedAt(now)
.expiration(expiryDate)
.signWith(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)))
.compact();
}
// 解析 JWT(jjwt 0.12.x 新 API)
public Claims getClaims(String token) {
return Jwts.parser()
.verifyWith(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)))
.build()
.parseSignedClaims(token)
.getPayload();
}
public boolean validateToken(String token) {
try {
getClaims(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
public String getUsername(String token) {
return getClaims(token).getSubject();
}
}
六、JWT 认证过滤器
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtils jwtUtils;
private final CustomUserDetailsService userDetailsService;
public JwtAuthenticationFilter(JwtUtils jwtUtils, CustomUserDetailsService userDetailsService) {
this.jwtUtils = jwtUtils;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String token = extractJwtFromRequest(request);
if (token != null && jwtUtils.validateToken(token)) {
String username = jwtUtils.getUsername(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
private String extractJwtFromRequest(HttpServletRequest request) {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
return header.substring(7);
}
return null;
}
}
七、登录与注册接口
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private AuthenticationManager authManager;
@Autowired
private JwtUtils jwtUtils;
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
Authentication authentication = authManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
);
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
List<String> roles = userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
String token = jwtUtils.generateToken(userDetails.getUsername(), roles);
return ResponseEntity.ok(new JwtResponse(token));
}
@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody RegisterRequest request) {
if (userRepository.findByUsername(request.getUsername()).isPresent()) {
return ResponseEntity.badRequest().body("用户名已存在");
}
User user = new User();
user.setUsername(request.getUsername());
user.setPassword(passwordEncoder.encode(request.getPassword()));
user.setEmail(request.getEmail());
user.setRoles("ROLE_USER");
userRepository.save(user);
return ResponseEntity.ok("注册成功");
}
}
八、RBAC 权限控制(方法级注解)
通过 @PreAuthorize 实现方法级权限校验:
@RestController
@RequestMapping("/admin")
public class AdminController {
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/users")
public List<User> listAllUsers() {
return userService.findAll();
}
@PreAuthorize("hasAuthority('SCOPE_write')")
@PostMapping("/data")
public String updateData() {
return "Data updated";
}
}
九、OAuth2 三方登录(以 GitHub 为例)
9.1 配置 application.yml
spring:
security:
oauth2:
client:
registration:
github:
client-id: ${GITHUB_CLIENT_ID}
client-secret: ${GITHUB_CLIENT_SECRET}
scope: read:user
provider:
github:
authorization-uri: https://github.com/login/oauth/authorize
token-uri: https://github.com/login/oauth/access_token
user-info-uri: https://api.github.com/user
user-name-attribute: id
9.2 OAuth2 登录成功处理器
@Component
public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
private JwtUtils jwtUtils;
@Autowired
private UserRepository userRepository;
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException {
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
Map<String, Object> attributes = token.getPrincipal().getAttributes();
String githubId = attributes.get("id").toString();
String name = (String) attributes.get("login");
User user = userRepository.findByUsername(githubId).orElseGet(() -> {
User newUser = new User();
newUser.setUsername(githubId);
newUser.setPassword(UUID.randomUUID().toString());
newUser.setRoles("ROLE_USER");
return userRepository.save(newUser);
});
String jwt = jwtUtils.generateToken(user.getUsername(), List.of(user.getRoles().split(",")));
response.sendRedirect("http://localhost:3000/oauth2/redirect?token=" + jwt);
}
}
9.3 在 SecurityConfig 中配置 OAuth2 登录
http
.oauth2Login(oauth2 -> oauth2
.successHandler(oAuth2LoginSuccessHandler)
)
.authorizeHttpRequests(authz -> authz
.requestMatchers("/auth/**", "/oauth2/**").permitAll()
.anyRequest().authenticated()
);
访问 /oauth2/authorization/github 即可跳转到 GitHub 授权页。
十、统一异常处理(生产环境必备)
Spring Security 的认证授权异常发生在过滤器层面,@RestControllerAdvice 无法捕获,需单独配置:
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"code\":\"unauthorized\",\"message\":\"请先登录\"}");
}
}
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json");
response.getWriter().write("{\"code\":\"forbidden\",\"message\":\"权限不足\"}");
}
}
在 SecurityConfig 中挂接:
http.exceptionHandling(exceptions -> exceptions
.authenticationEntryPoint(customAuthenticationEntryPoint)
.accessDeniedHandler(customAccessDeniedHandler)
);
十一、测试验证
# 1. 注册用户
curl -X POST http://localhost:8080/auth/register \
-H "Content-Type: application/json" \
-d '{"username":"test","password":"123456","email":"test@demo.com"}'
# 2. 登录获取 JWT
curl -X POST http://localhost:8080/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"test","password":"123456"}'
# 返回: {"token":"eyJ...", "type":"Bearer"}
# 3. 访问受保护接口
curl -X GET http://localhost:8080/admin/users \
-H "Authorization: Bearer eyJ..."
十二、生产环境最佳实践
- JWT Secret 管理:使用环境变量或配置中心,长度至少 256 位
- 令牌刷新:可结合 Redis 实现 Refresh Token 机制
- 日志与审计:结合 MDC 记录登录日志,参考本站 《Java 日志最佳实践 2026》
- 监控告警:接入 Prometheus 监控认证服务,参考 《Java 应用接入 Prometheus + Grafana 全记录》
- 容器化部署:参考 《Spring Boot 3.4 Docker 镜像最佳实践》
十三、总结
本文完整实现了基于 Spring Security 6 + JWT + OAuth2 的认证中心,核心要点:
- Lambda DSL 替代
WebSecurityConfigurerAdapter,配置更简洁 - jjwt 0.12.x 新 API(
Jwts.parser()替代parserBuilder()) @EnableMethodSecurity替代@EnableGlobalMethodSecurityrequestMatchers()替代antMatchers()- OAuth2 登录与 JWT 无缝集成
掌握这套方案,你就能为微服务架构构建坚实的身份与权限基座。
📌 系列拓展阅读:
- 《Java 日志最佳实践 2026:从 SLF4J 到 ELK 全链路日志追踪》——安全审计日志与 MDC 结合
- 《Java 应用接入 Prometheus + Grafana 全记录》——监控认证服务的健康状态
- 《Spring Boot 3.4 Docker 镜像最佳实践(含分层构建)》——容器化部署安全配置
- 《Arthas 与火焰图:Java 生产环境在线诊断从入门到精通》——线上问题诊断
📚 参考文献:
- Spring Security 官方文档. 6.4 Reference. https://docs.spring.io/spring-security/reference/
- JJWT GitHub. 0.12.6 API. https://github.com/jwtk/jjwt
- Baeldung. Migrate Application from Spring Security 5 to Spring Security 6. https://www.baeldung.com/spring-security-migrate-5-to-6
- Spring Security 6.x Migration Guide. WebSecurityConfigurerAdapter → SecurityFilterChain. https://github.com/giuseppe-trisciuoglio/developer-kit
- CSDN. Spring Security6 & OAuth2 实战:从零构建多场景认证授权系统. https://blog.csdn.net/wine/article/details/152388559









