Spring Boot Webflux Security - reading Principal in service class when writing tests - spring-boot

I am quite new to the Spring ecosystem in general and Webflux. There are 2 things that I am trying to figure out and cannot find any specifics about.
My Setup:
I am writing a Spring Boot 2 REST API using WebFlux (not using controllers but rather handler functions). The authentication server is a separate service which issues JWT tokens and those get attached to each request as Authentication headers. Here is a simple example of a request method:
public Mono<ServerResponse> all(ServerRequest serverRequest) {
return principal(serverRequest).flatMap(principal ->
ReactiveResponses.listResponse(this.projectService.all(principal)));
}
Which i use to react to a GET request for a list of all "Projects" that a user has access to.
I afterwards have a service which retrieves the list of projects for this user and i render a json response.
The Problems:
Now in order to filter the projects based on the current user id i need to read it from the request principal. One issue here is that i have plenty service methods which need the current user information and passing it through to the service seems like an overkill. One solution is to read the principal inside the service from:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Question 1:
Is this a good practice in general when writing functional code (If i do this instead of propagating the principal)? is this a good approach despite the complexity of reading and sending the principal from the request to the service in each method?
Question 2:
Should i instead use the SecurityContextHolder Thread Local to fetch the principal, and if i do that how do i write tests for my service?
If i use the Security Context how do i test my service implementations which are expecting a principal that is of type JWTAuthenticationToken
and i always get null when trying to do something like described here: Unit testing with Spring Security
In the service tests, In tests what i've managed to do so far is to propagate the principal to the service methods and use mockito to mock the principal. This is quite straightforward.
In the Endpoint Tests i am using #WithMockUser to populate the principal when doing requests and i mock out the service layer. This has the downside of the principal type being different.
Here is how my test class for the service layer looks:
#DataMongoTest
#Import({ProjectServiceImpl.class})
class ProjectServiceImplTest extends BaseServiceTest {
#Autowired
ProjectServiceImpl projectService;
#Autowired
ProjectRepository projectRepository;
#Mock
Principal principal;
#Mock
Principal principal2;
#BeforeEach
void setUp() {
initMocks(this);
when(principal.getName()).thenReturn("uuid");
when(principal2.getName()).thenReturn("uuid2");
}
// Cleaned for brevity
#Test
public void all_returnsOnlyOwnedProjects() {
Flux<Project> saved = projectRepository.saveAll(
Flux.just(
new Project(null, "First", "uuid"),
new Project(null, "Second", "uuid2"),
new Project(null, "Third", "uuid3")
)
);
Flux<Project> all = projectService.all(principal2);
Flux<Project> composite = saved.thenMany(all);
StepVerifier
.create(composite)
.consumeNextWith(project -> {
assertThat(project.getOwnerUserId()).isEqualTo("uuid2");
})
.verifyComplete();
}
}

