3
votes

I want to create an authorization server using Spring Oauth which is able to issue it's own JWT tokens. The authorization server must delegate the authentication to Google. I have been following this tutorial which does almost everything I want: https://spring.io/guides/tutorials/spring-boot-oauth2/

I was able to add Google as an authentication provider, but I'm struggling with the JWT part.

Here's my authorization server configuration:

@SpringBootApplication 
@EnableOAuth2Client
@EnableAuthorizationServer
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class MsAuthorizationGmailApplication extends WebSecurityConfigurerAdapter {

    @Autowired
    OAuth2ClientContext oauth2ClientContext;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/**").authorizeRequests().antMatchers("/", "/login**", "/webjars/**").permitAll().anyRequest()
            .authenticated().and().exceptionHandling()
            .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login/gmail")).and().logout()
            .logoutSuccessUrl("/").permitAll().and().csrf()
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
            .addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
    }

    @Bean
    @ConfigurationProperties("gmail")
    public ClientResources gmail() {
        return new ClientResources();
    }

    private Filter ssoFilter() {
        CompositeFilter filter = new CompositeFilter();
        List<Filter> filters = new ArrayList<>();
        filters.add(ssoFilter(gmail(), "/login/gmail"));
        filter.setFilters(filters);
        return filter;
    }

    private Filter ssoFilter(ClientResources client, String path) {
        OAuth2ClientAuthenticationProcessingFilter filter = new     OAuth2ClientAuthenticationProcessingFilter(
            path);
        OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext);
        filter.setRestTemplate(template);
        filter.setTokenServices(JwtConfig.tokenServices());
        return filter;
    }

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

}

In the JWT config I'm not trying to do anything fancy, just trying to make it pass for now:

public final class JwtConfig {

    private static final String KEY = "123";

    private JwtConfig() {
    }

    private static JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(KEY);
        return converter;
    }

    private static TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    public static DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }
}

I get the following exception:

org.springframework.security.authentication.BadCredentialsException: Could not obtain user details from token
at org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter.attemptAuthentication(OAuth2ClientAuthenticationProcessingFilter.java:122) ~[spring-security-oauth2-2.0.12.RELEASE.jar:na]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) ~[spring-security-web-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:112) [spring-web-4.3.6.RELEASE.jar:4.3.6.RELEASE]
at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:73) [spring-web-4.3.6.RELEASE.jar:4.3.6.RELEASE]
....
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.11.jar:8.5.11]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_121]
Caused by: org.springframework.security.oauth2.common.exceptions.InvalidTokenException: Cannot convert access token to JSON
at org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter.decode(JwtAccessTokenConverter.java:287) ~[spring-security-oauth2-2.0.12.RELEASE.jar:na]
at org.springframework.security.oauth2.provider.token.store.JwtTokenStore.convertAccessToken(JwtTokenStore.java:88) ~[spring-security-oauth2-2.0.12.RELEASE.jar:na]
at org.springframework.security.oauth2.provider.token.store.JwtTokenStore.readAccessToken(JwtTokenStore.java:80) ~[spring-security-oauth2-2.0.12.RELEASE.jar:na]
at org.springframework.security.oauth2.provider.token.DefaultTokenServices.loadAuthentication(DefaultTokenServices.java:229) ~[spring-security-oauth2-2.0.12.RELEASE.jar:na]
at org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter.attemptAuthentication(OAuth2ClientAuthenticationProcessingFilter.java:112) ~[spring-security-oauth2-2.0.12.RELEASE.jar:na]
... 62 common frames omitted
Caused by: java.lang.IllegalArgumentException: JWT must have 3 tokens
at org.springframework.security.jwt.JwtHelper.decode(JwtHelper.java:49) ~[spring-security-jwt-1.0.0.RELEASE.jar:na]
at org.springframework.security.jwt.JwtHelper.decodeAndVerify(JwtHelper.java:74) ~[spring-security-jwt-1.0.0.RELEASE.jar:na]
at org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter.decode(JwtAccessTokenConverter.java:277) ~[spring-security-oauth2-2.0.12.RELEASE.jar:na]
... 66 common frames omitted

How I understand this: It looks like that when Google issues an access token, the authorization server (being a client of Google OAuth) tries to decode the access token as a JWT, and throws an exception because Google's token is not a valid JWT (it's just an access token).

I would like to create a JWT containing the access token (which will be used to access Google APIs) and some additional information about the user. I would also like to be able to refresh the JWT token when the access token expires. Is there any way to achieve this?

1

1 Answers

0
votes

Am not sure about GMail but for your own Authorization server, you can add one token enhancer JwtAccessTokenConverter which will convert you token into JWT.

For sample, pls refer oauth2-spring-boot-mongo-jwt-sample

Generally, normal token payload is of below type

{
    "access_token": "bc9c021f-b5ae-43af-9746-737b533f9bc5",
    "token_type": "bearer",
    "refresh_token": "fee7a2a1-eff9-4757-8dd3-5392ee225bea",
    "expires_in": 43199,
    "scope": "read-foo" }

whereas, JWT looks something like this

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZm9vIl0sInVzZXJfbmFtZSI6InVzZXIiLCJzY29wZSI6WyJyZWFkLWZvbyJdLCJleHAiOjE1MTQ3ODMwNTIsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiJdLCJqdGkiOiJlMjM4MDg1YS0xZjFjLTQ5ZWQtODNiMC1iN2Q1MjI5OWUwZjYiLCJjbGllbnRfaWQiOiJ3ZWItY2xpZW50In0.-OSw1Vr4o1dnAQL3n7QFGG6UOXr4itc0Kp8dugyT4zU",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZm9vIl0sInVzZXJfbmFtZSI6InVzZXIiLCJzY29wZSI6WyJyZWFkLWZvbyJdLCJhdGkiOiJlMjM4MDg1YS0xZjFjLTQ5ZWQtODNiMC1iN2Q1MjI5OWUwZjYiLCJleHAiOjE1MTczMzE4NTIsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiJdLCJqdGkiOiIzYTA2OTZmMy1mYzg1LTQ2YTEtYjVlMC01NmQ2OGVmYTJhMmUiLCJjbGllbnRfaWQiOiJ3ZWItY2xpZW50In0.jSBriPfM-rSgHHLyifIuBHwrwCkyb5I2u2AKa8kQUUU",
    "expires_in": 43199,
    "scope": "read-foo",
    "jti": "e238085a-1f1c-49ed-83b0-b7d52299e0f6"
}