Modern Architecture
& Coding Solutions

Spring Security 6 + JWT + OAuth2 实战:从零构建安全认证中心

2026 年,Spring Security 6 已成为 Java 后端安全的绝对标配。但许多开发者仍在使用过时的 WebSecurityConfigurerAdapter,或对 JWT 的无状态校验、OAuth2 的授权码流程一知半解。本文将从零搭建一个生产级认证中心,涵盖 Spring Security 6 新配置范式、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>
    <!-- 密码编码 (BCrypt) -->
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-crypto</artifactId>
    </dependency>
</dependencies>

注意:jjwt 0.12.x 的 API 与 0.11.x 有较大变化,本文使用最新的构建器模式。


三、用户存储与认证逻辑

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

Spring Security 通过 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("用户不存在"));

        // 将逗号分隔的角色转为 GrantedAuthority 列表
        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
        );
    }
}

3.3 密码编码器

在 Security 配置中声明 BCryptPasswordEncoder Bean,它使用强度为 10 的哈希,安全性足够。

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

四、JWT 工具类(签发与校验)

jjwt 0.12.x 使用 Jwts.builder() 构建令牌,并支持 CompactJwsBuilder 链式调用。

@Component
public class JwtUtils {

    @Value("${jwt.secret}")
    private String secret; // 建议从环境变量读取,至少 256 位

    @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)), Jwts.SIG.HS256)
                .compact();
    }

    // 解析 JWT 并获取 Claims
    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();
    }
}

五、Spring Security 6 核心配置(抛弃 WebSecurityConfigurerAdapter)

Spring Security 6 推荐使用 SecurityFilterChain Bean + Lambda DSL。

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true) // 启用方法级权限注解
public class SecurityConfig {

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Autowired
    private JwtUtils jwtUtils;

