Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Access to microservice from gateway failed after 5 minute idle, re authentication required #9707

Closed
1 task done
eapriansyah opened this issue May 8, 2019 · 20 comments
Closed
1 task done
Labels
area: bug 🐛 $$ bug-bounty $$ https://www.jhipster.tech/bug-bounties/ theme: microservice theme: OIDC/OAuth2 $100 https://www.jhipster.tech/bug-bounties/
Milestone

Comments

@eapriansyah
Copy link

eapriansyah commented May 8, 2019

Overview of the issue

When the user is idle for more than 5 minutes, access from the browser to microservice is not authenticated.

Motivation for or Use Case

After 5 minute idle, access to the entire menu in the gateway, no need for authentication, only related to the microservice that needs authentication. For public users, it will be a bit confusing.

Reproduce the error
  1. Create a gateway application with security oauth2.
  2. Create a microservice application with security oauth2.
  3. Create one entity with the description field on microservice.
  4. run "jhipster entity Data1" from the command prompt
  5. Copy entity to gateway, do same thing with command "jhipster entity Data1 --skip-server"
  6. Run gateway and microservice, open Data-1 from entities->Data 1
**Gateway Configuration **

jhipster info
INFO! Using JHipster version installed locally in current project's node_modules
INFO! Executing jhipster:info
INFO! Options: from-cli: true
Welcome to the JHipster Information Sub-Generator

JHipster Version(s)
gateway@0.0.0 D:\tmp\check-zuul\gw
`-- generator-jhipster@6.0.0

JHipster configuration, a .yo-rc.json file generated in the root folder
.yo-rc.json file
{
  "generator-jhipster": {
    "promptValues": {
      "packageName": "com.mycompany"
    },
    "jhipsterVersion": "6.0.0",
    "applicationType": "gateway",
    "baseName": "gateway",
    "packageName": "com.mycompany",
    "packageFolder": "com/mycompany",
    "serverPort": "8080",
    "authenticationType": "oauth2",
    "cacheProvider": "ehcache",
    "enableHibernateCache": false,
    "websocket": false,
    "databaseType": "sql",
    "devDatabaseType": "h2Disk",
    "prodDatabaseType": "mysql",
    "searchEngine": false,
    "messageBroker": false,
    "serviceDiscoveryType": "eureka",
    "buildTool": "maven",
    "enableSwaggerCodegen": false,
    "clientFramework": "angularX",
    "clientTheme": "none",
    "clientThemeVariant": "",
    "useSass": true,
    "clientPackageManager": "npm",
    "testFrameworks": [],
    "jhiPrefix": "jhi",
    "entitySuffix": "",
    "dtoSuffix": "DTO",
    "otherModules": [],
    "enableTranslation": false
  }
}
JDL for the Entity configuration(s) entityName.json files generated in the .jhipster directory
JDL entity definitions
entity Data1 {
  description String
}
dto Data1 with mapstruct
paginate Data1 with pagination
service Data1 with serviceClass
microservice Data1 with svc
clientRootFolder Data1 with svc

Environment and Tools

java version "1.8.0_202"
Java(TM) SE Runtime Environment (build 1.8.0_202-b08)
Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)

git version 2.19.1.windows.1

node: v10.15.3

npm: 6.9.0

yeoman: 2.0.6

yarn: 1.9.4

Docker version 18.09.2, build 6247962

docker-compose version 1.23.2, build 1110ad01

**Microservice Configuration **

jhipster info
INFO! Using JHipster version installed locally in current project's node_modules
INFO! Executing jhipster:info
INFO! Options: from-cli: true
Welcome to the JHipster Information Sub-Generator

JHipster Version(s)
svc@0.0.0 D:\tmp\check-zuul\svc
`-- generator-jhipster@6.0.0

