Spring Boot应用程序始终重定向到登录页面,尽管请求具有有效的访问令牌。

huangapple go评论81阅读模式
英文:

Spring Boot application always redirect to login despite request having valid access token

问题

我有一个使用JHipster生成的Spring Boot微服务应用程序,集成了Keycloak。以下是该应用程序的版本信息:

  • JHipster - 7.9.3
  • Spring Boot - 3.0.2
  • Spring Cloud - 2022.0.1
  • Keycloak - 20.0.3

我手动更新了Spring Boot版本,而不是使用JHipster生成的版本。

安全配置如下所示:

@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Import(SecurityProblemSupport.class)
public class SecurityConfiguration {

    private final JHipsterProperties jHipsterProperties;

    @Value("${spring.security.oauth2.client.provider.oidc.issuer-uri}")
    private String issuerUri;

    private final SecurityProblemSupport problemSupport;

    public SecurityConfiguration(JHipsterProperties jHipsterProperties, SecurityProblemSupport problemSupport) {
        this.problemSupport = problemSupport;
        this.jHipsterProperties = jHipsterProperties;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .csrf()
            .disable()
            .exceptionHandling()
                .authenticationEntryPoint(problemSupport)
                .accessDeniedHandler(problemSupport)
        .and()
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
            .authorizeHttpRequests()
            .requestMatchers("/api/authenticate").permitAll()
            .requestMatchers("/api/auth-info").permitAll()
            .requestMatchers("/api/admin/**").hasAuthority(AuthoritiesConstants.ADMIN)
            .requestMatchers("/api/**").authenticated()
            .requestMatchers("/management/health").permitAll()
            .requestMatchers("/management/health/**").permitAll()
            .requestMatchers("/management/info").permitAll()
            .requestMatchers("/management/prometheus").permitAll()
            .requestMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
        .and()
            .oauth2ResourceServer()
                .jwt()
                .jwtAuthenticationConverter(authenticationConverter())
                .and()
            .and()
                .oauth2Client();
        return http.build();
        // @formatter:on
    }

    Converter<Jwt, AbstractAuthenticationToken> authenticationConverter() {
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new JwtGrantedAuthorityConverter());
        return jwtAuthenticationConverter;
    }

    @Bean
    JwtDecoder jwtDecoder() {
        NimbusJwtDecoder jwtDecoder = JwtDecoders.fromOidcIssuerLocation(issuerUri);

        OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(jHipsterProperties.getSecurity().getOauth2().getAudience());
        OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri);
        OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);

        jwtDecoder.setJwtValidator(withAudience);

        return jwtDecoder;
    }
}

安全相关的应用程序属性如下:

spring:
    security:
        oauth2:
          resource:
              filter-order: 3
          client:
            provider:
              oidc:
                issuer-uri: http://localhost:8080/realms/samplerealm
            registration:
              oidc:
                authorization-grant-type: client_credentials
                client-id: microservice-client
                client-secret: <VALID_CLIENT_SECRET>
                scope: openid, profile, email, offline_access # 最后一个用于刷新令牌

使用这些配置,应用程序监听localhost:8087以接收HTTP请求。

我在Keycloak中创建了另一个客户端dev-client,并使用Postman测试应用程序的API。我使用此客户端从Keycloak获取了访问令牌,并在PostmanAuthorization标头中使用了访问令牌(Bearer ----access token----)。即使使用有效令牌,API也将我重定向到localhost:8087/login,并返回一个HTML页面响应。

这是Postman控制台的快照(由于访问令牌的长度,该快照已被裁剪)。

我不确定为什么请求会被重定向/转发到localhost:8087/login,即使我提供了有效的访问令牌。我尝试使用不同客户端获取的使用password授权的访问令牌,但结果仍然相同。

应用程序的任何HTTP请求都被重定向到localhost:8087/login,到目前为止,我尝试了GET请求,但都遇到了这个问题。

英文:

I have a Spring Boot microservice application generated using JHipster with Keycloak. Below are the versions for the application:

  • JHipster - 7.9.3
  • Spring Boot - 3.0.2
  • Spring Cloud - 2022.0.1
  • Keycloak - 20.0.3

I had manually updated the Spring Boot version from the one generated by JHipster.

The security configuration is as follows:

@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Import(SecurityProblemSupport.class)
public class SecurityConfiguration {

    private final JHipsterProperties jHipsterProperties;

    @Value(&quot;${spring.security.oauth2.client.provider.oidc.issuer-uri}&quot;)
    private String issuerUri;

    private final SecurityProblemSupport problemSupport;

