云计算百科
云计算领域专业知识百科平台

SpringBoot 集成 OAuth2.0 资源服务器与授权服务器

上周有位读者在后台提问:“我们公司的系统要对外提供 API 接口,客户要求用 OAuth2.0 做安全认证,这该怎么实现呢?”

这确实是个很实际的需求。随着微服务和开放平台的兴起,OAuth2.0 已经成为 API 安全的事实标准。今天,我就从零开始,手把手带你实现 SpringBoot 中的 OAuth2.0 完整方案。

一、OAuth2.0为什么是“行业标准”?

在开始代码之前,我们先快速回顾一下 OAuth2.0 的核心概念。

OAuth2.0 要解决什么问题?想象一下,你想用微信登录某款 App,但又不愿把微信密码告诉这个 App。OAuth2.0 就是解决这个“授权代替认证”问题的标准方案。

四种授权模式对比:

模式

适用场景

安全级别

使用频率

授权码模式

Web 应用、移动端应用

★★★★★

密码模式

自家应用(信任环境)

★★☆☆☆

客户端模式

服务端之间调用

★★★☆☆

简化模式

纯前端应用(无后端)

★☆☆☆☆

今天的实战目标:我们要搭建一个完整的 OAuth2.0 系统,包括:

  • 1. 授权服务器(Authorization Server)- 负责颁发 Token

  • 2. 资源服务器(Resource Server)- 负责验证 Token 并返回资源

  • 3. 客户端应用(Client Application)- 获取 Token 并访问资源


  • 二、项目初始化与环境准备

    2.1 创建 SpringBoot 项目

    使用 Spring Initializr 创建项目,选择以下依赖:

    <dependencies>
        <!– SpringBoot Web –>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!– OAuth2 授权服务器 –>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
        </dependency>
        
        <!– OAuth2 资源服务器 –>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
        </dependency>
        
        <!– 数据库相关(存储客户端信息和用户信息) –>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        
        <!– 工具类 –>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    2.2 数据库配置

    # application.yml
    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/oauth2_demo?useSSL=false&serverTimezone=UTC
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
        
      jpa:
        hibernate:
          ddl-auto: update
        show-sql: true
        properties:
          hibernate:
            dialect: org.hibernate.dialect.MySQL8Dialect
            
    server:
      port: 8080


    三、授权服务器实现

    3.1 授权服务器配置类

    授权服务器是整个 OAuth2.0 体系的核心,负责管理客户端、用户认证和 Token 颁发。

    @Configuration
    @EnableWebSecurity
    public class AuthorizationServerConfig {
        
        /**
         * 配置 Spring Security
         */
        @Bean
        @Order(1)
        public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
            OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
            
            return http
                // 允许认证端点表单提交
                .formLogin(Customizer.withDefaults())
                .build();
        }
        
        /**
         * 配置授权服务器
         */
        @Bean
        public RegisteredClientRepository registeredClientRepository() {
            // 创建一个客户端注册信息
            RegisteredClient client = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("my-client")  // 客户端ID
                .clientSecret("{noop}my-secret")  // 客户端密钥,{noop}表示不加密
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)  // 认证方式
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)  // 授权码模式
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)  // 支持刷新Token
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)  // 客户端凭证模式
                .redirectUri("http://127.0.0.1:8080/login/oauth2/code/my-client")  // 回调地址
                .redirectUri("http://127.0.0.1:8080/authorized")  // 授权后的回调
                .scope(OidcScopes.OPENID)  // OpenID Connect 支持
                .scope("read")  // 自定义scope
                .scope("write")
                .clientSettings(ClientSettings.builder()
                    .requireAuthorizationConsent(true)  // 需要用户授权确认
                    .build())
                .build();
            
            // 可以注册多个客户端
            return new InMemoryRegisteredClientRepository(client);
        }
        
        /**
         * 配置 JWK Source(用于JWT签名)
         */
        @Bean
        public JWKSource<SecurityContext> jwkSource() {
            RSAKey rsaKey = generateRsa();
            JWKSet jwkSet = new JWKSet(rsaKey);
            return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
        }
        
        private static RSAKey generateRsa() {
            KeyPair keyPair = generateRsaKey();
            RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
            RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
            return new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
        }
        
        private static KeyPair generateRsaKey() {
            KeyPair keyPair;
            try {
                KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
                keyPairGenerator.initialize(2048);
                keyPair = keyPairGenerator.generateKeyPair();
            } catch (Exception ex) {
                throw new IllegalStateException(ex);
            }
            return keyPair;
        }
        
        /**
         * 配置 JWT 解码器
         */
        @Bean
        public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
            return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
        }
    }

    3.2 用户信息服务

    授权服务器需要知道用户信息才能进行认证:

    @Service
    public class CustomUserDetailsService implements UserDetailsService {
        
        /**
         * 实际项目中应该从数据库查询用户信息
         * 这里为了演示,使用内存数据
         */
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            // 模拟数据库查询
            Map<String, UserDetails> users = new HashMap<>();
            
            // 管理员用户
            users.put("admin", User.withUsername("admin")
                .password("{noop}admin123")  // {noop}表示不加密
                .roles("ADMIN", "USER")
                .authorities("read", "write")
                .build());
            
            // 普通用户
            users.put("user", User.withUsername("user")
                .password("{noop}user123")
                .roles("USER")
                .authorities("read")
                .build());
            
            UserDetails user = users.get(username);
            if (user == null) {
                throw new UsernameNotFoundException("用户不存在: " + username);
            }
            
            return user;
        }
    }

    3.3 配置用户认证

    @Configuration
    @EnableWebSecurity
    public class DefaultSecurityConfig {
        
        @Bean
        public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
            http
                .authorizeHttpRequests(authorize -> authorize
                    .requestMatchers("/oauth2/**", "/login/**").permitAll()  // 公开端点
                    .anyRequest().authenticated()  // 其他需要认证
                )
                .formLogin(form -> form
                    .loginPage("/login")  // 自定义登录页
                    .permitAll()
                )
                .csrf(csrf -> csrf.ignoringRequestMatchers("/oauth2/**"));  // 忽略OAuth2端点的CSRF
            
            return http.build();
        }
        
        @Bean
        public UserDetailsService userDetailsService() {
            return new CustomUserDetailsService();
        }
        
        @Bean
        public PasswordEncoder passwordEncoder() {
            // 实际项目中应该使用 BCryptPasswordEncoder
            return NoOpPasswordEncoder.getInstance();  // 仅用于演示,生产环境不安全!
        }
    }

    3.4 自定义登录页面

    <!– src/main/resources/templates/login.html –>
    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>OAuth2 授权登录</title>
        <style>
            body { font-family: Arial, sans-serif; max-width: 400px; margin: 50px auto; }
            .form-group { margin-bottom: 15px; }
            label { display: block; margin-bottom: 5px; }
            input { width: 100%; padding: 8px; box-sizing: border-box; }
            button { background: #007bff; color: white; border: none; padding: 10px 20px; cursor: pointer; }
            .error { color: red; margin-top: 10px; }
        </style>
    </head>
    <body>
        <h2>授权登录</h2>
        <div th:if="${param.error}" class="error">
            用户名或密码错误!
        </div>
        <form th:action="@{/login}" method="post">
            <div class="form-group">
                <label>用户名:</label>
                <input type="text" name="username" required>
            </div>
            <div class="form-group">
                <label>密码:</label>
                <input type="password" name="password" required>
            </div>
            <button type="submit">登录并授权</button>
        </form>
        
        <!– 显示可用的授权方式 –>
        <div style="margin-transform: translateY( 30px; border-top: 1px solid #eee; padding-top: 20px;">
            <h4>支持的授权方式:</h4>
            <ul>
                <li>授权码模式:/oauth2/authorize?response_type=code&client_id=my-client&redirect_uri=http://127.0.0.1:8080/authorized&scope=read</li>
                <li>客户端模式:/oauth2/token (使用 client_credentials)</li>
            </ul>
        </div>
    </body>
    </html>

    创建登录控制器:

    @Controller
    public class LoginController {
        
        @GetMapping("/login")
        public String login() {
            return "login";
        }
        
        @GetMapping("/authorized")
        @ResponseBody
        public String authorized(@RequestParam(required = false) String code, 
                                @RequestParam(required = false) String error) {
            if (error != null) {
                return "授权失败: " + error;
            }
            return "授权成功!授权码: " + code + "<br>"
                    + "请使用此授权码获取访问令牌。";
        }
    }


    四、资源服务器实现

    资源服务器负责验证 Token 并保护 API 资源。

    4.1 资源服务器配置

    @Configuration
    @EnableWebSecurity
    @EnableMethodSecurity(prePostEnabled = true)  // 启用方法级安全控制
    public class ResourceServerConfig {
        
        /**
         * 资源服务器的安全配置
         */
        @Bean
        @Order(2)
        public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception {
            http
                .securityMatcher("/api/**")  // 只保护 /api 开头的路径
                .authorizeHttpRequests(authorize -> authorize
                    .anyRequest().authenticated()  // 需要认证
                )
                .oauth2ResourceServer(oauth2 -> oauth2
                    .jwt(Customizer.withDefaults())  // 使用JWT令牌
                )
                .csrf(csrf -> csrf.disable());  // API通常禁用CSRF
            
            return http.build();
        }
        
        /**
         * 配置 JWT 解码器
         * 这里需要和授权服务器的配置一致
         */
        @Bean
        public JwtDecoder jwtDecoder() {
            // 从授权服务器获取公钥来验证JWT
            // 实际项目中,授权服务器应该提供 jwks_uri
            String jwkSetUri = "http://localhost:8080/oauth2/jwks";
            return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();
        }
    }

    4.2 受保护的 API 资源

    @RestController
    @RequestMapping("/api")
    public class ResourceController {
        
        /**
         * 获取用户信息(需要认证)
         */
        @GetMapping("/user/info")
        @PreAuthorize("hasAuthority('SCOPE_read')")  // 需要 read 权限
        public ResponseEntity<?> getUserInfo(@AuthenticationPrincipal Jwt jwt) {
            // 从JWT中提取用户信息
            String username = jwt.getClaim("sub");
            String email = jwt.getClaim("email");
            
            Map<String, Object> userInfo = new HashMap<>();
            userInfo.put("username", username);
            userInfo.put("email", email != null ? email : username + "@example.com");
            userInfo.put("userId", jwt.getClaim("user_id"));
            userInfo.put("authorities", jwt.getClaim("scope"));
            userInfo.put("issuedAt", jwt.getIssuedAt());
            userInfo.put("expiresAt", jwt.getExpiresAt());
            
            return ResponseEntity.ok(Map.of(
                "code", 200,
                "message", "获取用户信息成功",
                "data", userInfo
            ));
        }
        
        /**
         * 创建资源(需要 write 权限)
         */
        @PostMapping("/resource")
        @PreAuthorize("hasAuthority('SCOPE_write')")  // 需要 write 权限
        public ResponseEntity<?> createResource(@RequestBody Map<String, Object> resource,
                                              @AuthenticationPrincipal Jwt jwt) {
            String username = jwt.getClaim("sub");
            
            Map<String, Object> response = new HashMap<>();
            response.put("id", UUID.randomUUID().toString());
            response.put("name", resource.get("name"));
            response.put("creator", username);
            response.put("createdAt", new Date());
            response.put("message", "资源创建成功");
            
            return ResponseEntity.status(HttpStatus.CREATED).body(Map.of(
                "code", 201,
                "message", "资源创建成功",
                "data", response
            ));
        }
        
        /**
         * 公开接口(不需要认证)
         */
        @GetMapping("/public/hello")
        public ResponseEntity<?> publicHello() {
            return ResponseEntity.ok(Map.of(
                "code", 200,
                "message", "这是一个公开接口,无需认证",
                "timestamp", new Date()
            ));
        }
        
        /**
         * 管理接口(需要 ADMIN 角色)
         */
        @GetMapping("/admin/dashboard")
        @PreAuthorize("hasRole('ADMIN')")
        public ResponseEntity<?> adminDashboard(@AuthenticationPrincipal Jwt jwt) {
            return ResponseEntity.ok(Map.of(
                "code", 200,
                "message", "欢迎来到管理后台,管理员:" + jwt.getClaim("sub"),
                "adminData", Map.of(
                    "totalUsers", 1500,
                    "activeSessions", 42,
                    "systemStatus", "正常"
                )
            ));
        }
    }

    4.3 全局异常处理

    @RestControllerAdvice
    public class GlobalExceptionHandler {
        
        /**
         * 处理认证异常
         */
        @ExceptionHandler(AuthenticationException.class)
        public ResponseEntity<?> handleAuthenticationException(AuthenticationException e) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(
                "code", 401,
                "message", "认证失败:" + e.getMessage(),
                "timestamp", new Date()
            ));
        }
        
        /**
         * 处理访问被拒绝异常
         */
        @ExceptionHandler(AccessDeniedException.class)
        public ResponseEntity<?> handleAccessDeniedException(AccessDeniedException e) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(Map.of(
                "code", 403,
                "message", "权限不足,无法访问此资源",
                "timestamp", new Date()
            ));
        }
        
        /**
         * 处理 Token 过期或无效
         */
        @ExceptionHandler(JwtException.class)
        public ResponseEntity<?> handleJwtException(JwtException e) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(
                "code", 401,
                "message", "Token无效或已过期",
                "error", e.getMessage(),
                "timestamp", new Date()
            ));
        }
    }


    五、测试完整的 OAuth2.0 流程

    5.1 启动应用

    启动应用后,访问:http://localhost:8080

    5.2 授权码模式测试

    步骤 1:获取授权码

    浏览器访问授权端点:

    http://localhost:8080/oauth2/authorize?response_type=code&client_id=my-client&redirect_uri=http://127.0.0.1:8080/authorized&scope=read

    系统会跳转到登录页面,使用以下任一账号登录:

    • • 用户名:admin,密码:admin123

    • • 用户名:user,密码:user123

    登录成功后,会看到授权确认页面,点击"确认授权"后,会跳转到回调地址并显示授权码。

    步骤 2:用授权码获取访问令牌

    使用 Postman 或 curl:

    curl –location 'http://localhost:8080/oauth2/token' \\
    –header 'Content-Type: application/x-www-form-urlencoded' \\
    –header 'Authorization: Basic bXktY2xpZW50Om15LXNlY3JldA==' \\
    –data-urlencode 'grant_type=authorization_code' \\
    –data-urlencode 'code=YOUR_AUTHORIZATION_CODE' \\
    –data-urlencode 'redirect_uri=http://127.0.0.1:8080/authorized'

    注意:Authorization 头是 client_id:client_secret 的 Base64 编码。

    步骤 3:使用访问令牌调用 API

    curl –location 'http://localhost:8080/api/user/info' \\
    –header 'Authorization: Bearer YOUR_ACCESS_TOKEN'

    5.3 客户端模式测试

    curl –location 'http://localhost:8080/oauth2/token' \\
    –header 'Content-Type: application/x-www-form-urlencoded' \\
    –header 'Authorization: Basic bXktY2xpZW50Om15LXNlY3JldA==' \\
    –data-urlencode 'grant_type=client_credentials' \\
    –data-urlencode 'scope=read'

    5.4 刷新令牌

    访问令牌过期后,可以使用刷新令牌获取新的访问令牌:

    curl –location 'http://localhost:8080/oauth2/token' \\
    –header 'Content-Type: application/x-www-form-urlencoded' \\
    –header 'Authorization: Basic bXktY2xpZW50Om15LXNlY3JldA==' \\
    –data-urlencode 'grant_type=refresh_token' \\
    –data-urlencode 'refresh_token=YOUR_REFRESH_TOKEN'


    六、高级配置与优化

    6.1 使用数据库存储客户端信息

    上面的例子使用了内存存储,实际项目中应该使用数据库:

    @Entity
    @Table(name = "oauth2_clients")
    @Data
    public class OAuth2Client {
        
        @Id
        private String id;
        
        @Column(nullable = false, unique = true)
        private String clientId;
        
        private String clientSecret;
        
        private String authenticationMethods;  // 逗号分隔
        
        private String authorizationGrantTypes;  // 逗号分隔
        
        private String redirectUris;  // 逗号分隔
        
        private String scopes;  // 逗号分隔
        
        private Boolean requireProofKey = false;
        
        private Boolean requireAuthorizationConsent = false;
        
        @CreationTimestamp
        private LocalDateTime createTime;
        
        @UpdateTimestamp
        private LocalDateTime updateTime;
    }@Service
    @Transactional
    public class JpaRegisteredClientRepository implements RegisteredClientRepository {
        
        @Autowired
        private OAuth2ClientRepository clientRepository;
        
        @Override
        public void save(RegisteredClient registeredClient) {
            // 保存到数据库
        }
        
        @Override
        public RegisteredClient findById(String id) {
            // 从数据库查询
        }
        
        @Override
        public RegisteredClient findByClientId(String clientId) {
            // 从数据库查询
        }
    }

    6.2 自定义 Token 增强器

    可以在 JWT Token 中添加自定义声明:

    @Component
    public class CustomTokenEnhancer implements OAuth2TokenCustomizer<JwtEncodingContext> {
        
        @Override
        public void customize(JwtEncodingContext context) {
            if (context.getPrincipal() instanceof UsernamePasswordAuthenticationToken) {
                // 添加用户ID和角色到Token
                Authentication authentication = context.getPrincipal();
                UserDetails userDetails = (UserDetails) authentication.getPrincipal();
                
                context.getClaims().claims(claims -> {
                    claims.put("user_id", generateUserId(userDetails.getUsername()));
                    claims.put("email", userDetails.getUsername() + "@company.com");
                    
                    // 添加自定义声明
                    claims.put("company", "MyCompany");
                    claims.put("department", "IT");
                });
            }
        }
        
        private String generateUserId(String username) {
            // 生成用户ID的逻辑
            return "user_" + username.hashCode();
        }
    }

    在配置中注册:

    @Bean
    public OAuth2AuthorizationServerConfiguration.OAuth2AuthorizationServerConfigurer authorizationServerConfigurer(
            RegisteredClientRepository registeredClientRepository,
            CustomTokenEnhancer customTokenEnhancer) {
        
        return OAuth2AuthorizationServerConfiguration
            .authorizationServerConfigurer(registeredClientRepository)
            .tokenEnhancer(customTokenEnhancer);
    }

    6.3 配置 Token 有效期

    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder()
            .tokenEndpoint("/oauth2/token")
            .authorizationEndpoint("/oauth2/authorize")
            .tokenIntrospectionEndpoint("/oauth2/introspect")
            .tokenRevocationEndpoint("/oauth2/revoke")
            .jwkSetEndpoint("/oauth2/jwks")
            .oidcUserInfoEndpoint("/connect/userinfo")
            .build();
    }

    @Bean
    public TokenSettings tokenSettings() {
        return TokenSettings.builder()
            .accessTokenTimeToLive(Duration.ofHours(2))  // 访问令牌2小时过期
            .refreshTokenTimeToLive(Duration.ofDays(7))  // 刷新令牌7天过期
            .authorizationCodeTimeToLive(Duration.ofMinutes(5))  // 授权码5分钟过期
            .reuseRefreshTokens(false)  // 不重复使用刷新令牌
            .build();
    }


    七、注意事项

    7.1 安全最佳实践

  • 1. 不要使用 {noop} 密码编码器

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();  // 生产环境使用
    }

  • 2. 使用 HTTPS生产环境必须使用 HTTPS,防止 Token 被窃取。

  • 3. 限制重定向 URI

    RegisteredClient client = RegisteredClient.withId("1")
        .clientId("web-app")
        .redirectUris(uris -> {
            uris.add("https://production.com/callback");
            // 不要使用通配符或过于宽松的规则
        })
        .build();

  • 4. 定期轮换密钥JWK 签名密钥应定期轮换。

  • 7.2 监控与日志

    @Component
    public class OAuth2AuditLogger {
        
        private static final Logger log = LoggerFactory.getLogger("OAUTH2_AUDIT");
        
        @EventListener
        public void onAuthorizationSuccess(AuthorizationSuccessEvent event) {
            log.info("授权成功 – 客户端: {}, 用户: {}, 范围: {}",
                event.getAuthorization().getRegisteredClientId(),
                event.getAuthorization().getPrincipalName(),
                event.getAuthorization().getAuthorizedScopes());
        }
        
        @EventListener
        public void onAuthorizationFailure(AuthorizationErrorEvent event) {
            log.warn("授权失败 – 错误: {}, 客户端: {}",
                event.getError(),
                event.getAuthorization().getRegisteredClientId());
        }
        
        @EventListener
        public void onTokenIssued(OAuth2TokenIssuedEvent event) {
            log.info("Token签发 – 类型: {}, 客户端: {}",
                event.getToken().getClass().getSimpleName(),
                event.getAuthorization().getRegisteredClientId());
        }
    }

    7.3 性能优化

  • 1. 使用 Redis 缓存令牌

    @Bean
    public OAuth2AuthorizationService authorizationService(
            RegisteredClientRepository registeredClientRepository,
            RedisConnectionFactory redisConnectionFactory) {
        
        return new RedisOAuth2AuthorizationService(
            redisConnectionFactory,
            registeredClientRepository);
    }

  • 2. 启用响应式支持

    @Configuration
    @EnableWebFluxSecurity
    public class ReactiveResourceServerConfig {
        
        @Bean
        public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
            http
                .authorizeExchange(exchanges -> exchanges
                    .anyExchange().authenticated()
                )
                .oauth2ResourceServer(oauth2 -> oauth2
                    .jwt(Customizer.withDefaults())
                );
            return http.build();
        }
    }


  • 八、常见问题与解决方案

    Q1: 如何支持多种认证方式?

    A: 在 RegisteredClient 中配置多种认证方法:

    .clientAuthenticationMethods(methods -> {
        methods.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
        methods.add(ClientAuthenticationMethod.CLIENT_SECRET_POST);
        methods.add(ClientAuthenticationMethod.CLIENT_SECRET_JWT);
        methods.add(ClientAuthenticationMethod.PRIVATE_KEY_JWT);
    })

    Q2: 如何实现单点登录(SSO)?

    A: Spring Security 支持 OAuth2 单点登录:

    @Configuration
    @EnableWebSecurity
    public class OAuth2LoginConfig {
        
        @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            http
                .oauth2Login(oauth2 -> oauth2
                    .loginPage("/login")
                    .defaultSuccessUrl("/home")
                )
                .authorizeHttpRequests(authz -> authz
                    .anyRequest().authenticated()
                );
            return http.build();
        }
    }

    Q3: 如何集成第三方登录(微信、GitHub等)?

    A: 添加对应的依赖和配置:

    spring:
      security:
        oauth2:
          client:
            registration:
              github:
                client-id: YOUR_GITHUB_CLIENT_ID
                client-secret: YOUR_GITHUB_CLIENT_SECRET
              wechat:
                client-id: YOUR_WECHAT_APPID
                client-secret: YOUR_WECHAT_SECRET
                authorization-grant-type: authorization_code
                scope: snsapi_userinfo
                client-name: 微信
                provider: wechat
            provider:
              wechat:
                authorization-uri: https://open.weixin.qq.com/connect/oauth2/authorize
                token-uri: https://api.weixin.qq.com/sns/oauth2/access_token
                user-info-uri: https://api.weixin.qq.com/sns/userinfo


    写在最后

    通过今天的文章,我们完整实现了 SpringBoot 集成 OAuth2.0 的授权服务器和资源服务器。总结一下关键点:

  • 1. 授权服务器负责颁发 Token,支持多种授权模式

  • 2. 资源服务器负责验证 Token 并保护 API

  • 3. JWT Token 是无状态的,适合分布式系统

  • 4. Spring Security OAuth2 提供了完整的解决方案

  • OAuth2.0 看似复杂,但理解了核心概念后,结合 Spring Security 的自动化配置,实现起来并不困难。建议你先在本地运行这个示例,然后根据实际业务需求进行调整。


    原创不易,如果觉得有帮助,欢迎点赞、在看或分享给你的小伙伴~

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » SpringBoot 集成 OAuth2.0 资源服务器与授权服务器
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!