You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

纯REST风格Spring Security+Angular OAuth2.0登录配置求助

解决方案:纯REST风格OAuth2授权码流程实现(Spring + Angular)

1. 流程可行性确认

你的方案完全可行。Spring Security支持同时将应用作为OAuth2客户端(对接Google/自定义授权服务器)和资源服务器(用JWT保护API),走标准授权码流程,完全避开Implicit模式和PKCE(虽然PKCE更适配SPA,但你明确无法使用的话,当前方案是合理替代)。

2. Spring Security核心配置实现

第一步:添加依赖

在pom.xml中引入必要依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>
    <!-- 可选:用JJWT生成自定义JWT -->
    <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>
</dependencies>

第二步:配置OAuth2客户端与资源服务器

在application.yml中配置Google OAuth2信息(后续替换为自定义授权服务器即可):

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: 你的Google客户端ID
            client-secret: 你的Google客户端密钥
            scope: openid,email,profile
            redirect-uri: "http://localhost:8080/OAuthTrial/api/oauth-callback"
        provider:
          google:
            authorization-uri: https://accounts.google.com/o/oauth2/v2/auth
            token-uri: https://oauth2.googleapis.com/token
            user-info-uri: https://www.googleapis.com/oauth2/v3/userinfo
            user-name-attribute: sub
      resource-server:
        jwt:
          issuer-uri: https://accounts.google.com # Google的JWT签发地址,自定义服务器替换为对应地址
server:
  servlet:
    context-path: /OAuthTrial/api

第三步:SecurityFilterChain配置

编写SecurityConfig类,放行登录和回调端点,同时配置资源服务器的JWT验证:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.List;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final OAuth2AuthorizedClientService authorizedClientService;

    public SecurityConfig(OAuth2AuthorizedClientService authorizedClientService) {
        this.authorizedClientService = authorizedClientService;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .cors(cors -> cors.configurationSource(corsConfigurationSource())) // 启用CORS配置
                .csrf(csrf -> csrf.disable()) // SPA场景下可禁用CSRF,或配置CSRF令牌传递
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/oauth-login", "/oauth-callback").permitAll() // 放行登录和回调端点
                        .anyRequest().authenticated() // 其他API需认证
                )
                .oauth2Client(oauth2 -> oauth2 // 配置OAuth2客户端支持
                        .authorizedClientService(authorizedClientService)
                )
                .oauth2ResourceServer(oauth2 -> oauth2 // 配置资源服务器,用JWT验证
                        .jwt(jwt -> jwt.jwtAuthenticationConverter(new CustomJwtAuthenticationConverter())) // 可选:自定义JWT转换逻辑
                );
        return http.build();
    }

    // CORS配置,解决跨域问题
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOrigins(List.of("http://localhost:4200")); // 允许Angular的域名
        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        config.setAllowedHeaders(List.of("*"));
        config.setAllowCredentials(true); // 允许携带凭证(Cookie等)
        config.setExposedHeaders(List.of("Location")); // 暴露Location头,解决重定向时的CORS问题

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return source;
    }
}

第四步:实现登录与回调的REST端点

编写AuthController,处理oauth-login的重定向和oauth-callback的token交换逻辑:

import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@RestController
public class AuthController {

    private final OAuth2AuthorizedClientService authorizedClientService;
    private final JwtTokenGenerator jwtTokenGenerator; // 自定义JWT生成工具类

    public AuthController(OAuth2AuthorizedClientService authorizedClientService, JwtTokenGenerator jwtTokenGenerator) {
        this.authorizedClientService = authorizedClientService;
        this.jwtTokenGenerator = jwtTokenGenerator;
    }

    // Angular点击按钮调用的登录端点,返回Location头重定向到Google认证页
    @GetMapping("/oauth-login")
    public void oauthLogin(HttpServletResponse response) throws IOException {
        String authUrl = "https://accounts.google.com/o/oauth2/v2/auth?client_id=你的客户端ID&redirect_uri=http://localhost:8080/OAuthTrial/api/oauth-callback&response_type=code&scope=openid%20email%20profile";
        response.setHeader("Location", authUrl);
        response.setStatus(HttpServletResponse.SC_FOUND);
    }

    // Google回调端点,交换token并返回自定义JWT给Angular
    @GetMapping("/oauth-callback")
    public AuthResponse oauthCallback(OAuth2AuthenticationToken authenticationToken) {
        // 获取授权后的客户端信息
        OAuth2AuthorizedClient authorizedClient = authorizedClientService.loadAuthorizedClient(
                authenticationToken.getAuthorizedClientRegistrationId(),
                authenticationToken.getName()
        );
        OAuth2AccessToken accessToken = authorizedClient.getAccessToken();

        // 用Google的用户信息生成自定义JWT(建议自定义,避免依赖第三方token)
        String customJwt = jwtTokenGenerator.generateToken(authenticationToken.getPrincipal().getAttributes());

        return new AuthResponse(customJwt);
    }

    // 自定义响应类,返回JWT给Angular
    public static class AuthResponse {
        private String token;

        public AuthResponse(String token) {
            this.token = token;
        }

        public String getToken() {
            return token;
        }
    }
}

第五步:自定义JWT生成工具类示例

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.Map;

@Component
public class JwtTokenGenerator {

    private static final String SECRET_KEY = "你的自定义密钥(生产环境用RSA非对称密钥)";
    private static final long EXPIRATION_TIME = 86400000; // 24小时

    public String generateToken(Map<String, Object> userAttributes) {
        return Jwts.builder()
                .setClaims(userAttributes)
                .setSubject(userAttributes.get("email").toString())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .signWith(SignatureAlgorithm.HS512, SECRET_KEY)
                .compact();
    }
}

3. CORS错误解决方案

上面的SecurityConfig中已经配置了完整的CORS规则,重点注意:

  • 必须设置setAllowCredentials(true),因为OAuth2流程涉及会话或凭证传递
  • 必须setExposedHeaders(List.of("Location")),确保Angular能读取重定向的Location头
  • 确保Spring Security的过滤器链优先处理CORS(通过.cors()配置)

另外,Angular端请求时需开启withCredentials:

this.http.get('http://localhost:8080/OAuthTrial/api/oauth-login', { withCredentials: true })
  .subscribe(() => {
    // 处理重定向逻辑
  });

关键注意事项

  • 生产环境禁止用对称密钥生成JWT,改用RSA非对称密钥
  • 回调端点必须和Google控制台配置的redirect_uri完全一致,否则会触发授权失败
  • 后续迁移到自定义授权服务器时,只需替换application.yml中的spring.security.oauth2.client.provider配置即可
  • 如果不需要自定义JWT,也可以直接返回Google颁发的accessToken,但自定义JWT更便于控制权限和有效期

内容的提问来源于stack exchange,提问作者Thomas A Mathew

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 03:11:00