    // 1. 配置密码编码器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // 2. 配置认证管理器(使用 DaoAuthenticationProvider)
    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder());
        return http.authenticationProvider(authProvider).getSharedObject(AuthenticationManager.class);
    }

    // 3. 核心过滤器链:禁用 CSRF、设置无状态 Session、配置 JWT 过滤器
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManager authManager) throws Exception {
        // JWT 认证过滤器(自定义)
        JwtAuthenticationFilter jwtFilter = new JwtAuthenticationFilter(jwtUtils, userDetailsService);

        http
            .csrf(csrf -> csrf.disable()) // 无状态 API 无需 CSRF
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/auth/login", "/auth/register", "/oauth2/**").permitAll()
                .anyRequest().authenticated()
            )
            .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
            .authenticationManager(authManager);

        return http.build();
    }
}

5.1 JwtAuthenticationFilter(核心过滤器)

该过滤器拦截每个请求,从 Authorization Header 提取 JWT 并校验,将用户信息放入 SecurityContext。

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;
    }
}

六、登录接口与注册接口

6.1 登录控制器

@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) {
        // 1. 执行认证
        Authentication authentication = authManager.authenticate(
                new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
        );

        // 2. 获取用户信息
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        List<String> roles = userDetails.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList());

        // 3. 生成 JWT
        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("注册成功");
    }
}

6.2 请求/响应 DTO

public class LoginRequest {
    private String username;
    private String password;
    // getters/setters
}

public class JwtResponse {
    private String token;
    private String type = "Bearer";
    // constructor, getters
}

七、RBAC 权限控制(方法级注解)

Spring Security 6 通过 @PreAuthorize 实现方法级权限校验,需在配置类上开启 @EnableMethodSecurity

@RestController
@RequestMapping("/admin")
public class AdminController {

    @PreAuthorize("hasRole('ADMIN')")
    @GetMapping("/users")
    public List<User> listAllUsers() {
        // 只有 ADMIN 可访问
        return userService.findAll();
    }

    @PreAuthorize("hasAuthority('SCOPE_write')") // 可用于 OAuth2 scope
    @PostMapping("/data")
    public String updateData() {
        return "Data updated";
    }
}

权限表达式支持 hasRolehasAuthorityhasAnyRole@ 自定义 Bean 方法调用等。


八、OAuth2 三方登录集成(以 GitHub 为例)

Spring Security 6 内置 OAuth2 客户端支持,只需配置 application.yml

8.1 依赖(已包含在 spring-boot-starter-security 中)

无需额外依赖,但需添加 spring-security-oauth2-client 自动配置(Spring Boot 已包含)。

8.2 配置 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

8.3 自定义 OAuth2 登录成功处理器

登录成功后,我们根据 GitHub 用户信息生成自己的 JWT 令牌,返回给前端。

@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);
        });

        // 生成 JWT 并重定向到前端回调地址
        String jwt = jwtUtils.generateToken(user.getUsername(), List.of(user.getRoles().split(",")));
        response.sendRedirect("http://localhost:3000/oauth2/redirect?token=" + jwt);
    }
}

SecurityConfigfilterChain 中添加 OAuth2 相关配置:

http
    .oauth2Login(oauth2 -> oauth2
        .successHandler(oAuth2LoginSuccessHandler)
        .loginPage("/oauth2/authorization/github") // 触发 GitHub 登录的端点
    )
    .authorizeHttpRequests(auth -> auth
        .requestMatchers("/auth/**", "/oauth2/**").permitAll()
        .anyRequest().authenticated()
    );

之后访问 /oauth2/authorization/github 即跳转到 GitHub 授权页,授权后回调并携带 JWT。


九、测试与验证

启动应用后,可使用 curl 测试:

# 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..."

若 test 用户只有 ROLE_USER,访问 /admin/users 将返回 403 Forbidden,证明 RBAC 生效。


十、生产环境最佳实践

  1. JWT Secret 管理:使用环境变量或配置中心,长度至少 256 位(HS256),推荐使用 RSA 非对称加密(更安全,可公钥校验)。
  2. 令牌刷新:JWT 过期后,可通过 /refresh 接口,用 Refresh Token 换取新令牌(本文未实现,但可参考 jjwt 的 Refresh Token 模式)。
  3. 日志与监控:集成 MDC 记录登录失败、令牌过期等事件,参考本站 《Java 日志最佳实践 2026》(待发布,可先关联 《Java 应用接入 Prometheus + Grafana 全记录》 监控部分)。
  4. 多因素认证:Spring Security 6 支持集成 TOTP(Google Authenticator),可通过扩展 AuthenticationProvider 实现。
  5. 容器化部署:参考 《Spring Boot 3.4 Docker 镜像最佳实践》 制作镜像,将 Secret 作为容器环境变量注入。

十一、总结

本文完整实现了基于 Spring Security 6 + JWT + OAuth2 的认证中心,涵盖了从用户存储、JWT 工具、无状态过滤器、RBAC 权限到三方登录的全链路。Spring Security 6 的 Lambda 配置更简洁,与 Spring Boot 3.x 深度融合,让安全模块不再是“黑盒”。掌握这套方案,你就能为微服务架构构建坚实的身份与权限基座。

未来可扩展方向:支持多租户、集成 Keycloak 作为外部认证、使用 Spring Authorization Server 构建完整 OAuth2 授权服务器。


📌 系列拓展阅读


📚 参考文献

  1. Spring Security 官方文档. 6.4.1 Reference. https://docs.spring.io/spring-security/reference/index.html
  2. JJWT GitHub. 0.12.6 API. https://github.com/jwtk/jjwt
  3. OAuth 2.0 标准. RFC 6749. https://datatracker.ietf.org/doc/html/rfc6749
  4. Baeldung. Spring Security 6 with JWT. https://www.baeldung.com/spring-security-6-jwt
  5. Spring Blog. Spring Security 6.0 Migration Guide. https://spring.io/blog/2022/05/12/spring-security-6-0
  6. 本站. Java 应用接入 Prometheus + Grafana 全记录. https://www.macs.vip/archives/835
赞(0) 打赏
未经允许不得转载:MACS Dev Hub » Spring Security 6 + JWT + OAuth2 实战:从零构建安全认证中心

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续提供更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫

微信扫一扫

登录

找回密码

注册