Skip to content

Commit

Permalink
Multi-tenancy for Resource Server
Browse files Browse the repository at this point in the history
  • Loading branch information
jzheaux committed Mar 29, 2019
1 parent 35face5 commit aa1f0db
Show file tree
Hide file tree
Showing 15 changed files with 762 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.AuthenticationManagerResolver;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
Expand All @@ -32,9 +33,9 @@
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.authentication.OAuth2IntrospectionAuthenticationProvider;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
Expand Down Expand Up @@ -128,6 +129,7 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<

private final ApplicationContext context;

private AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver;
private BearerTokenResolver bearerTokenResolver;

private JwtConfigurer jwtConfigurer;
Expand All @@ -154,6 +156,13 @@ public OAuth2ResourceServerConfigurer<H> authenticationEntryPoint(Authentication
return this;
}

public OAuth2ResourceServerConfigurer<H> authenticationManagerResolver
(AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver) {
Assert.notNull(authenticationManagerResolver, "authenticationManagerResolver cannot be null");
this.authenticationManagerResolver = authenticationManagerResolver;
return this;
}

public OAuth2ResourceServerConfigurer<H> bearerTokenResolver(BearerTokenResolver bearerTokenResolver) {
Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null");
this.bearerTokenResolver = bearerTokenResolver;
Expand Down Expand Up @@ -188,10 +197,12 @@ public void configure(H http) throws Exception {
BearerTokenResolver bearerTokenResolver = getBearerTokenResolver();
this.requestMatcher.setBearerTokenResolver(bearerTokenResolver);

AuthenticationManager manager = http.getSharedObject(AuthenticationManager.class);
AuthenticationManagerResolver resolver = this.authenticationManagerResolver;
if (resolver == null) {
resolver = request -> http.getSharedObject(AuthenticationManager.class);
}

BearerTokenAuthenticationFilter filter =
new BearerTokenAuthenticationFilter(manager);
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver);
filter.setBearerTokenResolver(bearerTokenResolver);
filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
filter = postProcess(filter);
Expand All @@ -203,7 +214,9 @@ public void configure(H http) throws Exception {
"same time");
}