JHipster configuration, a .yo-rc.json file generated in the root folder
.yo-rc.json file
{
  "generator-jhipster": {
    "promptValues": {
      "packageName": "com.mycompany"
    },
    "jhipsterVersion": "6.0.0",
    "applicationType": "microservice",
    "baseName": "svc",
    "packageName": "com.mycompany",
    "packageFolder": "com/mycompany",
    "serverPort": "8082",
    "authenticationType": "oauth2",
    "cacheProvider": "hazelcast",
    "enableHibernateCache": false,
    "websocket": false,
    "databaseType": "sql",
    "devDatabaseType": "h2Disk",
    "prodDatabaseType": "mysql",
    "searchEngine": false,
    "messageBroker": false,
    "serviceDiscoveryType": "eureka",
    "buildTool": "maven",
    "enableSwaggerCodegen": false,
    "jwtSecretKey": "bXktc2VjcmV0LXRva2VuLXRvLWNoYW5nZS1pbi1wcm9kdWN0aW9uLWFuZC10by1rZWVwLWluLWEtc2VjdXJlLXBsYWNl",
    "testFrameworks": [],
    "jhiPrefix": "jhi",
    "entitySuffix": "",
    "dtoSuffix": "DTO",
    "otherModules": [],
    "enableTranslation": false,
    "clientPackageManager": "npm",
    "skipClient": true,
    "skipUserManagement": true
  }
}
JDL for the Entity configuration(s) entityName.json files generated in the .jhipster directory
JDL entity definitions
entity Data1 {
  description String
}
dto Data1 with mapstruct
paginate Data1 with pagination
service Data1 with serviceClass
microservice Data1 with svc
clientRootFolder Data1 with svc

Environment and Tools

java version "1.8.0_202"
Java(TM) SE Runtime Environment (build 1.8.0_202-b08)
Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)

git version 2.19.1.windows.1

node: v10.15.3

npm: 6.9.0

yeoman: 2.0.6

yarn: 1.9.4

Docker version 18.09.2, build 6247962

docker-compose version 1.23.2, build 1110ad01

  • Checking this box is mandatory (this is just to show you read everything)

First Open Page:

image

After 5 Minute, and click edit in Form:
image

image

@mraible
Copy link
Contributor

mraible commented May 8, 2019

You mentioned "Microsoft". Are you using Microsoft as your OIDC provider? If so, does the same behavior happen with Keycloak?

@eapriansyah
Copy link
Author

eapriansyah commented May 8, 2019

No that is typo, not microsoft, I mean microservice.
OIDC that I use keycloak 6.0.1.

@mraible
Copy link
Contributor

mraible commented May 8, 2019

Hmmm, it sounds like Spring Security might not be fetching refresh tokens and renewing access tokens for you. I'm not sure how to fix just yet.

@ruddell
Copy link
Member

ruddell commented May 8, 2019

It looks like the path name is included in the OAuth2 redirect here, it should only include the base HREF and not the path. I did a fix for websockets which had a similar issue (changes)

@Tcharl
Copy link
Contributor

Tcharl commented May 9, 2019

Same behavior here: no refresh token fetched from Keycloak (part of the OAuth2AuthenticationToken)

@Tcharl
Copy link
Contributor

Tcharl commented May 15, 2019

