如何为 Okta 自动刷新不记名令牌

Posted

技术标签:

【中文标题】如何为 Okta 自动刷新不记名令牌【英文标题】:How to refresh Bearer token Automatically for Okta 【发布时间】:2021-04-24 00:43:43 【问题描述】:

我有一个带有 Angular UI、Zuul 和一些与 Okta 登录 (OAuth) 集成的服务的应用程序(非响应式)。这工作正常,但在 jwt 令牌过期后卡住了。关于工作流程的一些细节

    应用 URL 指向 Zuul。 Zuul 将请求重定向到 Okta。用户登录。 Okta 发回一个不记名令牌(也是一个刷新令牌)。 此不记名令牌被传递到 UI 并存储为 cookie。对于每个请求,UI 都会发送 Authorization 标头以及不记名令牌。 此过程正常运行,直到 jwt 令牌在一小时内过期,然后 Zuul 尝试将其重定向到默认登录页面,因为我们使用 okta 登录,所以该页面没有任何内容。

我的问题

    如果需要https://dev1234.okta.com/oauth2/default,可以将登录页面重定向到哪里? 如何根据刷新令牌获取新的不记名令牌? 我能否根据刷新令牌在 Zuul 中自动获取新的不记名令牌。如果这不可能,最好的方法是什么?

这是 Zuul 的 application.yml 文件

spring:
  application:
    name: service-gateway
  cloud:
    loadbalancer:
      ribbon:
        enabled: false

server:
  port: 8080

okta:
  oauth2:
    issuer: https://dev1234.okta.com/oauth2/default
    client-id: <value>
    client-secret: <value>

    
feign:
  hystrix:
    enabled: true
hystrix:
  shareSecurityContext: true

eureka:
  client:
    enabled: true
    fetch-registry: true
    
