使用Spring Authorization Server 1.0.0认证客户端遇登录重定向循环求助
问题描述
我正在构建Spring Boot客户端应用,拟通过Spring Authorization Server 1.0.0实现认证,但无法正常登录,始终重定向至授权服务器登录页面。
授权服务器配置(pom.xml)
port: 9000 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.0</version> </parent> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mariadb.jdbc</groupId> <artifactId>mariadb-java-client</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> <version>2.7.5</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-authorization-server</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>5.0.1</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>31.1-jre</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.12.0</version> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>20041127.091804</version> </dependency> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>20030211.134440</version> </dependency> </dependencies>
授权服务器核心配置类
@Configuration(proxyBeanMethods = false) public class AuthorizationServerConfig { private final PasswordEncoder passwordEncoder; public AuthorizationServerConfig(PasswordEncoder passwordEncoder) { this.passwordEncoder = passwordEncoder; } @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); http.getConfigurer(OAuth2AuthorizationServerConfigurer.class) .oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0 // @formatter:off http .exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")) ) .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); // Accept access tokens for User Info and/or Client Registration // @formatter:on return http.build(); } // @formatter:off @Bean public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) { // Save registered client in db as if in-memory JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate); RegisteredClient existingClient = registeredClientRepository.findByClientId("front-client"); if(existingClient == null){ RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("front-client") .clientSecret(passwordEncoder.encode("123456")) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) //.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) .redirectUri("http://127.0.0.1:8080/login/oauth2/code/front-client-oidc") .redirectUri("http://127.0.0.1:8080/authorized") .scope(OidcScopes.OPENID) .scope("read") .tokenSettings(TokenSettings.builder() .accessTokenTimeToLive(Duration.ofDays(30)) .refreshTokenTimeToLive(Duration.ofDays(365)) .build()) //.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) .build(); registeredClientRepository.save(registeredClient); } return registeredClientRepository; } // @formatter:on @Bean public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository); } @Bean public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository); } @Bean public JWKSource<SecurityContext> jwkSource() { RSAKey rsaKey = Jwks.generateRsa(); JWKSet jwkSet = new JWKSet(rsaKey); return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); } @Bean public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) { return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); } @Bean public AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().build(); } }
授权服务器安全配置类
@EnableWebSecurity @Configuration(proxyBeanMethods = false) public class DefaultSecurityConfig { private final CustomAuthenticationProvider customAuthenticationProvider; public DefaultSecurityConfig(CustomAuthenticationProvider customAuthenticationProvider) { this.customAuthenticationProvider = customAuthenticationProvider; } // @formatter:off @Bean SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated() ) .formLogin(withDefaults()); //for default login page return http.build(); } // @formatter:on @Autowired public void bindAuthenticationProvider(AuthenticationManagerBuilder authenticationManagerBuilder){ authenticationManagerBuilder.authenticationProvider(customAuthenticationProvider); } }
客户端应用配置(pom.xml)
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <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-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity6</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies>
客户端应用属性配置(application.yaml)
server: port: 8080 spring: security: oauth2: client: registration: front-client-oidc: provider: spring client-id: front-client client-secret: 123456 authorization-grant-type: authorization_code redirect-uri: "http://127.0.0.1:8080/login/oauth2/code/front-client-oidc" scope: openid client-name: front-client-oidc front-client-authorization-code: provider: spring client-id: front-client client-secret: 123456 authorization-grant-type: authorization_code redirect-uri: "http://127.0.0.1:8080/authorized" scope: read client-name: front-client-authorization-code front-client-client-credentials: provider: spring client-id: front-client client-secret: 123456 authorization-grant-type: client_credentials scope: read,write client-name: front-client-client-credentials provider: spring: issuer-uri: http://127.0.0.1:9000
客户端应用安全配置与控制器
@EnableWebSecurity @Configuration(proxyBeanMethods = false) public class SecurityConfig { // @formatter:off @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated() ) .oauth2Login(oauth2Login -> oauth2Login.loginPage("/oauth2/authorization/front-client-oidc")) .oauth2Client(withDefaults()); return http.build(); } // @formatter:on } @Controller public class DefaultController { @GetMapping("/") public String root() { return "redirect:/index"; } @GetMapping("/index") public String index() { return "index"; } }
浏览器重定向日志
Request URL: http://localhost:8080 redirect to: http://127.0.0.1:9000/login redirect to: http://127.0.0.1:9000/oauth2/authorize?response_type=code&client_id=front-client&scope=openid&state=SKhb4bxYHH36G9mRVb4Dx4b4MCzLk8J85FiSjnwQjTw=&redirect_uri=http://127.0.0.1:8080/login/oauth2/code/front-client-oidc&nonce=nmGlkPVUvvUFEE3_WhaiKonKcGqQQWUMbP__ABznb6A&continue redirect to: http://127.0.0.1:8080/login/oauth2/code/front-client-oidc?code=yBikXvgTh3vVDztjLsHeFFzkN7xeskyo9dERmjG002d9Jnjwrw21PpzjzuttTZ4cfp5E4vj9FfqG23jObRFoiiPL3G1IHEIzXa2wf-l8f3iNEGBZ2GTXrSv_6ZxV3biJ&state=SKhb4bxYHH36G9mRVb4Dx4b4MCzLk8J85FiSjnwQjTw%3D redirect to: http://127.0.0.1:8080/oauth2/authorization/front-client-oidc?error again redirect to: http://127.0.0.1:9000/login
问题排查与解决方案
1. 自定义认证提供者有效性检查
授权服务器中使用了CustomAuthenticationProvider但未提供实现代码,若该类未正确返回已认证的Authentication对象,会导致登录后认证状态无效,触发循环重定向:
- 检查
CustomAuthenticationProvider的authenticate方法,确保返回的对象包含有效用户信息,且isAuthenticated()返回true; - 若无需自定义认证,可暂时注释掉相关配置,改用Spring Security默认用户认证机制测试。
2. 移除客户端登录页冗余配置
客户端安全配置中手动指定loginPage("/oauth2/authorization/front-client-oidc")会覆盖默认OAuth2跳转逻辑,导致授权码回调后无法完成认证:
// 修改后的客户端安全配置 @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated() ) .oauth2Login(withDefaults()) .oauth2Client(withDefaults()); return http.build(); }
3. 调整授权服务器端点权限
授权服务器默认安全链设置anyRequest().authenticated(),会限制未登录用户访问/login等必要端点,需开放公共访问:
// 修改后的授权服务器默认安全链 @Bean SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeHttpRequests(authorize -> authorize.requestMatchers("/login", "/error").permitAll() .anyRequest().authenticated() ) .formLogin(withDefaults()); return http.build(); }
4. 验证JWT配置有效性
访问http://127.0.0.1:9000/.well-known/openid-configuration确认授权服务器的JWKS端点可正常访问,确保客户端能获取到有效的JWT验证密钥。
5. 确认重定向URI一致性
再次核对授权服务器注册的客户端重定向URI与客户端配置的redirect-uri,确保IP、端口、路径完全一致(无大小写或空格差异)。
内容的提问来源于stack exchange,提问作者Mujahid Khan
相关产品推荐
相关产品推荐