Based on the other answer, i managed to solve this problem in the following way.
I added the following methods to read the id from claims where it normally resides within the JWT token.
public static Mono<String> currentUserId() {
return jwt().map(jwt -> jwt.getClaimAsString(USER_ID_CLAIM_NAME));
}
public static Mono<Jwt> jwt() {
return ReactiveSecurityContextHolder.getContext()
.map(context -> context.getAuthentication().getPrincipal())
.cast(Jwt.class);
}
Then i use this within my services wherever needed, and i am not forwarding it through the handler to the service.
The tricky part was always testing. I am able to resolve this using the custom SecurityContextFactory. I created an annotation which i can attach the same way as #WithMockUser, but with some of the claim details i need instead.
#Retention(RetentionPolicy.RUNTIME)
#WithSecurityContext(factory = WithMockTokenSecurityContextFactory.class)
public #interface WithMockToken {
String sub() default "uuid";
String email() default "test#test.com";
String name() default "Test User";
}
Then the Factory:
String token = "....ANY_JWT_TOKEN_GOES_HERE";
#Override
public SecurityContext createSecurityContext(WithMockToken tokenAnnotation) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
HashMap<String, Object> headers = new HashMap<>();
headers.put("kid", "SOME_ID");
headers.put("typ", "JWT");
headers.put("alg", "RS256");
HashMap<String, Object> claims = new HashMap<>();
claims.put("sub", tokenAnnotation.sub());
claims.put("aud", new ArrayList<>() {{
add("SOME_ID_HERE");
}});
claims.put("updated_at", "2019-06-24T12:16:17.384Z");
claims.put("nickname", tokenAnnotation.email().substring(0, tokenAnnotation.email().indexOf("#")));
claims.put("name", tokenAnnotation.name());
claims.put("exp", new Date());
claims.put("iat", new Date());
claims.put("email", tokenAnnotation.email());
Jwt jwt = new Jwt(token, Instant.now(), Instant.now().plus(1, ChronoUnit.HOURS), headers,
claims);
JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(jwt, AuthorityUtils.NO_AUTHORITIES); // Authorities are needed to pass authentication in the Integration tests
context.setAuthentication(jwtAuthenticationToken);
return context;
}
Then a simple test will look like this:
#Test
#WithMockToken(sub = "uuid2")
public void delete_whenNotOwner() {
Mono<Void> deleted = this.projectService.create(projectDTO)
.flatMap(saved -> this.projectService.delete(saved.getId()));
StepVerifier
.create(deleted)
.verifyError(ProjectDeleteNotAllowedException.class);
}

As you are using Webflux you should be using the ReactiveSecurityContextHolder to retrieve the principal like so : Object principal = ReactiveSecurityContextHolder.getContext().getAuthentication().getPrincipal();
The use of the non-reactive one will return null as you are seeing.
There is more info related to the topic in this answer - https://stackoverflow.com/a/51350355/197342

Related

Duplicate config code problem Spring Boot microservices

Is there a way to create a spring configuration class and use it to all my microservices?
For example, I have to copy the following configuration class and paste it through all my microservices which means that when I want to make a small change I have to go through all the microservices editing the same class.
I have investigated and the most I could find is to create a module with all the classes in common and import it in my microservices by the pom, what happens with this is that when I want to get the SecurityContextHolder.getContext() this is null for a context issue and I do not know very well how to give solution to this or what other alternatives there are.
#Configuration
public class FeignGlobalConfiguration {
#Bean
public ErrorDecoder errorDecoder() {
return new RetrieveMessageErrorDecoder();
}
#Bean
public RequestInterceptor requestInterceptor(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return requestTemplate -> {
requestTemplate.header(JwtClaims.USERNAME, authentication.getPrincipal().toString());
requestTemplate.header(JwtClaims.CLIENT, authentication.getDetails().toString());
requestTemplate.header(JwtClaims.TOKEN, authentication.getCredentials().toString());
requestTemplate.header(JwtClaims.ROLES, authentication.getAuthorities().toString());
};
}
}
The problem is your bean definition.
The line Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); is called when the bean is constructed so only once. After that the reference is used (which is probably null at the time of construction.
To solve move tha tline inside the lambda so it gets evaluated each time a request is processed.
#Bean
public RequestInterceptor requestInterceptor(){
return requestTemplate -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
requestTemplate.header(JwtClaims.USERNAME, authentication.getPrincipal().toString());
requestTemplate.header(JwtClaims.CLIENT, authentication.getDetails().toString());
requestTemplate.header(JwtClaims.TOKEN, authentication.getCredentials().toString());
requestTemplate.header(JwtClaims.ROLES, authentication.getAuthorities().toString());
};
}

Extract Mono nonblocking response and store it in a variable and use it globally

