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

Spring Cloud Gateway与Spring Authorization Server认证401问题求助

基于OAuth2的Spring Cloud Security网关认证问题排查

我正在基于OAuth2模式实现Spring Cloud Security,将Gateway作为OAuth2客户端,目前遇到认证与授权相关问题,恳请帮忙排查。

具体问题:输入用户名和密码完成认证后,页面跳转至回调URL时出现401 Unauthorized错误,回调URL示例:

https://127.0.0.1:8443/login/oauth2/code/spring?code=axdUp_ImMSt8vdDrN6XqIrQrEjB7P91h2HxNcqasy7JmCsJmw71u59nRUotAzkIEuBIo1quZsz3CP36Cm_xde3dMPDuvTFvs2GMRUjvDZV0BVhg0bN-LqPUp9gjMi7gm&state=BfLaHTmU9TsdJVMgWgviot1GSKHA1QnbFhhH1ESYWbg%3D

授权服务器配置

@Configuration
@EnableWebSecurity
public class AuthorizationServerConfiguration {

  @Bean
  @Order(1)
  public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
      throws Exception {
    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
    http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
        .oidc(Customizer.withDefaults());
    http
        .exceptionHandling(
            (exceptions) ->
                exceptions.defaultAuthenticationEntryPointFor(
                    new LoginUrlAuthenticationEntryPoint("/login"),
                    new MediaTypeRequestMatcher(
                        org.springframework.http.MediaType.valueOf(MediaType.TEXT_HTML))))
        .oauth2ResourceServer((resourceServer) -> resourceServer.jwt(Customizer.withDefaults()));

    return http.build();
  }

  @Bean
  @Order(2)
  public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(
            (authorize) ->
                authorize
                    .requestMatchers("/ping", "/oauth2/authorize")
                    .permitAll()
                    .anyRequest()
                    .authenticated())
        .formLogin(Customizer.withDefaults());
    return http.build();
  }

  @Bean
  public RegisteredClientRepository registeredClientRepository() {
    RegisteredClient authorizationCodeFlow =
        RegisteredClient.withId(UUID.randomUUID().toString())
            .clientId("ac-client")
            .clientSecret("{noop}ac-secret")
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
            .redirectUri("https://127.0.0.1:8443/login/oauth2/code/spring")
            .postLogoutRedirectUri("https://127.0.0.1:8443/app/logout")
            .scopes(scopes -> scopes.addAll(Set.of(OidcScopes.OPENID, OidcScopes.PROFILE)))
            .clientSettings(ClientSettings.builder().requireAuthorizationConsent(false).build())
            .tokenSettings(
                TokenSettings.builder()
                    .authorizationCodeTimeToLive(Duration.of(5, ChronoUnit.MINUTES))
                    .accessTokenTimeToLive(Duration.of(30, ChronoUnit.MINUTES))
                    .build())
            .build();
    return new InMemoryRegisteredClientRepository(authorizationCodeFlow);
  }

  @Bean
  public UserDetailsService userDetailsService() {
    UserDetails adminUser =
        User.builder().username("admin").password("{noop}admin").roles("ADMIN").build();
    return new InMemoryUserDetailsManager(adminUser);
  }
}

网关配置

@Configuration
@EnableWebFluxSecurity
public class SecurityConfiguration {

  private static final String[] WHITELIST_URL = {
    "/login/oauth2/code/spring", "/oauth2/authorization/spring", "/app/logout", "favicon.ico"
  };

  @Bean
  public SecurityWebFilterChain springSecurityFilterChain(
      ServerHttpSecurity http, ReactiveClientRegistrationRepository clientRegistrationRepository) {
    http.authorizeExchange(
            authorizeExchangeSpec ->
                authorizeExchangeSpec
                    .pathMatchers(WHITELIST_URL)
                    .permitAll()
                    .anyExchange()
                    .authenticated())
        .oauth2Login(
            oAuth2LoginSpec ->
                oAuth2LoginSpec
                    .authenticationSuccessHandler(this::authenticationSuccessHandler)
                    .authenticationFailureHandler(this::authenticationFailureHandler))
        .logout(
            logoutSpec ->
                logoutSpec.logoutSuccessHandler(
                    serverLogoutSuccessHandler(clientRegistrationRepository)))
        .exceptionHandling(
            exceptionHandlingSpec ->
                exceptionHandlingSpec.authenticationEntryPoint(this::authenticationEntryPoint));
    return http.build();
  }

  private ServerLogoutSuccessHandler serverLogoutSuccessHandler(
      ReactiveClientRegistrationRepository clientRegistrationRepository) {
    var logout = new OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository);
    logout.setPostLogoutRedirectUri("https://127.0.0.1:8443/app/logout");
    return logout;
  }

  private Mono<Void> authenticationSuccessHandler(
      WebFilterExchange webFilterExchange, Authentication authentication) {
    return buildResponse(webFilterExchange, HttpStatus.OK, Map.of("status", "LoginSuccess"));
  }

  private Mono<Void> authenticationFailureHandler(
      WebFilterExchange webFilterExchange, AuthenticationException exception) {
    return buildResponse(
        webFilterExchange, HttpStatus.UNAUTHORIZED, Map.of("status", "Authentication Failed"));
  }

  private Mono<Void> authenticationEntryPoint(
      ServerWebExchange exchange, AuthenticationException ex) {
    ServerHttpResponse response = exchange.getResponse();
    response.setStatusCode(HttpStatus.UNAUTHORIZED);
    DataBuffer result =
        response
            .bufferFactory()
            .wrap(Map.of("status", "Login to access APIs"));
    return response.writeWith(Mono.just(result));
  }

  private Mono<Void> buildResponse(
      WebFilterExchange webFilterExchange, HttpStatus status, Map<String, String> message) {
    ServerHttpResponse response = webFilterExchange.getExchange().getResponse();
    response.setStatusCode(status);
    response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
    DataBuffer result =
        response.bufferFactory().wrap(message);
    return response.writeWith(Mono.just(result));
  }
}

网关application.yml配置

spring:
  application.name: gateway
  cloud:
    gateway:
      default-filters:
        - TokenRelay=
        - RemoveRequestHeader=Cookie
      routes:
        - id: product-service
          uri: lb://product-service
          predicates:
            - Path=/product/**
  security:
    oauth2:
      client:
        provider:
          spring:
            issuer-uri: ${ISSUER_URI:http://localhost:9000/auth}
        registration:
          spring:
            provider: spring
            client-id: ac-client
            client-secret: ac-secret
            authorization-grant-type: authorization_code
            client-authentication-method: client_secret_basic
            redirect-uri: "https://127.0.0.1:8443/login/oauth2/code/{registrationId}"
            scope: openid, profile

内容的提问来源于stack exchange,提问作者vishal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 16:03:11