if (this.jwtConfigurer == null && this.opaqueTokenConfigurer == null) {
if (this.jwtConfigurer == null && this.opaqueTokenConfigurer == null &&
this.authenticationManagerResolver == 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 " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ public final class JwtAuthenticationProvider implements AuthenticationProvider {

public JwtAuthenticationProvider(JwtDecoder jwtDecoder) {
Assert.notNull(jwtDecoder, "jwtDecoder cannot be null");

this.jwtDecoder = jwtDecoder;
}

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 All @@ -24,6 +24,7 @@

import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContext;
Expand Down Expand Up @@ -51,7 +52,7 @@
* @see JwtAuthenticationProvider
*/
public final class BearerTokenAuthenticationFilter extends OncePerRequestFilter {
private final AuthenticationManager authenticationManager;
private final AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver;

private final AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource =
new WebAuthenticationDetailsSource();
Expand All @@ -60,13 +61,24 @@ public final class BearerTokenAuthenticationFilter extends OncePerRequestFilter

private AuthenticationEntryPoint authenticationEntryPoint = new BearerTokenAuthenticationEntryPoint();

/**
* Construct a {@code BearerTokenAuthenticationFilter} using the provided parameter(s)
* @param authenticationManagerResolver
*/
public BearerTokenAuthenticationFilter
(AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver) {

Assert.notNull(authenticationManagerResolver, "authenticationManagerResolver cannot be null");
this.authenticationManagerResolver = authenticationManagerResolver;
}

/**
* Construct a {@code BearerTokenAuthenticationFilter} using the provided parameter(s)
* @param authenticationManager
*/
public BearerTokenAuthenticationFilter(AuthenticationManager authenticationManager) {
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
this.authenticationManager = authenticationManager;
this.authenticationManagerResolver = request -> authenticationManager;
}

/**
Expand Down Expand Up @@ -104,7 +116,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));

try {
Authentication authenticationResult = this.authenticationManager.authenticate(authenticationRequest);
AuthenticationManager authenticationManager = this.authenticationManagerResolver.resolve(request);
Authentication authenticationResult = authenticationManager.authenticate(authenticationRequest);

SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authenticationResult);
Expand Down Expand Up @@ -139,5 +152,4 @@ public final void setAuthenticationEntryPoint(final AuthenticationEntryPoint aut
Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint cannot be null");
this.authenticationEntryPoint = authenticationEntryPoint;
}

}
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 @@ -17,12 +17,12 @@

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

Expand All @@ -31,6 +31,7 @@
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.BearerTokenError;
Expand All @@ -57,6 +58,9 @@ public class BearerTokenAuthenticationFilterTests {
@Mock
AuthenticationManager authenticationManager;

@Mock
AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver;

@Mock
BearerTokenResolver bearerTokenResolver;

Expand All @@ -66,27 +70,38 @@ public class BearerTokenAuthenticationFilterTests {

MockFilterChain filterChain;

@InjectMocks
BearerTokenAuthenticationFilter filter;

@Before
public void httpMocks() {
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
this.filterChain = new MockFilterChain();
}

@Before
public void setterMocks() {
this.filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
this.filter.setBearerTokenResolver(this.bearerTokenResolver);
@Test
public void doFilterWhenBearerTokenPresentThenAuthenticates() throws ServletException, IOException {
when(this.bearerTokenResolver.resolve(this.request)).thenReturn("token");

BearerTokenAuthenticationFilter filter =
addMocks(new BearerTokenAuthenticationFilter(this.authenticationManager));
filter.doFilter(this.request, this.response, this.filterChain);

ArgumentCaptor<BearerTokenAuthenticationToken> captor =
ArgumentCaptor.forClass(BearerTokenAuthenticationToken.class);

verify(this.authenticationManager).authenticate(captor.capture());

assertThat(captor.getValue().getPrincipal()).isEqualTo("token");
}

@Test
public void doFilterWhenBearerTokenPresentThenAuthenticates() throws ServletException, IOException {
public void doFilterWhenUsingAuthenticationManagerResolverThenAuthenticates() throws Exception {
BearerTokenAuthenticationFilter filter =
addMocks(new BearerTokenAuthenticationFilter(this.authenticationManagerResolver));

when(this.bearerTokenResolver.resolve(this.request)).thenReturn("token");
when(this.authenticationManagerResolver.resolve(any())).thenReturn(this.authenticationManager);

this.filter.doFilter(this.request, this.response, this.filterChain);
filter.doFilter(this.request, this.response, this.filterChain);

ArgumentCaptor<BearerTokenAuthenticationToken> captor =
ArgumentCaptor.forClass(BearerTokenAuthenticationToken.class);
Expand Down Expand Up @@ -137,36 +152,56 @@ public void doFilterWhenAuthenticationFailsThenPropagatesError() throws ServletE
when(this.authenticationManager.authenticate(any(BearerTokenAuthenticationToken.class)))
.thenThrow(exception);

this.filter.doFilter(this.request, this.response, this.filterChain);
BearerTokenAuthenticationFilter filter =
addMocks(new BearerTokenAuthenticationFilter(this.authenticationManager));
filter.doFilter(this.request, this.response, this.filterChain);

verify(this.authenticationEntryPoint).commence(this.request, this.response, exception);
}

@Test
public void setAuthenticationEntryPointWhenNullThenThrowsException() {
assertThatCode(() -> this.filter.setAuthenticationEntryPoint(null))
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(this.authenticationManager);
assertThatCode(() -> filter.setAuthenticationEntryPoint(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("authenticationEntryPoint cannot be null");
}

@Test
public void setBearerTokenResolverWhenNullThenThrowsException() {
assertThatCode(() -> this.filter.setBearerTokenResolver(null))
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(this.authenticationManager);
assertThatCode(() -> filter.setBearerTokenResolver(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("bearerTokenResolver cannot be null");
}

@Test
public void constructorWhenNullAuthenticationManagerThenThrowsException() {
assertThatCode(() -> new BearerTokenAuthenticationFilter(null))
assertThatCode(() -> new BearerTokenAuthenticationFilter((AuthenticationManager) null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("authenticationManager cannot be null");
}

@Test
public void constructorWhenNullAuthenticationManagerResolverThenThrowsException() {
assertThatCode(() ->
new BearerTokenAuthenticationFilter((AuthenticationManagerResolver<HttpServletRequest>) null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("authenticationManagerResolver cannot be null");
}

private BearerTokenAuthenticationFilter addMocks(BearerTokenAuthenticationFilter filter) {
filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
filter.setBearerTokenResolver(this.bearerTokenResolver);
return filter;
}

private void dontAuthenticate()
throws ServletException, IOException {

this.filter.doFilter(this.request, this.response, this.filterChain);
BearerTokenAuthenticationFilter filter =
addMocks(new BearerTokenAuthenticationFilter(this.authenticationManager));
filter.doFilter(this.request, this.response, this.filterChain);

verifyNoMoreInteractions(this.authenticationManager);
}
Expand Down
Loading

0 comments on commit aa1f0db

Please sign in to comment.