In my project I have a requirement where I need to call a third party api authentic url to get the the access token. I need to set that access token in every subsequent request header .The access token has some lifetime and when the lifetime expired I need to regenerate the access token.
application.yml
I have hardcoded the client_id,client_secret,auth_url and grant_type .
AuthController.java
here I have created an endpoint to generate the access token.
**`AuthService.java`**
#Services
#Slf4j
public class AuthService{
#Autowired
private WebClient webClient;
static String accessToken="";
public Mono<SeekResponse> getAccessToken(AuthRequest authRequest) throws InvalidTokenException{
Mono<AuthResponse> authResponse=webClient.post()
.bodyValue(authRequest)
.accept(MediaType.APPLICATION_JSON)
.retrive()
.bodyToMono(AuthResponse.class);
authResponse.doOnNext(response->{
String value=response.getAccess_token();
accessToken=accessToken+value;
})
}
}
Although I have updated the "accessToken" value but it will return me null. I understand as I have made async call this value coming as null. I can't use blocking mechanism here.
Is there any other way to generate the access token and pass it as a header for the subsequent request for authentication. Or how can I use the accessToken value globally so that I can set those token value to my subsequent api request call.
I have tried with oAuth2 by following the below article:
https://medium.com/#asce4s/oauth2-with-spring-webclient-761d16f89cdd
But when I execute I am getting the below error :
"An expected CSRF token cannot found".
I'm also learning Webflux. Here's my thought. Please correct me if I'm wrong.
We are not going to rely on doOnNext() nor doOnSuccess() nor other similar method to try to work on an pre-defined variable accessToken (That's not a way to let Mono flow). What we should focus on is converting a mono to another mono, for example converting mono response to mono access token.
The way to do that is .flatmap()/.map()/.zipwith()/...
For example,
Mono<string> tokenMono = responseMono.flatmap(
// in the map or flatmap, we get the chance to operate on variables/objects.
resp -> {
string token = response.getAccess_token();
return Mono.just(token); // with Mono.just(), we are able to convert object to Mono again.
}
) // this example is not practical, as map() is better to do the same thing. flatmap with Mono.just() is meaningless here.
Mono<string> tokenMono2 = responseMono.map(
resp -> {
string token = response.getAccess_token();
return token;
}
)
Everything starting from Mono should be always Mono until subscribed or blocked. And they provide us ways to operate on those variables inside Mono<variables>. Those are map() flatmap(), zipwith(), etc.
https://stackoverflow.com/a/60105107/18412317
Referring to a point this author said, doOnNext() is for side effect such as logging.
It's hard to understand provided sample and implementation is not really reactive. The method returns Mono but at the same time throws InvalidTokenException or usage of onNext that is a so-called side-effect operation that should be used for logging, metrics, or other similar use cases.
The way you implement oauth flow for WebClient is to create filter, Client Filters.
Spring Security provides some boilerplates for common oauth flows. Check Spring Security OAuth2 for more details.
Here is an example of simple implementation of the client credential provider
private ServerOAuth2AuthorizedClientExchangeFilterFunction oauth(String clientRegistrationId, ClientConfig config) {
var clientRegistration = ClientRegistration
.withRegistrationId(clientRegistrationId)
.tokenUri(config.getAuthUrl() + "/token")
.clientId(config.getClientId())
.clientSecret(config.getClientSecret())
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.build();
var authRepository = new InMemoryReactiveClientRegistrationRepository(clientRegistration);
var authClientService = new InMemoryReactiveOAuth2AuthorizedClientService(authRepository);
var authClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
authRepository, authClientService);
var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authClientManager);
oauth.setDefaultClientRegistrationId(clientRegistrationId);
return oauth;
}
then you could use it in the WebClient
WebClient.builder()
.filter(oauth)
.build()
UPDATE
Here is an example of the alternative method without filters
AuthService
#Service
public class AuthService {
private final WebClient webClient;
public AuthService() {
this.webClient = WebClient.create("<url>/token");
}
public Mono<String> getAccessToken() {
return webClient.post()
.bodyValue()
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(AuthResponse.class)
.map(res -> res.getAccessToken());
}
}
ApiService
#Service
public class ApiService {
private final WebClient webClient;
private final Mono<String> requestToken;
public ApiService(AuthService authService) {
this.webClient = WebClient.create("<url>/api");
// cache for token expiration
this.requestToken = authService.getAccessToken().cache(Duration.ofMinutes(10));
}
public Mono<String> request() {
return requestToken
.flatMap(token ->
webClient.get()
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(String.class)
);
}
}

