纯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
相关产品推荐
相关产品推荐