Let me historize what I'm doing within my current project to have a 6.0.0 oauth2+Microservice support.
Feel free to comment (or even implement a PR based on it if you're sure that's the right way, I'm not that available ATM but will try to make something if I don't see it implemented in a month). I'll continue to amend that comment until I'll have something which 'just works'.

  • Angular login.service
    Use the trick by @ruddell to redirect to the base href
    const baseHref = document.querySelector('base').getAttribute('href').replace(//$/, '');

    location.href = `//${location.hostname}${port}${baseHref}/oauth2/authorization/oidc`;
  • expired and error interceptors

Change err.url.includes('/api/account') by err.url.includes('api/account')

  • create an angular component and route to avoid it complaining about non existing mapping at the token response

export const OAUTH_ROUTE: Route = {
  path: 'login/oauth2/code/oidc',
  component: OauthComponent,
};
export const AUTH_ROUTES = [LOGIN_ROUTE, OAUTH_ROUTE];

@Component({
  selector: 'jhi-login',
  templateUrl: './login.component.html',
  styleUrls: ['login.component.scss']
})
export class OauthComponent implements OnInit {
  constructor(private router: Router, private logger: NGXLogger) {}

  ngOnInit() {
    this.router.navigate(['/']).then(() => this.logger.debug('redirected user after auth'));
  }
}

  • Security configuration
    redirect oauth auth to a login page that calls the login service if not authenticated on init
       .and()
            .oauth2Login()
            .loginPage("/")
  • Create a JwtConfiguration containing the decoder to be able to consume it as a bean (not mandatory)

  • Create an implementation of JwtAuthenticationConverter


@Configuration
public class AuthorityConfiguration extends JwtAuthenticationConverter {

    public AuthorityConfiguration() {
    }

    protected Collection<GrantedAuthority> extractAuthorities(Jwt jwt) {
        return SecurityUtils.extractAuthorityFromClaims(jwt.getClaims());
    }
}
public static List<GrantedAuthority> extractAuthorityFromClaims(Map<String, Object> claims) {
        return ((JSONArray)((JSONObject)claims.get("realm_access")).get("roles")).stream()
            .map(group -> ((String) group))
            .filter(group -> group.startsWith("ROLE_"))
            .map(SimpleGrantedAuthority::new).collect(Collectors.toList());
    }
  • Amend the AuthorizationHeaderUtil to handle either Oauth2AccessToken (gateway) and Jwt (service to service with Feign). Also Throw an exception while the token as expired (may be we can throw it in another place like a filter?
public Optional<String> getAuthorizationHeader() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication instanceof OAuth2AuthenticationToken) {
            OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
            String name =  oauthToken.getName();
            String registrationId = oauthToken.getAuthorizedClientRegistrationId();
            OAuth2AuthorizedClient client = clientService.loadAuthorizedClient(
                registrationId,
                name);

            if (null == client) {
                throw new OAuth2AuthorizationException(new OAuth2Error("access_denied", "The token is expired", null));
            }
            OAuth2AccessToken accessToken = client.getAccessToken();

            if (accessToken != null) {
                String tokenType = accessToken.getTokenType().getValue();
                String accessTokenValue = accessToken.getTokenValue();
                if (isExpired(accessToken)) {
                    log.info("AccessToken expired, refreshing automatically");
                    accessTokenValue = refreshToken(client, oauthToken);
                    if (null == accessTokenValue) {
                        SecurityContextHolder.getContext().setAuthentication(null);
                        throw new OAuth2AuthorizationException(new OAuth2Error("access_denied", "The token is expired", null));
                    }
                }
                String authorizationHeaderValue = String.format("%s %s", tokenType, accessTokenValue);
                return Optional.of(authorizationHeaderValue);
            }

        } else if (authentication instanceof JwtAuthenticationToken) {
            JwtAuthenticationToken accessToken = (JwtAuthenticationToken) authentication;
            String tokenType = (String) accessToken.getToken().getClaims().get("typ");
            String tokenValue = accessToken.getToken().getTokenValue();
            String authorizationHeaderValue = String.format("%s %s", tokenType, tokenValue);
            return Optional.of(authorizationHeaderValue);
        }
        return Optional.empty();
    }

    private String refreshToken(OAuth2AuthorizedClient client, OAuth2AuthenticationToken oauthToken) {
        OAuth2AccessTokenResponse atr = refreshTokenClient(client);
        if (atr == null || atr.getAccessToken() == null) {
            log.info("Failed to refresh token for ${currentUser.name}");
            return null;
        }

        OAuth2RefreshToken refreshToken = atr.getRefreshToken() != null? atr.getRefreshToken(): client.getRefreshToken();
        OAuth2AuthorizedClient updatedClient = new OAuth2AuthorizedClient(
            client.getClientRegistration(),
            client.getPrincipalName(),
            atr.getAccessToken(),
            refreshToken
        );

        clientService.saveAuthorizedClient(updatedClient, oauthToken);
        return atr.getAccessToken().getTokenValue();
    }

    private OAuth2AccessTokenResponse refreshTokenClient(OAuth2AuthorizedClient currentClient ) {

        MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
        formParameters.add(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.REFRESH_TOKEN.getValue());
        formParameters.add(OAuth2ParameterNames.REFRESH_TOKEN, currentClient.getRefreshToken().getTokenValue());
        formParameters.add(OAuth2ParameterNames.CLIENT_ID, currentClient.getClientRegistration().getClientId());
        RequestEntity requestEntity = RequestEntity
            .post(URI.create(currentClient.getClientRegistration().getProviderDetails().getTokenUri()))
            .contentType(MediaType.APPLICATION_FORM_URLENCODED)
            .body(formParameters);
        try {
            RestTemplate r = restTemplate(currentClient.getClientRegistration().getClientId(), currentClient.getClientRegistration().getClientSecret());
            ResponseEntity<OAuthIdpTokenResponseDTO> responseEntity = r.exchange(requestEntity, OAuthIdpTokenResponseDTO.class);
            return toOAuth2AccessTokenResponse(responseEntity.getBody());
        } catch (OAuth2AuthorizationException e) {
            log.error("Unable to refresh token ${e.error.errorCode}");
            throw new OAuth2AuthenticationException(e.getError(), e);
        }
    }

    private OAuth2AccessTokenResponse toOAuth2AccessTokenResponse(OAuthIdpTokenResponseDTO oAuthIdpResponse) {
        Map<String, Object> additionalParameters = new HashMap<>();
        additionalParameters.put("id_token", oAuthIdpResponse.getIdToken());
        additionalParameters.put("not-before-policy", oAuthIdpResponse.getNotBefore());
        additionalParameters.put("refresh_expires_in", oAuthIdpResponse.getRefreshExpiresIn());
        additionalParameters.put("session_state", oAuthIdpResponse.getSessionState());
        return OAuth2AccessTokenResponse.withToken(oAuthIdpResponse.getAccessToken())
            .expiresIn(oAuthIdpResponse.getExpiresIn())
            .refreshToken(oAuthIdpResponse.getRefreshToken())
            .scopes(Pattern.compile("\\s").splitAsStream(oAuthIdpResponse.getScope()).collect(Collectors.toSet()))
            .tokenType(OAuth2AccessToken.TokenType.BEARER)
            .additionalParameters(additionalParameters)
            .build();
    }

    private RestTemplate restTemplate(String clientId, String clientSecret)

    {
        return restTemplateBuilder
            .additionalMessageConverters(
                new FormHttpMessageConverter(),
                new OAuth2AccessTokenResponseHttpMessageConverter())
            .errorHandler(new OAuth2ErrorResponseErrorHandler())
            .basicAuthentication(clientId, clientSecret)
            .build();
    }

    private boolean isExpired(OAuth2AccessToken accessToken) {
        Instant now = Instant.now();
        Instant expiresAt = accessToken.getExpiresAt();
        return now.isAfter(expiresAt.minus(Duration.ofMinutes(1L)));
    }