Spring Authorization server customizing the access token response to add additional params

I am implementing the spring authorization server and I want to add few custom properties to the token response json. Below is how I want the response to be.
{
"access_token": *jwt*,
"scope": "articles.read openid",
"token_type": "Bearer",
"expires_in": 299,
***"customvalue1":99***
}
I have seen multiple posts in stack overflow where similar topic is discussed, but in those scenarios the additional data is added either to the claim or header of jwt. My requirement is to add it outside of the jwt.
I tried to implement OAuth2TokenCustomizer, but this allows only the claims or headers of the jwt to be modified. Can anyone pls help?
To anyone coming here looking for answer:
I ended up overriding OAuth2TokenEndpointFilter. It has a authentication successhandler which can be injected to perform any additional token response manipulation.
#Bean
public Customizer<OAuth2TokenEndpointConfigurer> customizeTokenEndpoint() {
return tokenEndpoint -> tokenEndpoint
.accessTokenResponseHandler(success());
}
#Bean(name = "token")
public AuthenticationSuccessHandler success() {
return new TokenResponseSuccessHandler();
}
Then inside success handler,
#Override
public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication) throws IOException {
final OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication;
******
**
Map<String, Object> additionalParameters = accessTokenAuthentication.getAdditionalParameters();
if(additionalParameters.size()==0)
additionalParameters=new HashMap<>();
additionalParameters.put("hi","hi");
Finally use, OAuth2AccessTokenResponse.Builder to build a new response.
In case you are using the new authorization server then creating this bean will help you achieve your goal. The good thing, once the bean is detected it will automatically be applied.
#Bean
public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer() {
return context -> {
Authentication principal = context.getPrincipal();
//context.getTokenType().getValue().equals("access_token")
if (Objects.equals(context.getTokenType().getValue(), "access_token") && principal instanceof UsernamePasswordAuthenticationToken) {
Set<String> authorities = principal.getAuthorities().stream()
.map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
User user = (User) principal.getPrincipal();
context.getClaims().claim("authorities", authorities)
.claim("user", user);
}
};
}
Duplicate of How to create custom claims in JWT using spring-authorization-server
This class and the method maybe help you.You can find the class init place

Spring Security 5 rest client with OAuth2

I would like to implement a client which should simply send some rest calls with a OAuth2 token. Using spring-security-oauth it was pretty easy to use the OAuth2RestTemplate with a client-credentials flow. Today I saw most of those classes are deprecated in 2.4.0 and the recommendation is to use Spring Security 5. I've googled around and looked into the Migration Guide [1] but I've not understood what I've to do to perform some simple rest call which fetches a token with Spring Security 5. I think I'm even not sure what kind of libraries are needed. So what I'm basically is looking for is a way to provide a client-id, client-secret and a tokenendpoint programatically (not via properties) to some kind of rest template and send a request to a specific url.
--edit--
I found a way of using the web client without using the properties but rather using the ClientRegestration object. I'm not sure if that is a recommended way:
#Test
public void test() {
WebClient webClient = getWebClient();
ResponseSpec retrieve = webClient.get().uri("https://somepath")
.attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId(REG_ID)).retrieve();
Flux<String> result = retrieve.bodyToFlux(String.class); // flux makes no sense here, use Mono instead
Mono<List<String>> response = result.collectList();
List<String> block = response.block();
System.out.print(block);
System.out.print("debug");
}
public WebClient getWebClient() {
Builder clientRegestrationBuilder = ClientRegistration.withRegistrationId(REG_ID);
clientRegestrationBuilder.clientId(CLIENT_ID);
clientRegestrationBuilder.clientSecret(CLIENT_SECRET);
clientRegestrationBuilder.tokenUri(TOKEN_ENDPOINT);
clientRegestrationBuilder.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS);
ClientRegistration clientRegistration = clientRegestrationBuilder.build();
ReactiveClientRegistrationRepository repo = new InMemoryReactiveClientRegistrationRepository(clientRegistration);
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(repo,
new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
return WebClient.builder().filter(oauth).build();
}
Regards
monti
[1] https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide
The following code is a unit test which shows how a ClientRegistration can be done programatically. In "real" spring scenario I guess the ClientRegistration should be provided as bean and finally injected as a list to a ReactiveClientRegistrationRepository...
public void test() {
WebClient webClient = getWebClient();
ResponseSpec retrieve = webClient.get().uri("https://somepath")
.attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId(REG_ID)).retrieve();
Flux<String> result = retrieve.bodyToFlux(String.class); // flux makes no sense here, use Mono instead
Mono<List<String>> response = result.collectList();
List<String> block = response.block();
System.out.print(block);
System.out.print("debug");
}
public WebClient getWebClient() {
Builder clientRegestrationBuilder = ClientRegistration.withRegistrationId(REG_ID);
clientRegestrationBuilder.clientId(CLIENT_ID);
clientRegestrationBuilder.clientSecret(CLIENT_SECRET);
clientRegestrationBuilder.tokenUri(TOKEN_ENDPOINT);
clientRegestrationBuilder.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS);
ClientRegistration clientRegistration = clientRegestrationBuilder.build();
ReactiveClientRegistrationRepository repo = new InMemoryReactiveClientRegistrationRepository(clientRegistration);
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(repo,
new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
return WebClient.builder().filter(oauth).build();
}

