Skip to content

Add Support for Opaque OAuth2 Tokens to Resource Server #6352

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

Merged
merged 3 commits into from
Feb 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -22,6 +22,7 @@
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
Expand All @@ -31,6 +32,7 @@
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.OAuth2IntrospectionAuthenticationProvider;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
Expand Down Expand Up @@ -86,6 +88,10 @@
* </li>
* </ul>
*
* <p>
* When using {@link #opaque()}, supply an introspection endpoint and its authentication configuration
* </p>
*
* <h2>Security Filters</h2>
*
* The following {@code Filter}s are populated when {@link #jwt()} is configured:
Expand Down Expand Up @@ -123,7 +129,9 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
private final ApplicationContext context;

private BearerTokenResolver bearerTokenResolver;

private JwtConfigurer jwtConfigurer;
private OpaqueTokenConfigurer opaqueTokenConfigurer;

private AccessDeniedHandler accessDeniedHandler = new BearerTokenAccessDeniedHandler();
private AuthenticationEntryPoint authenticationEntryPoint = new BearerTokenAuthenticationEntryPoint();
Expand Down Expand Up @@ -160,6 +168,14 @@ public JwtConfigurer jwt() {
return this.jwtConfigurer;
}

public OpaqueTokenConfigurer opaqueToken() {
if (this.opaqueTokenConfigurer == null) {
this.opaqueTokenConfigurer = new OpaqueTokenConfigurer();
}

return this.opaqueTokenConfigurer;
}

@Override
public void init(H http) throws Exception {
registerDefaultAccessDeniedHandler(http);
Expand All @@ -182,24 +198,34 @@ public void configure(H http) throws Exception {

http.addFilter(filter);

if ( this.jwtConfigurer == null ) {
throw new IllegalStateException("Jwt is the only supported format for bearer tokens " +
"in Spring Security and no Jwt configuration was found. Make sure to specify " +
"a jwk set uri by doing http.oauth2ResourceServer().jwt().jwkSetUri(uri), or wire a " +
"JwtDecoder instance by doing http.oauth2ResourceServer().jwt().decoder(decoder), or " +
"expose a JwtDecoder instance as a bean and do http.oauth2ResourceServer().jwt().");
if (this.jwtConfigurer != null && this.opaqueTokenConfigurer != null) {
throw new IllegalStateException("Spring Security only supports JWTs or Opaque Tokens, not both at the " +
"same time");
}

if (this.jwtConfigurer == null && this.opaqueTokenConfigurer == null) {
throw new IllegalStateException("Jwt and Opaque Token are the only supported formats for bearer tokens " +
"in Spring Security and neither was found. Make sure to configure JWT " +
"via http.oauth2ResourceServer().jwt() or Opaque Tokens via " +
"http.oauth2ResourceServer().opaque().");
}

JwtDecoder decoder = this.jwtConfigurer.getJwtDecoder();
Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter =
this.jwtConfigurer.getJwtAuthenticationConverter();
if (this.jwtConfigurer != null) {
JwtDecoder decoder = this.jwtConfigurer.getJwtDecoder();
Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter =
this.jwtConfigurer.getJwtAuthenticationConverter();

JwtAuthenticationProvider provider =
new JwtAuthenticationProvider(decoder);
provider.setJwtAuthenticationConverter(jwtAuthenticationConverter);
provider = postProcess(provider);

JwtAuthenticationProvider provider =
new JwtAuthenticationProvider(decoder);
provider.setJwtAuthenticationConverter(jwtAuthenticationConverter);
provider = postProcess(provider);
http.authenticationProvider(provider);
}

http.authenticationProvider(provider);
if (this.opaqueTokenConfigurer != null) {
http.authenticationProvider(this.opaqueTokenConfigurer.getProvider());
}
}

public class JwtConfigurer {
Expand Down Expand Up @@ -248,6 +274,31 @@ JwtDecoder getJwtDecoder() {
}
}

public class OpaqueTokenConfigurer {
private String introspectionUri;
private String introspectionClientId;
private String introspectionClientSecret;

public OpaqueTokenConfigurer introspectionUri(String introspectionUri) {
Assert.notNull(introspectionUri, "introspectionUri cannot be null");
this.introspectionUri = introspectionUri;
return this;
}

public OpaqueTokenConfigurer introspectionClientCredentials(String clientId, String clientSecret) {
Assert.notNull(clientId, "clientId cannot be null");
Assert.notNull(clientSecret, "clientSecret cannot be null");
this.introspectionClientId = clientId;
this.introspectionClientSecret = clientSecret;
return this;
}

AuthenticationProvider getProvider() {
return new OAuth2IntrospectionAuthenticationProvider(this.introspectionUri,
this.introspectionClientId, this.introspectionClientSecret);
}
}

private void registerDefaultAccessDeniedHandler(H http) {
ExceptionHandlingConfigurer<H> exceptionHandling = http
.getConfigurer(ExceptionHandlingConfigurer.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -1109,7 +1109,7 @@ public void configuredWhenMissingJwtAuthenticationProviderThenWiringException()

assertThatCode(() -> this.spring.register(JwtlessConfig.class).autowire())
.isInstanceOf(BeanCreationException.class)
.hasMessageContaining("no Jwt configuration was found");
.hasMessageContaining("neither was found");
}

@Test
Expand All @@ -1120,6 +1120,13 @@ public void configureWhenMissingJwkSetUriThenWiringException() {
.hasMessageContaining("No qualifying bean of type");
}

@Test
public void configureWhenUsingBothJwtAndOpaqueThenWiringException() {
assertThatCode(() -> this.spring.register(OpaqueAndJwtConfig.class).autowire())
.isInstanceOf(BeanCreationException.class)
.hasMessageContaining("Spring Security only supports JWTs or Opaque Tokens");
}

// -- support

@EnableWebSecurity
Expand Down Expand Up @@ -1623,6 +1630,19 @@ JwtDecoder decoder() throws Exception {
}
}

@EnableWebSecurity
static class OpaqueAndJwtConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.oauth2ResourceServer()
.jwt()
.and()
.opaqueToken();
}
}

@Configuration
static class JwtDecoderConfig {
@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ dependencies {
compile springCoreDependency

optional project(':spring-security-oauth2-jose')
optional 'com.nimbusds:oauth2-oidc-sdk'
optional 'io.projectreactor:reactor-core'
optional 'org.springframework:spring-webflux'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
public abstract class AbstractOAuth2TokenAuthenticationToken<T extends AbstractOAuth2Token> extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

private Object principal;
private Object credentials;
private T token;

/**
Expand All @@ -64,9 +66,20 @@ protected AbstractOAuth2TokenAuthenticationToken(
T token,
Collection<? extends GrantedAuthority> authorities) {

super(authorities);
this(token, token, token, authorities);
}

protected AbstractOAuth2TokenAuthenticationToken(
T token,
Object principal,
Object credentials,
Collection<? extends GrantedAuthority> authorities) {

super(authorities);
Assert.notNull(token, "token cannot be null");
Assert.notNull(principal, "principal cannot be null");
this.principal = principal;
this.credentials = credentials;
this.token = token;
}

Expand All @@ -75,15 +88,15 @@ protected AbstractOAuth2TokenAuthenticationToken(
*/
@Override
public Object getPrincipal() {
return this.getToken();
return this.principal;
}

/**
* {@inheritDoc}
*/
@Override
public Object getCredentials() {
return this.getToken();
return this.credentials;
}

/**
Expand Down
Loading