    public SecurityConfiguration(JHipsterProperties jHipsterProperties, SecurityProblemSupport problemSupport) {
        this.problemSupport = problemSupport;
        this.jHipsterProperties = jHipsterProperties;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .csrf()
            .disable()
            .exceptionHandling()
                .authenticationEntryPoint(problemSupport)
                .accessDeniedHandler(problemSupport)
        .and()
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
            .authorizeHttpRequests()
            .requestMatchers(&quot;/api/authenticate&quot;).permitAll()
            .requestMatchers(&quot;/api/auth-info&quot;).permitAll()
            .requestMatchers(&quot;/api/admin/**&quot;).hasAuthority(AuthoritiesConstants.ADMIN)
            .requestMatchers(&quot;/api/**&quot;).authenticated()
            .requestMatchers(&quot;/management/health&quot;).permitAll()
            .requestMatchers(&quot;/management/health/**&quot;).permitAll()
            .requestMatchers(&quot;/management/info&quot;).permitAll()
            .requestMatchers(&quot;/management/prometheus&quot;).permitAll()
            .requestMatchers(&quot;/management/**&quot;).hasAuthority(AuthoritiesConstants.ADMIN)
        .and()
            .oauth2ResourceServer()
                .jwt()
                .jwtAuthenticationConverter(authenticationConverter())
                .and()
            .and()
                .oauth2Client();
        return http.build();
        // @formatter:on
    }

    Converter&lt;Jwt, AbstractAuthenticationToken&gt; authenticationConverter() {
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new JwtGrantedAuthorityConverter());
        return jwtAuthenticationConverter;
    }

    @Bean
    JwtDecoder jwtDecoder() {
        NimbusJwtDecoder jwtDecoder = JwtDecoders.fromOidcIssuerLocation(issuerUri);

        OAuth2TokenValidator&lt;Jwt&gt; audienceValidator = new AudienceValidator(jHipsterProperties.getSecurity().getOauth2().getAudience());
        OAuth2TokenValidator&lt;Jwt&gt; withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri);
        OAuth2TokenValidator&lt;Jwt&gt; withAudience = new DelegatingOAuth2TokenValidator&lt;&gt;(withIssuer, audienceValidator);

        jwtDecoder.setJwtValidator(withAudience);

        return jwtDecoder;
    }
}

The security related application properties are:

spring:
    security:
        oauth2:
          resource:
              filter-order: 3
          client:
            provider:
              oidc:
                issuer-uri: http://localhost:8080/realms/samplerealm
            registration:
              oidc:
                authorization-grant-type: client_credentials
                client-id: microservice-client
                client-secret: &lt;VALID_CLIENT_SECRET&gt;
                scope: openid, profile, email, offline_access # last one for refresh tokens

With these configurations, the application is listening on localhost:8087 for HTTP requests.

I created another client in Keycloak dev-client and using Postman to test the application API. I acquired an access token from Keycloak using this client and used the access token in Postman in the Authorization header (Bearer ----access token----). Even with this valid token, the API forwards me to localhost:8087/login with an HTML page response:

&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;

&lt;head&gt;
	&lt;meta charset=&quot;utf-8&quot;&gt;
	&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1, shrink-to-fit=no&quot;&gt;
	&lt;meta name=&quot;description&quot; content=&quot;&quot;&gt;
	&lt;meta name=&quot;author&quot; content=&quot;&quot;&gt;
	&lt;title&gt;Please sign in&lt;/title&gt;
	&lt;link href=&quot;https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css&quot; rel=&quot;stylesheet&quot;
		integrity=&quot;sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M&quot; crossorigin=&quot;anonymous&quot;&gt;
	&lt;link href=&quot;https://getbootstrap.com/docs/4.0/examples/signin/signin.css&quot; rel=&quot;stylesheet&quot;
		crossorigin=&quot;anonymous&quot; /&gt;
&lt;/head&gt;

&lt;body&gt;
	&lt;div class=&quot;container&quot;&gt;
		&lt;h2 class=&quot;form-signin-heading&quot;&gt;Login with OAuth 2.0&lt;/h2&gt;
		&lt;table class=&quot;table table-striped&quot;&gt;
		&lt;/table&gt;
	&lt;/div&gt;
&lt;/body&gt;

&lt;/html&gt;

Here is a snapshot of Postman console (the snapshot is cropped because of the length of the access token)

Spring Boot应用程序始终重定向到登录页面,尽管请求具有有效的访问令牌。

I am not sure why are the requests being redirected/forwarded to localhost:8087/login even if I have provided a valid access token. I have tried poviding an access token which is acquired using password grant with a different client but it still gave me the same result.

Any HTTP requests to the application gets forwarded to localhost:8087/login, so far I tried GET request and it is throwing me this issue.

答案1

得分: 2

保留默认的 JwtDecoder

使用Spring Boot。不要覆盖 JwtDecoder 只是为了验证 issueraudience。相反,应该定义Spring Boot属性。

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          audiences:
          - http://localhost:8087
          issuer-uri: http://localhost:8080/realms/samplerealm

有几个很好的原因:

  • 如果Spring团队确定应该更改要使用的实现或添加一些配置(出于性能、安全性或其他原因),仅仅升级版本不会使你受益。
  • 配置将更容易阅读和理解(例如:“JWT解码器没有特殊之处,只是标准推荐的内容”)。
  • 如果你定义了一个新的 bean,你应该对其进行单元测试。你确定你已经掌握了JWT解码器应该做什么?

UI客户端与资源服务器

作为提醒,当资源服务器提供资源时,客户端会消耗这些资源。

在这里,我区分了“UI”(或类似 BFF 的 spring-cloud-gateway 配置为OAuth2客户端)和“REST”客户端。前者提供用户看到的内容,并触发认证服务器上的登录和登出。REST客户端也可能被用于“UI”客户端和资源服务器,以从资源服务器消耗资源。第一种(UI)需要一些特定的安全过滤器链配置,但第二种(REST客户端)通常要么使用已经存在于安全上下文中的访问令牌来代表用户发出请求,要么获取一个新的访问令牌(使用客户端凭据)以自己的名义发出请求。

对于Spring来说,安全方面的考虑不同,因此它提供了不同的库。例如,资源服务器通常可以是无状态的(无会话,状态与令牌相关),而UI客户端通常需要用户会话。

不要在同一安全过滤器链中混合UI客户端和资源服务器配置。如果你需要两者在同一个应用程序中,应创建具有顺序和 securityMatcher 的单独的过滤器链,让第一个仅拦截应该拦截的路由,让第二个作为后备。关于该主题的更多细节可参见“在Spring Boot 3中使用Keycloak Spring Adapter”。

在这里,你似乎只有REST端点 => 从你的安全过滤器链中移除客户端配置(.oauth2Client()),并确保没有 oauth2Login,因为它属于客户端问题。

精简化

除非你使用由Spring Boot自动配置的REST客户端(如 WebClient@FeignClientRestTemplate 等)从另一个微服务获取资源,否则移除 spring.security.oauth2.resource.client 属性和 spring-boot-starter-oauth2-client

删除无用的依赖、属性和Java配置将使你的项目更易于维护、更易于调试,在硬件上占用空间更小,并且对新开发人员来说更容易理解。

各种OAuth2使用案例的教程

在我的这个仓库上:https://github.com/ch4mpy/spring-addons/tree/master/samples/tutorials

英文:

Keep the Default JwtDecoder

You are using boot. Don't override JwtDecoder for just validating issuer and audience. Define boot properties instead.

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          audiences:
          - http://localhost:8087
          issuer-uri: http://localhost:8080/realms/samplerealm

There are a few good reasons for that:

  • if spring team figures out that the implementation to use should be switched or that some configuration should be added, (for performance, security or whatever reason) you won't benefit it by just bumping versions.
  • configuration will be easier to read and understand (like: "nothing special regarding JWT decoder, just the standard recommended stuff")
  • if you define a new bean you should unit-test it. Are you sure you master enough the specs of what a JWT decoder should do for that?

UI Client V.S. Resource-Server

As a reminder, clients consume resources when resource-servers serve it.

Here I make a distinction between "UI" (or BFF like spring-cloud-gateway configured as OAuth2 client) and "REST" clients. the first serve what users see and trigger login and logout on the authization-server. REST clients might be used as well on "UI" clients and resource-server to consume a resource from a resource-server. Some specific security filter-chain config is required for the first (UI) but not for the second (REST client) which usually either use the access-token already in the security context to issue a request on behalf of the user or acquires a new access-token (with client credentials) to issue a request in its own name.

Security concerns are different enough for spring to provide WIth different libs. For instance, resource-server can frequently be stateless (no session, the state is associated to the token) when UI clients generally need a session for the user.

Don't mix UI client and resource-server config in the same security filter-chain.
If you need the two in the same application, create separate filter chains with order and securityMatcher for the first to intercept only the routes it should and let the second act as fallback. More details on that subject in "Use Keycloak Spring Adapter with Spring Boot 3"

Here you seem to have only REST endpoints => remove client configuration from your security filter-chain (.oauth2Client() and also be sure you don't have oauth2Login which is a client concern).

Be Lean

Unless you use a REST client autoconfigured by spring-boot (WebClient, @FeignClient, RestTemplate, ...) to fetch resources from another micro-service, also remove spring.security.oauth2.resource.client properties and spring-boot-starter-oauth2-client.

Removing useless dependencies, properties and Java configuration will make your project easier to maintain, easier to debug, have a smaller footprint on the hardware and easier to grasp for new developers.

Tutorials for Various OAuth2 Use-Cases

On this repo of mine: https://github.com/ch4mpy/spring-addons/tree/master/samples/tutorials

huangapple
  • 本文由 发表于 2023年2月8日 19:00:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/75384815.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定