Limiting Access to Endpoints by Method in Spring Boot

We are implementing a simple health check API our load balancers can call to help with the routing of requests. If the status of the application is "standby", requests should not be sent to it. Only the admins can set the state to "up" or "standby", but anyone (including the load balancers) can get the status of the application.
We are trying this with Spring Boot 2, but are having problems configuring security to grant anonymous access to just one of the routes. Consider the following controller:
#RestController
public class AppStatusController {
private static final String STATUS = "status";
String state = "standby";
private String getState() {
return state;
}
private Map<String, String> getStatusMap() {
Map<String, String> retval = new HashMap<>();
retval.put(STATUS, getState());
return retval;
}
// GET calls are public, all others require AuthN & AuthZ
#GetMapping(path = "/appstatus", produces = "application/json")
public Map<String, String> getStatus() {
return getStatusMap();
}
// Only those with the ADMIN role can POST to this endpoint
#PostMapping(path = "/appstatus", consumes = "application/json", produces = "application/json")
public Map<String, String> setStatus(#RequestBody Map state) {
// Validate and update the state
return getStatusMap();
}
}
There is only one endpoint, /appstatus, but one method is called with an HTTP GET and the other with an HTTP POST. We want calls to getStatus to be public, but allow Spring Security to control access to setStatus. One might expect an annotation such as #Anonymous or something similar to be applied to the getStatus() method but we can't seem to find one.
Some have suggested using a separate #Configuration class and setting up antMatchers but it's not clear how we can match on the HTTP method.
Does anyone have suggestions on how to configure Spring Security to allow public access to GET method requests but control access to other methods?
EDIT: We are trying to avoid any authentication on the getStatus() call. We can't store auth credentials in the health check probe and can't perform a login exchange. This is a simple GET request to see if the application is up and ready for operation.
Have you tried using Method Security Expressions?
It looks like this will do what you want:
// GET calls are public, all others require AuthN & AuthZ
#GetMapping(path = "/appstatus", produces = "application/json")
#PreAuthorize("permitAll")
public Map<String, String> getStatus() {
return getStatusMap();
}
// Only those with the ADMIN role can POST to this endpoint
#PostMapping(path = "/appstatus", consumes = "application/json", produces = "application/json")
#PreAuthorize("hasRole('ADMIN')")
public Map<String, String> setStatus(#RequestBody Map state) {
// Validate and update the state
return getStatusMap();
}
Note: I don't know what roles your admins have, so I used 'ADMIN' as a placeholder.

Resources