zuul:
  routes:
    abc-service:
      path: /api/**
      strip-prefix: true
      service-id: ABC-SERVICE

    ui:
      path: /**
      url: http://localhost:4200
      
  host:
    connect-timeout-millis: 10000
    socket-timeout-millis: 20000
    
  sensitive-headers:
  - Cookie,Set-Cookie  

gradle 文件

plugins 
    id 'org.springframework.boot' version '2.3.7.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'


group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations 
    developmentOnly
    runtimeClasspath 
        extendsFrom developmentOnly
    


repositories 
    mavenCentral()


ext 
    set('springCloudServicesVersion', "2.3.0.RELEASE")
    set('springCloudVersion', "Hoxton.SR9")


dependencies 
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'com.okta.spring:okta-spring-boot-starter:1.4.0'
    implementation 'io.pivotal.spring.cloud:spring-cloud-services-starter-service-registry'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-hystrix'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-zuul'
    implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
    implementation 'io.pivotal.spring.cloud:spring-cloud-services-starter-config-client'
    implementation 'org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    testImplementation('org.springframework.boot:spring-boot-starter-test') 
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    


dependencyManagement 
    imports 
        mavenBom "io.pivotal.spring.cloud:spring-cloud-services-dependencies:$springCloudServicesVersion"
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:$springCloudVersion"
    


test 
    useJUnitPlatform()

Zulu WebSecurityConfigurerAdapter

package abc
@EnableDiscoveryClient
@EnableZuulProxy
@SpringBootApplication

public class ServiceGateway 

    private static List<String> clients = Arrays.asList("okta");
    @Autowired
    private ClientRegistrationRepository clientRegistrationRepository;

    @Value("$okta.oauth2.client-id")
    String clientId;
    @Value("$okta.oauth2.client-secret")
    String clientSecret;

    public static void main(String[] args) 
        SpringApplication.run(ServiceGateway.class, args);
    

    @Configuration
    static class OktaOAuth2WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter 

        @Override
        protected void configure(HttpSecurity http) throws Exception 
            // @formatter:off
            http
            .authorizeRequests().anyRequest().authenticated()
            .and()
            .oauth2Login()
            .and()
            .oauth2ResourceServer().jwt();
            // @formatter:on
        
    

    @Bean
    public FilterRegistrationBean<CorsFilter> simpleCorsFilter() 
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.setAllowedOrigins(Collections.singletonList("*"));
        config.setAllowedMethods(Collections.singletonList("*"));
        config.setAllowedHeaders(Collections.singletonList("*"));
        source.registerCorsConfiguration("/**", config);
        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return bean;
    

    @Bean
    public OktaAuthenticationFilter oktaAuthFilter(OAuth2AuthorizedClientService clientService) 
        return new OktaAuthenticationFilter(clientService);
    


服务应用程序.yml

spring:
  application:
    name: analytics-service

server:
  port: 8081

eureka:
  client:
    enabled: true

okta:
  oauth2:
    issuer: https://dev1234.okta.com/oauth2/default
    client-id: <value>
    client-secret: <value>

服务的配置文件

@Configuration
    static class OktaOAuth2WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter 

        @Override
        protected void configure(HttpSecurity http) throws Exception 
            // @formatter:off
            http
                .authorizeRequests().anyRequest().authenticated()
                    .and()
                    .oauth2ResourceServer().jwt();
            // @formatter:on
        
    

    @Bean
    protected RestTemplate restTemplate() 
        return new OAuth2RestTemplate(oAuthDetails());
    

Gradle 文件

buildscript 
    dependencies 
        classpath('gradle.plugin.com.palantir.gradle.docker:gradle-docker:0.13.0')
    


plugins 
    id 'org.springframework.boot' version '2.3.7.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'


apply plugin: 'io.spring.dependency-management'
apply plugin: 'com.palantir.docker'

group = 'com.demo'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations 
    developmentOnly
    runtimeClasspath 
        extendsFrom developmentOnly
    
    compileOnly 
        extendsFrom annotationProcessor
    


repositories 
    mavenCentral()
    maven  url 'https://repo.spring.io/milestone' 


ext 
    set('springCloudVersion', "Hoxton.SR9")
    set('springCloudServicesVersion', "2.3.0.RELEASE")


dependencies 
    implementation 'io.pivotal.spring.cloud:spring-cloud-services-starter-config-client'
    implementation 'io.pivotal.spring.cloud:spring-cloud-services-starter-service-registry'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    implementation 'org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure'
    implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
    implementation 'com.okta.spring:okta-spring-boot-starter:1.4.0'
    compileOnly    'org.projectlombok:lombok'
    implementation    'com.googlecode.json-simple:json-simple:1.1.1'
    annotationProcessor 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
    testImplementation 'org.mockito:mockito-core:2.7.22'


dependencyManagement 
    imports 
        mavenBom "io.pivotal.spring.cloud:spring-cloud-services-dependencies:$springCloudServicesVersion"
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:$springCloudVersion"
    


task unpack(type: Copy) 
    dependsOn bootJar
    from(zipTree(tasks.bootJar.outputs.files.singleFile))
    into("build/dependency")

docker 
    name "$project.group/$bootJar.baseName"
    copySpec.from(tasks.unpack.outputs).into("dependency")
    buildArgs(['DEPENDENCY': "dependency"])


task standardTests(type: Test) 
    useJUnitPlatform 
    
  

更新 看起来范围“offline_access”确实有所作为。现在我收到 CORS 错误。过滤器到位后,我应该看不到这个吗?这是由于授权标头的存在吗? 此外,如果我手动刷新浏览器,则会提供一个新令牌。那么没有CORS问题

zone.js:3243 POST http://localhost:8080/api/system/summary/salesHierarchy 401
core.js:15724 ERROR 
HttpErrorResponse headers: HttpHeaders, status: 401, statusText: "OK", url: "http://localhost:8080/api/system/summary/salesHierarchy", ok: false, …
:8080/verification:1 Access to XMLHttpRequest at 'https://dev-770454.okta.com/oauth2/default/v1/authorize?response_type=code&…0/login/oauth2/code/okta&nonce=4aoYCPl3OKhsOTTpCUiqayYjQXdpZLuonn6_Q6193-o' (redirected from 'http://localhost:8080/verification') from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
zone.js:3243 GET https://dev-770454.okta.com/oauth2/default/v1/authorize?response_type=code&…0/login/oauth2/code/okta&nonce=4aoYCPl3OKhsOTTpCUiqayYjQXdpZLuonn6_Q6193-o net::ERR_FAILED
core.js:15724 ERROR 
HttpErrorResponse headers: HttpHeaders, status: 0, statusText: "Unknown Error", url: "http://localhost:8080/verification", ok: false, …

更新1 当 JWT 令牌过期时,我看到下面的错误,这是预期的。

2021-01-31 10:38:53 [http-nio-8080-exec-9] DEBUG o.s.s.o.s.r.w.BearerTokenAuthenticationFilter - Authentication request for failed!
org.springframework.security.oauth2.server.resource.InvalidBearerTokenException: An error occurred while attempting to decode the Jwt: Jwt expired at 2021-01-31T16:35:39Z

401 被发送到 UI,UI 捕获它并重定向到 APP 中的另一个 url。这些是网关日志

2021-01-31 10:38:53 [http-nio-8080-exec-10] DEBUG o.s.s.w.s.HttpSessionRequestCache - DefaultSavedRequest added to Session: DefaultSavedRequest[http://localhost:8080/verification]
2021-01-31 10:38:53 [http-nio-8080-exec-10] DEBUG o.s.s.w.a.ExceptionTranslationFilter - Calling Authentication entry point.
org.springframework.security.web.authentication.WebAuthenticationDetails@2cd90: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: A55D113FD46A0031CA1FADD21C008382; Granted Authorities: ROLE_ANONYMOUS
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.access.vote.AffirmativeBased - Voter: org.springframework.security.web.access.expression.WebExpressionVoter@4180b4a8, returned: -1
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.security.web.FilterChainProxy - /oauth2/authorization/okta at position 1 of 17 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.security.web.FilterChainProxy - /oauth2/authorization/okta at position 2 of 17 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - HttpSession returned null object for SPRING_SECURITY_CONTEXT
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade@422bdd20. A new one will be created.
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.security.web.FilterChainProxy - /oauth2/authorization/okta at position 3 of 17 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.security.web.FilterChainProxy - /oauth2/authorization/okta at position 4 of 17 in additional filter chain; firing Filter: 'CorsFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.a.ExceptionTranslationFilter - Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84)
    at 
......

o.s.s.w.a.ExceptionTranslationFilter - Calling Authentication entry point.
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.a.DelegatingAuthenticationEntryPoint - Trying to match using AndRequestMatcher [requestMatchers=[NegatedRequestMatcher [requestMatcher=RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]], MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@6f86a06a, matchingMediaTypes=[application/xhtml+xml, image/*, text/html, text/plain], useEquals=false, ignoredMediaTypes=[*/*]]]]
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Trying to match using org.springframework.security.web.csrf.CsrfFilter$DefaultRequiresCsrfMatcher@3f60847a
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Trying to match using NegatedRequestMatcher [requestMatcher=RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]]
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Did not match
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.NegatedRequestMatcher - matches = true
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.security.web.FilterChainProxy - /oauth2/authorization/okta at position 6 of 17 in additional filter chain; firing Filter: 'LogoutFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Trying to match using MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@6f86a06a, matchingMediaTypes=[application/xhtml+xml, image/*, text/html, text/plain], useEquals=false, ignoredMediaTypes=[*/*]]
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Request 'GET /oauth2/authorization/okta' doesn't match 'POST /logout'
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.security.web.FilterChainProxy - /oauth2/authorization/okta at position 7 of 17 in additional filter chain; firing Filter: 'OAuth2AuthorizationRequestRedirectFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/oauth2/authorization/okta'; against '/oauth2/authorization/registrationId'
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/oauth2/authorization/okta'; against '/oauth2/authorization/registrationId'
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.MediaTypeRequestMatcher - httpRequestMediaTypes=[image/avif, image/webp, image/apng, image/svg+xml, image/*, */*;q=0.8]
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.MediaTypeRequestMatcher - Processing image/avif
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.MediaTypeRequestMatcher - application/xhtml+xml .isCompatibleWith image/avif = false
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.MediaTypeRequestMatcher - image/* .isCompatibleWith image/avif = true
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - All requestMatchers returned true
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.a.DelegatingAuthenticationEntryPoint - Match found! Executing org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint@1c10eb3a
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.a.DelegatingAuthenticationEntryPoint - Trying to match using AndRequestMatcher [requestMatchers=[NegatedRequestMatcher [requestMatcher=RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]], NegatedRequestMatcher [requestMatcher=AndRequestMatcher [requestMatchers=[OrRequestMatcher [requestMatchers=[Ant [pattern='/login'], Ant [pattern='/favicon.ico']]], AndRequestMatcher [requestMatchers=[NegatedRequestMatcher [requestMatcher=RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]], MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@6f86a06a, matchingMediaTypes=[application/xhtml+xml, image/*, text/html, text/plain], useEquals=false, ignoredMediaTypes=[*/*]]]]]]]]]
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Trying to match using NegatedRequestMatcher [requestMatcher=RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]]
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.NegatedRequestMatcher - matches = true
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Trying to match using NegatedRequestMatcher [requestMatcher=AndRequestMatcher [requestMatchers=[OrRequestMatcher [requestMatchers=[Ant [pattern='/login'], Ant [pattern='/favicon.ico']]], AndRequestMatcher [requestMatchers=[NegatedRequestMatcher [requestMatcher=RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]], MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@6f86a06a, matchingMediaTypes=[application/xhtml+xml, image/*, text/html, text/plain], useEquals=false, ignoredMediaTypes=[*/*]]]]]]]
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Trying to match using OrRequestMatcher [requestMatchers=[Ant [pattern='/login'], Ant [pattern='/favicon.ico']]]
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.OrRequestMatcher - Trying to match using Ant [pattern='/login']
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/favicon.ico'; against '/login'
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.OrRequestMatcher - Trying to match using Ant [pattern='/favicon.ico']
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/favicon.ico'; against '/favicon.ico'
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.OrRequestMatcher - matched
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Trying to match using AndRequestMatcher [requestMatchers=[NegatedRequestMatcher [requestMatcher=RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]], MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@6f86a06a, matchingMediaTypes=[application/xhtml+xml, image/*, text/html, text/plain], useEquals=false, ignoredMediaTypes=[*/*]]]]
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Trying to match using NegatedRequestMatcher [requestMatcher=RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]]
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.NegatedRequestMatcher - matches = true
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Trying to match using MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@6f86a06a, matchingMediaTypes=[application/xhtml+xml, image/*, text/html, text/plain], useEquals=false, ignoredMediaTypes=[*/*]]
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.MediaTypeRequestMatcher - httpRequestMediaTypes=[image/avif, image/webp, image/apng, image/svg+xml, image/*, */*;q=0.8]
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.web.DefaultRedirectStrategy - Redirecting to 'https://dev-770454.okta.com/oauth2/default/v1/authorize?response_type=code&client_id=0oa2amci5xaQcrWbF357&scope=openid%20profile%20email%20address%20phone%20offline_access&state=yXMfdZoYPFl3yoISRnJLftlFnXmf3AnBgnUdGk0MBAc%3D&redirect_uri=http://localhost:8080/login/oauth2/code/okta&nonce=sKf92sk_wHRT6Zq1XwEw-NdYwik-CRMgXLZa_3jfKpA'
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.MediaTypeRequestMatcher - Processing image/avif
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.MediaTypeRequestMatcher - application/xhtml+xml .isCompatibleWith image/avif = false
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.w.h.writers.HstsHeaderWriter - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@61505c39
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.MediaTypeRequestMatcher - image/* .isCompatibleWith image/avif = true
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - All requestMatchers returned true
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - All requestMatchers returned true
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.NegatedRequestMatcher - matches = false
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Did not match
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.a.DelegatingAuthenticationEntryPoint - No match found. Using default entry point org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint@693f1240
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.web.DefaultRedirectStrategy - Redirecting to 'http://localhost:8080/login'
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.h.writers.HstsHeaderWriter - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@61505c39
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.security.web.FilterChainProxy - /login at position 1 of 17 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.security.web.FilterChainProxy - /login at position 2 of 17 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - HttpSession returned null object for SPRING_SECURITY_CONTEXT
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade@422bdd20. A new one will be created.
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.security.web.FilterChainProxy - /login at position 3 of 17 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.security.web.FilterChainProxy - /login at position 4 of 17 in additional filter chain; firing Filter: 'CorsFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-6] TRACE o.s.c.n.zuul.web.ZuulHandlerMapping - Mapped to HandlerExecutionChain with [org.springframework.cloud.netflix.zuul.web.ZuulController@3ce8f88f] and 1 interceptors
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.security.web.FilterChainProxy - /login at position 5 of 17 in additional filter chain; firing Filter: 'CsrfFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Trying to match using org.springframework.security.web.csrf.CsrfFilter$DefaultRequiresCsrfMatcher@3f60847a
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Did not match
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.security.web.FilterChainProxy - /login at position 6 of 17 in additional filter chain; firing Filter: 'LogoutFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Request 'GET /login' doesn't match 'POST /logout'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.security.web.FilterChainProxy - /login at position 7 of 17 in additional filter chain; firing Filter: 'OAuth2AuthorizationRequestRedirectFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/login'; against '/oauth2/authorization/registrationId'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.security.web.FilterChainProxy - /login at position 8 of 17 in additional filter chain; firing Filter: 'OAuth2LoginAuthenticationFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/login'; against '/login/oauth2/code/*'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.security.web.FilterChainProxy - /login at position 9 of 17 in additional filter chain; firing Filter: 'DefaultLoginPageGeneratingFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.s.w.h.writers.HstsHeaderWriter - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@61505c39

在浏览器控制台上我看到了错误

Access to XMLHttpRequest at 'https://dev-770454.okta.com/oauth2/default/v1/authorize?response_type=code&client_id=0oa2amci5xaQcrWbF357&scope=openid%20profile%20email%20address%20phone%20offline_access&state=yXMfdZoYPFl3yoISRnJLftlFnXmf3AnBgnUdGk0MBAc%3D&redirect_uri=http://localhost:8080/login/oauth2/code/okta&nonce=sKf92sk_wHRT6Zq1XwEw-NdYwik-CRMgXLZa_3jfKpA' (redirected from 'http://localhost:8080/verification') from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

此 CORS 错误是否应该由网关上定义的过滤器处理,或者是否未应用 CORS 过滤器?让我感到困惑的是,如果我转到浏览器并单击刷新按钮,它就可以正常工作。

【问题讨论】:

【参考方案1】:

来自Spring Security's documentation:

OAuth2RefreshToken 可以选择在授权码和密码授权类型的访问令牌响应中返回。如果OAuth2AuthorizedClient.getRefreshToken()可用且OAuth2AuthorizedClient.getAccessToken()已过期,则RefreshTokenOAuth2AuthorizedClientProvider会自动刷新。

检查 Okta 应用程序中的刷新令牌框不足以获取刷新令牌。您还需要传入offline_access 范围,否则不会返回。

我建议您升级到 Okta Spring Boot starter 2.0.0 并使用以下范围属性:

okta.oauth2.scopes=openid, email, profile, offline_access

【讨论】:

我在 OAuth2AuthorizedClient 的令牌中都有。 offline_access 的范围是缺少的。让我试试看。 现在我遇到了 CORS 问题,CORS 过滤器不应该处理它吗? Okta 或 API 出现 CORS 错误?您能否添加错误/更新您的原始问题? CORS 错误似乎来自网关。我在 Okta 中看不到错误日志。已更新 update1 中的日志 如果 CORS 错误来自 Okta,您需要在 API > Trusted Origins 的 Okta 开发人员仪表板中添加 http://localhost:8080。如果它来自您的网关,则需要为 Spring Security 配置 CORS。 ***.com/a/60962180/65681【参考方案2】:

如果你有刷新令牌,你可以send it to the /token endpoint to get a new access token。您只想决定是抢先获取新令牌(基于它何时到期)还是重新获取(如果现有令牌停止工作,则请求新的访问令牌)。

【讨论】:

我确实编写了一个 zuul 过滤器来执行此操作,并且能够获取新令牌。在将令牌发送到 UI 并将其存储在 cookie 中之后。当使用新令牌发出新请求时,错误。 2021-01-28 11:45:43 [http-nio-8080-exec-1] DEBUG o.s.s.w.a.ExceptionTranslationFilter - 访问被拒绝(用户是匿名的);重定向到身份验证入口点 org.springframework.security.access.AccessDeniedException:访问被拒绝

以上是关于如何为 Okta 自动刷新不记名令牌的主要内容,如果未能解决你的问题,请参考以下文章

刷新令牌如何为 jwt 添加安全性?

Jhipster 隐藏不记名令牌

在 asp.net vnext 上使用不记名令牌身份验证刷新令牌

使用 Moya 刷新身份验证令牌

在 Skype For Business Online 上自动刷新 UCWA 令牌

在 React 中无效时自动刷新令牌