OAuthIdpTokenResponseDTO {
@JsonProperty("token_type")
    private String tokenType;

    private String scope;

    @JsonProperty("expires_in")
    private Long expiresIn;

    @JsonProperty("ext_expires_in")
    private Long extExpiresIn;

    @JsonProperty("expires_on")
    private Long expiresOn;

    @JsonProperty("not-before-policy")
    private Long notBefore;

    private UUID resource;

    @JsonProperty("access_token")
    private String accessToken;

    @JsonProperty("refresh_token")
    private String refreshToken;

    @JsonProperty("id_token")
    private String idToken;

    @JsonProperty("session_state")
    private String sessionState;

    @JsonProperty("refresh_expires_in")
    private String refreshExpiresIn;
}
  • Throw a 401 in case of OAuth2AuthorizationException (which will trigger another login dance):

 @ExceptionHandler
    public ResponseEntity<Problem> handleConcurrencyFailure(OAuth2AuthorizationException ex, NativeWebRequest request) {
        Problem problem = Problem.builder()
            .withStatus(Status.UNAUTHORIZED)
            .with(MESSAGE_KEY, ErrorConstants.AUTH_EXPIRED)
            .build();
        return create(ex, problem, request);
    }

Side stuff:

Update the GrantedAuthorityMapper to handle Keycloak mappings:


/**
     * Map authorities from "groups" or "roles" claim in ID Token.
     *
     * @return a {@link GrantedAuthoritiesMapper} that maps groups from
     * the IdP to Spring Security Authorities.
     */
    @Bean
    @SuppressWarnings("unchecked")
    public GrantedAuthoritiesMapper userAuthoritiesMapper() {
        return (authorities) -> {
            Set<GrantedAuthority> mappedAuthorities = new HashSet<>();

            authorities.forEach(authority -> {
                OidcUserAuthority oidcUserAuthority = (OidcUserAuthority) authority;
                OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
                Collection<String> groups = (Collection<String>) userInfo.getClaims().get("roles");
                mappedAuthorities.addAll(groups.stream()
                    .filter(group -> group.startsWith("ROLE_"))
                    .map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
            });

            return mappedAuthorities;
        };
    }

@mraible
Copy link
Contributor

mraible commented May 15, 2019

Could be related to this issue: spring-attic/spring-security-oauth2-boot#125

@eapriansyah
Copy link
Author

The solution to overcome this problem, as follows.

  1. Add spring-cloud-starter-oauth2 in pom.

    org.springframework.cloud
    spring-cloud-starter-oauth2
  2. Change SecurityConfiguration.java as follows:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@EnableOAuth2Sso
@import(SecurityProblemSupport.class)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

private final CorsFilter corsFilter;

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

private final SecurityProblemSupport problemSupport;

public SecurityConfiguration(CorsFilter corsFilter, SecurityProblemSupport problemSupport) {
    this.corsFilter = corsFilter;
    this.problemSupport = problemSupport;
}

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring()
        .antMatchers(HttpMethod.OPTIONS, "/**")
        .antMatchers("/app/**/*.{js,html}")
        .antMatchers("/i18n/**")
        .antMatchers("/content/**")
        .antMatchers("/swagger-ui/index.html")
        .antMatchers("/test/**");
}

@Override
public void configure(HttpSecurity http) throws Exception {
    // @formatter:off
    http
        .csrf()
        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
    .and()
        .addFilterBefore(corsFilter, CsrfFilter.class)
        // .addFilterAfter(oAuth2AuthenticationProcessingFilter(), AbstractPreAuthenticatedProcessingFilter.class)
        .exceptionHandling()
        .accessDeniedHandler(problemSupport)
    .and()
        .headers()
        .frameOptions()
        .disable()
    .and()
        .authorizeRequests()
        .antMatchers("/api/**").authenticated()
        .antMatchers("/api/auth-info").permitAll()
        .antMatchers("/websocket/tracker").hasAuthority(AuthoritiesConstants.ADMIN)
        .antMatchers("/websocket/**").permitAll()
        .antMatchers("/management/health").permitAll()
        .antMatchers("/management/info").permitAll()
        .antMatchers("/management/prometheus").permitAll()
        .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
    .and()
        .oauth2Login()
    .and()
        .oauth2ResourceServer().jwt();
    // @formatter:on
}

/**
 * Map authorities from "groups" or "roles" claim in ID Token.
 *
 * @return a {@link GrantedAuthoritiesMapper} that maps groups from
 * the IdP to Spring Security Authorities.
 */
@Bean
@SuppressWarnings("unchecked")
public GrantedAuthoritiesMapper userAuthoritiesMapper() {
    return (authorities) -> {
        Set<GrantedAuthority> mappedAuthorities = new HashSet<>();

        authorities.forEach(authority -> {
            OidcUserAuthority oidcUserAuthority = (OidcUserAuthority) authority;
            OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
            Collection<String> groups = (Collection<String>) userInfo.getClaims().get("groups");
            if (groups == null) {
                groups = (Collection<String>) userInfo.getClaims().get("roles");
            }
            mappedAuthorities.addAll(groups.stream()
                .filter(group -> group.startsWith("ROLE_"))
                .map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
        });

        return mappedAuthorities;
    };
}

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

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

    jwtDecoder.setJwtValidator(withAudience);

    return jwtDecoder;
}

@Bean
public AuthorizationHeaderFilter authHeaderFilter(AuthorizationHeaderUtil headerUtil) {
    return new AuthorizationHeaderFilter(headerUtil);
}

@Bean
@ConfigurationProperties("spring.security.oauth2.client")
public ClientCredentialsResourceDetails oauth2RemoteResource() {
    return new ClientCredentialsResourceDetails();
}

@Bean
@Primary
public OAuth2RestTemplate restTemplate(OAuth2ClientContext clientContext) {
    return new OAuth2RestTemplate(oauth2RemoteResource(), clientContext);
}

}

