如何在Spring Authorization Server中实现无/login页的OAuth2授权码流程
一、OAuth2客户端配置
核心是修改未认证时的跳转逻辑为forward,替换默认的重定向过滤器:
1. 自定义授权请求转发过滤器
替换默认的OAuth2AuthorizationRequestRedirectFilter,将重定向改为forward:
public class ForwardingOAuth2AuthorizationRequestRedirectFilter extends OAuth2AuthorizationRequestRedirectFilter { public ForwardingOAuth2AuthorizationRequestRedirectFilter(ClientRegistrationRepository clientRegistrationRepository) { super(clientRegistrationRepository); } @Override protected void sendRedirect(HttpServletRequest request, HttpServletResponse response, String redirectUri) throws IOException { try { request.getRequestDispatcher(redirectUri).forward(request, response); } catch (ServletException e) { throw new IOException("Failed to forward to authorization endpoint", e); } } }
2. 客户端SecurityFilterChain配置
调整未认证入口,绑定自定义过滤器:
@Bean public SecurityFilterChain clientSecurityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .oauth2Login(oauth2 -> oauth2 .authorizationEndpoint(endpoint -> endpoint .authorizationRequestRedirectFilter(new ForwardingOAuth2AuthorizationRequestRedirectFilter(clientRegistrationRepository())) ) .redirectionEndpoint(endpoint -> endpoint.baseUri("/login/oauth2/code/*")) ) .exceptionHandling(ex -> ex .authenticationEntryPoint((request, response, authException) -> { // 未认证时forward到授权请求端点 String clientId = "your-client-id"; // 可根据业务动态获取 request.getRequestDispatcher("/oauth2/authorization/" + clientId).forward(request, response); }) ); return http.build(); } @Bean public ClientRegistrationRepository clientRegistrationRepository() { return new InMemoryClientRegistrationRepository( ClientRegistration.withRegistrationId("your-client-id") .clientId("client-id") .clientSecret("client-secret") .authorizationUri("http://auth-server:8080/oauth2/authorize") .tokenUri("http://auth-server:8080/oauth2/token") .redirectUri("{baseUrl}/login/oauth2/code/{registrationId}") .scope("openid", "profile") .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .clientName("Your Client") .build() ); }
二、授权服务配置
核心是实现自定义登录API,登录成功后forward到客户端回调端点:
1. 自定义登录接口
处理前端登录请求,认证通过后转发到客户端回调:
@RestController public class CustomLoginController { private final AuthenticationManager authenticationManager; private final OAuth2AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepo; public CustomLoginController(AuthenticationManager authenticationManager, OAuth2AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepo) { this.authenticationManager = authenticationManager; this.authorizationRequestRepo = authorizationRequestRepo; } @PostMapping("/login") public void login(@RequestBody LoginRequest loginRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 执行用户名密码认证 UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( loginRequest.getUsername(), loginRequest.getPassword() ); Authentication auth = authenticationManager.authenticate(authToken); SecurityContextHolder.getContext().setAuthentication(auth); // 加载之前保存的授权请求 OAuth2AuthorizationRequest authRequest = authorizationRequestRepo.loadAuthorizationRequest(request); if (authRequest == null) { throw new IllegalStateException("No pending authorization request found"); } // Forward到客户端回调端点 request.getRequestDispatcher(authRequest.getRedirectUri()).forward(request, response); } // 登录请求DTO public static class LoginRequest { private String username; private String password; // Getters & Setters public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } }
2. 授权服务Security&AuthorizationServer配置
@Configuration @EnableWebSecurity public class AuthServerSecurityConfig { @Bean public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); http .exceptionHandling(ex -> ex // 未认证时重定向到前端登录页面 .authenticationEntryPoint((request, response, authException) -> response.sendRedirect("/your-front-login-page")) ) .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) .authorizeHttpRequests(auth -> auth .requestMatchers("/login").permitAll() .anyRequest().authenticated() ); return http.build(); } @Bean public RegisteredClientRepository registeredClientRepository() { RegisteredClient client = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("client-id") .clientSecret("{noop}client-secret") // 生产环境用BCrypt加密 .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .redirectUri("http://client:8081/login/oauth2/code/your-client-id") .scope("openid") .scope("profile") .build(); return new InMemoryRegisteredClientRepository(client); } // JWT相关配置 @Bean public JwtDecoder jwtDecoder(JwkSource<SecurityContext> jwkSource) { return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); } @Bean public JwkSource<SecurityContext> jwkSource() { RSAKey rsaKey = Jwks.generateRsa(); return (selector, context) -> selector.select(new JWKSet(rsaKey)); } @Bean public AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().build(); } // 认证管理器配置 @Bean public AuthenticationManager authenticationManager(UserDetailsService userDetailsService) { DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); provider.setUserDetailsService(userDetailsService); provider.setPasswordEncoder(passwordEncoder()); return new ProviderManager(provider); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
关键注意事项
- Forward的局限性:Forward仅能在同Servlet容器或可内部路由的场景生效,如果客户端和授权服务跨域独立部署,此方式会失效,需改用授权码传递的其他方案。
- 授权请求存储:默认用Session存储授权请求,跨服务部署时需改用Redis等分布式Session共享方案,否则forward时无法加载授权请求。
- 前端配合:前端需实现自定义登录页面,调用授权服务的
/login接口完成认证,后续流程由服务端自动转发处理。
内容的提问来源于stack exchange,提问作者bruce0828
相关产品推荐
相关产品推荐

