Reactive Jwt Authentication Converter Support
Fixes: gh-5092
This commit is contained in:
+18
-2
@@ -18,11 +18,14 @@ package org.springframework.security.oauth2.server.resource.authentication;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtException;
|
||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
|
||||
@@ -37,7 +40,8 @@ import org.springframework.util.Assert;
|
||||
* @since 5.1
|
||||
*/
|
||||
public final class JwtReactiveAuthenticationManager implements ReactiveAuthenticationManager {
|
||||
private final JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
|
||||
private Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter
|
||||
= new ReactiveJwtAuthenticationConverterAdapter(new JwtAuthenticationConverter());
|
||||
|
||||
private final ReactiveJwtDecoder jwtDecoder;
|
||||
|
||||
@@ -53,11 +57,23 @@ public final class JwtReactiveAuthenticationManager implements ReactiveAuthentic
|
||||
.cast(BearerTokenAuthenticationToken.class)
|
||||
.map(BearerTokenAuthenticationToken::getToken)
|
||||
.flatMap(this.jwtDecoder::decode)
|
||||
.map(this.jwtAuthenticationConverter::convert)
|
||||
.flatMap(this.jwtAuthenticationConverter::convert)
|
||||
.cast(Authentication.class)
|
||||
.onErrorMap(JwtException.class, this::onError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the given {@link Converter} for converting a {@link Jwt} into an {@link AbstractAuthenticationToken}.
|
||||
*
|
||||
* @param jwtAuthenticationConverter the {@link Converter} to use
|
||||
*/
|
||||
public void setJwtAuthenticationConverter(
|
||||
Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter) {
|
||||
|
||||
Assert.notNull(jwtAuthenticationConverter, "jwtAuthenticationConverter cannot be null");
|
||||
this.jwtAuthenticationConverter = jwtAuthenticationConverter;
|
||||
}
|
||||
|
||||
private OAuth2AuthenticationException onError(JwtException e) {
|
||||
OAuth2Error invalidRequest = invalidToken(e.getMessage());
|
||||
return new OAuth2AuthenticationException(invalidRequest, e.getMessage());
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2002-2018 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.server.resource.authentication;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A reactive {@link Converter} for adapting a non-blocking imperative {@link Converter}
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 5.1.1
|
||||
*/
|
||||
public class ReactiveJwtAuthenticationConverterAdapter implements Converter<Jwt, Mono<AbstractAuthenticationToken>> {
|
||||
private final JwtAuthenticationConverter delegate;
|
||||
|
||||
public ReactiveJwtAuthenticationConverterAdapter(JwtAuthenticationConverter delegate) {
|
||||
Assert.notNull(delegate, "delegate cannot be null");
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public final Mono<AbstractAuthenticationToken> convert(Jwt jwt) {
|
||||
return Mono.just(jwt).map(this.delegate::convert);
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2002-2018 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.server.resource.authentication;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ReactiveJwtAuthenticationConverterAdapter}
|
||||
*
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
public class ReactiveJwtAuthenticationConverterAdapterTests {
|
||||
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
|
||||
ReactiveJwtAuthenticationConverterAdapter jwtAuthenticationConverter =
|
||||
new ReactiveJwtAuthenticationConverterAdapter(converter);
|
||||
|
||||
@Test
|
||||
public void convertWhenTokenHasScopeAttributeThenTranslatedToAuthorities() {
|
||||
Jwt jwt = this.jwt(Collections.singletonMap("scope", "message:read message:write"));
|
||||
|
||||
AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt).block();
|
||||
Collection<GrantedAuthority> authorities = authentication.getAuthorities();
|
||||
|
||||
assertThat(authorities).containsExactly(
|
||||
new SimpleGrantedAuthority("SCOPE_message:read"),
|
||||
new SimpleGrantedAuthority("SCOPE_message:write"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenTokenHasEmptyScopeAttributeThenTranslatedToNoAuthorities() {
|
||||
Jwt jwt = this.jwt(Collections.singletonMap("scope", ""));
|
||||
|
||||
AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt).block();
|
||||
|
||||
Collection<GrantedAuthority> authorities = authentication.getAuthorities();
|
||||
|
||||
assertThat(authorities).containsExactly();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenTokenHasScpAttributeThenTranslatedToAuthorities() {
|
||||
Jwt jwt = this.jwt(Collections.singletonMap("scp", Arrays.asList("message:read", "message:write")));
|
||||
|
||||
AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt).block();
|
||||
|
||||
Collection<GrantedAuthority> authorities = authentication.getAuthorities();
|
||||
|
||||
assertThat(authorities).containsExactly(
|
||||
new SimpleGrantedAuthority("SCOPE_message:read"),
|
||||
new SimpleGrantedAuthority("SCOPE_message:write"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenTokenHasEmptyScpAttributeThenTranslatedToNoAuthorities() {
|
||||
Jwt jwt = this.jwt(Collections.singletonMap("scp", Arrays.asList()));
|
||||
|
||||
AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt).block();
|
||||
|
||||
Collection<GrantedAuthority> authorities = authentication.getAuthorities();
|
||||
|
||||
assertThat(authorities).containsExactly();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenTokenHasBothScopeAndScpThenScopeAttributeIsTranslatedToAuthorities() {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("scp", Arrays.asList("message:read", "message:write"));
|
||||
claims.put("scope", "missive:read missive:write");
|
||||
Jwt jwt = this.jwt(claims);
|
||||
|
||||
AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt).block();
|
||||
|
||||
Collection<GrantedAuthority> authorities = authentication.getAuthorities();
|
||||
|
||||
assertThat(authorities).containsExactly(
|
||||
new SimpleGrantedAuthority("SCOPE_missive:read"),
|
||||
new SimpleGrantedAuthority("SCOPE_missive:write"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenTokenHasEmptyScopeAndNonEmptyScpThenScopeAttributeIsTranslatedToNoAuthorities() {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("scp", Arrays.asList("message:read", "message:write"));
|
||||
claims.put("scope", "");
|
||||
Jwt jwt = this.jwt(claims);
|
||||
|
||||
AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt).block();
|
||||
|
||||
Collection<GrantedAuthority> authorities = authentication.getAuthorities();
|
||||
|
||||
assertThat(authorities).containsExactly();
|
||||
}
|
||||
|
||||
private Jwt jwt(Map<String, Object> claims) {
|
||||
Map<String, Object> headers = new HashMap<>();
|
||||
headers.put("alg", JwsAlgorithms.RS256);
|
||||
|
||||
return new Jwt("token", Instant.now(), Instant.now().plusSeconds(3600), headers, claims);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user