@mraible
Copy link
Contributor

mraible commented May 16, 2019 via email

@eapriansyah
Copy link
Author

Yes it works, with additional bean:

@bean
@ConfigurationProperties("spring.security.oauth2.client")
public ClientCredentialsResourceDetails oauth2RemoteResource() {
return new ClientCredentialsResourceDetails();
}

@Bean
public OAuth2ClientContext oauth2ClientContext() {
   return new DefaultOAuth2ClientContext(new DefaultAccessTokenRequest());
}

@Bean
public OAuth2RestTemplate restTemplate(OAuth2ClientContext clientContext) {
    return new OAuth2RestTemplate(oauth2RemoteResource(), clientContext);
}

@eapriansyah
Copy link
Author

Alternatively, act as client refer from this https://docs.spring.io/spring-security-oauth2-boot/docs/current-SNAPSHOT/reference/htmlsingle/

@EnableOAuth2Client
and add bean:
@bean
@ConfigurationProperties("spring.security.oauth2.client")
public ClientCredentialsResourceDetails oauth2RemoteResource() {
return new ClientCredentialsResourceDetails();
}

@bean
public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext oauth2ClientContext,
OAuth2ProtectedResourceDetails details) {
return new OAuth2RestTemplate(details, oauth2ClientContext);
}

@mraible
Copy link
Contributor

mraible commented May 16, 2019 via email

@pascalgrimaud pascalgrimaud added the $100 https://www.jhipster.tech/bug-bounties/ label May 16, 2019
@pascalgrimaud
Copy link
Member

Just added the bounty @mraible

@mraible
Copy link
Contributor

mraible commented May 17, 2019

I've been able to reproduce this problem with Spring Boot and Spring Security. The fix suggested doesn't work. I'll consult with my colleagues and try to figure out a solution.

@eapriansyah
Copy link
Author

Sorry, I made a mistake. My test folder is mixed with jhipster v5, in the microservice. So the problem is still there. Sorry for this misunderstanding.

@Stjerndal
Copy link

I've been able to reproduce this problem with Spring Boot and Spring Security. The fix suggested doesn't work. I'll consult with my colleagues and try to figure out a solution.

So the bug is in Spring Security 5.1 rather than jhipster?

@mraible
Copy link
Contributor

mraible commented May 20, 2019

I think it's in JHipster. I believe this might be caused by a change I made in AuthorizationHeaderUtil:

https://github.com/jhipster/generator-jhipster/pull/9416/files#diff-0626afa9caefec7aca2255c6f6a50173L35

In the Spring Security 5.0 version, we used an OAuth2RestTemplate to get the access token, rather than from the OAuth2AuthorizedClient. We also had error handling when a UserRedirectRequiredException happened.

@mraible
Copy link
Contributor

mraible commented May 22, 2019

From @jzheaux of the Spring Security team:

You're absolutely right that the commit you linked to is making the difference. Spring Security 5.x doesn't yet automatically refresh the OAuth token. It does have an ExchangeFilterFunction that refreshes the token, which can be used with WebClient.

There's currently a 5.2 ticket open that Joe is working on that will lead to that logic being extracted into something more general purpose: spring-projects/spring-security#6811.

@Tcharl
Copy link
Contributor

Tcharl commented May 24, 2019

the getAuthorizationHeader() implementation I posted in the ticket refreshes the token (so it's OK for Feign calls), however, I don't know the new equivalent of the OAuth2RestTemplate to hook that token refresh.
Does anyone know which bean it is?

@Tcharl Tcharl mentioned this issue Jun 8, 2019
4 tasks
@jdubois jdubois added the $$ bug-bounty $$ https://www.jhipster.tech/bug-bounties/ label Jun 17, 2019
@pascalgrimaud
Copy link
Member

I'm closing this as #9874 has been merged.
Then, maybe it can be improved but the solution proposed by @Tcharl works!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: bug 🐛 $$ bug-bounty $$ https://www.jhipster.tech/bug-bounties/ theme: microservice theme: OIDC/OAuth2 $100 https://www.jhipster.tech/bug-bounties/
Projects
None yet
Development

No branches or pull requests

7 participants