How to have JWT authentication between micro services in kubernetes cluster - java

We have 8 java microservices talking to each other in kubeneters cluster. Each microservice is bundled with auth library which intercepts and validates/renews JWT token for each REST request to controllers.
Scenario:
From Frontend, we get access token for the first time, Authentication gets successful. Lets say
Frontend hit 'Microservice A' with access token - Successful
'Microservice A' internally hits 'Microservice B' via restTemplate.
My 'Microservice B' also needs logged in user details.
Issue: I have to pass same access token from 'A' to 'B' but I am not able to get access token in Controller/Service logic but can get only in filters where token is being validated. I can get token in Rest Controllers by adding following argument in all rest methods in controller:
#RequestHeader (name="Authorization") String token
But I dont want to go with this approach as I have to pass this token to everywhere till end and have to declare this argument in all APIS.
I want to get token from TokenStore by passing authentication object. We are using Oauth2 and I checked the code in library, There are many tokenStore providers.
In DefaultTokenServices.java class, I am calling
Authentication auth = SecurityContextHolder.getContext().getAuthentication() // Passed this auth to tokenStore
String token = tokenStore.getAccessToken(auth).getValue(); // NullPointerException
My code is going through JWTTokenStore provider which is returning null. I checked, there is a provider called InMemoryTokenStore.class which actually extrActs token from store. But my flow is not going into in memory implementation.
Is there any way I can get token afterwards without grabbing it in controller via arguments? or how can I enable/use inMemoryTokenStore?
Also recommend something better for kubernetes intercommunication authentication?
TIA

It looks like you're using Spring (and Spring Security), so I believe the relevant part of the docs is the part on Bearer Token Propagation.
Its recommendation is to use a WebClient (the recommended replacement for RestTemplate as of Spring 5) that uses the provided ServletBearerExchangeFilterFunction to automagically propagate the JWT token from the incoming request into the outgoing request:
#Bean
public WebClient rest() {
return WebClient.builder()
.filter(new ServletBearerExchangeFilterFunction())
.build();
}
On RestTemplate, the docs say:
"There is no dedicated support for RestTemplate at the moment, but you can achieve propagation quite simply with your own interceptor"
and the following example is provided:
#Bean
RestTemplate rest() {
RestTemplate rest = new RestTemplate();
rest.getInterceptors().add((request, body, execution) -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return execution.execute(request, body);
}
if (!(authentication.getCredentials() instanceof AbstractOAuth2Token)) {
return execution.execute(request, body);
}
AbstractOAuth2Token token = (AbstractOAuth2Token) authentication.getCredentials();
request.getHeaders().setBearerAuth(token.getTokenValue());
return execution.execute(request, body);
});
return rest;
}
I don't believe you need to be looking at TokenStores if all you're trying to do is propagate the token. Remember everything relevant about a JWT should be inside the token itself. (Which is why the doc for the JwtTokenStore explains that it doesn't actually store anything, but just pulls info out of the token, and will return null for some methods, including the getAccessToken() method you're calling.)

Related

Spring Boot + Keycloak: optional auth endpoint

I'm trying to configure a Spring Boot application with Keycloak to have an endpoint that is both accessible for authenticated and unauthenticated users. For authenticated users, I want to return some extra information. Here is a simple example of what I'm trying to achieve:
#RestController
public class HelloController {
#GetMapping("/")
public String index(Principal principal) {
KeycloakPrincipal keycloakPrincipal = (KeycloakPrincipal) principal;
if (keycloakPrincipal != null) {
return "Hello " + keycloakPrincipal.getKeycloakSecurityContext().getToken().getPreferredUsername();
} else {
return "Hello";
}
}
}
application.properties:
keycloak.securityConstraints[0].authRoles[0] = *
keycloak.securityConstraints[0].securityCollections[0].name = Hello
keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /*
So far, I only got it to work for one of both cases. If I protect the endpoint using the security constraint above, the endpoint is only accessible to authenticated users. If I remove the security constraint, the endpoint is accessible for everyone, but then the principal will always be null.
Is it possible to achieve the intended behavior?
Have you tried something like Principal principal = SecurityContextHolder.getContext().getAuthentication();?
I believe the Principal as method parameter is only populated on secured endpoints but am unsure if it would exist in the SecurityContext. If not, you need to add a Filter to add it yourself.
I was able to solve the problem by calling the authenticate() method on the HttpServletRequest object. This will trigger the authentication process and will populate the user principal whenever possible. From the docs:
Triggers the same authentication process as would be triggered if the
request is for a resource that is protected by a security constraint.
To avoid triggering an authentication challenge, I pass in a dummy response object to the authenticate() call.

Webclient - refresh API key on unauthorized

I'm looking for solution how to properly implement token refreshing mechanism.
A token should be changed after we got 401, or 403 http status code from external API. I'd like to call login endpoint to obtain a valid token, save the token into database and try again with the new token.
Currently, I'm having problem with reactor and streams. Below are two methods that contains core functionality.
register method (external call)
public Mono<Void> register(UserConfiguration configuration) {
return externalClient.post()
.uri("http://example.com")
.body(Mono.just(new SomeRequest()), SomeRequest.class)
.header(AUTHORIZATION_TOKEN_HEADER, token)
.exchangeToMono(clientResponse -> handleResponse(clientResponse, Void.class, configuration))
.retryWhen(Retry.max(5)
.doBeforeRetry(retrySignal -> log.info("Unauthorized request"))
.filter(UnauthorizedException.class::isInstance))
.then();
}
handleResponse method
private <R> Mono<R> handleResponse(ClientResponse clientResponse, Class<R> clazz, Configuration configuration, ) {
if (clientResponse.statusCode().equals(HttpStatus.OK)) {
return clientResponse.bodyToMono(clazz);
} else if (clientResponse.statusCode().equals(HttpStatus.FORBIDDEN) || clientResponse.statusCode().equals(HttpStatus.UNAUTHORIZED)) {
return authenticationService.refreshToken(configuration).flatMap(conf -> Mono.error(new UnauthorizedException()));
} else {
return clientResponse.createException()
.flatMap(Mono::error);
}
}
register is responsible for calling external API. handleResponse take care of handling response status, and when there is unauthorized response, the method invokes authenticationService in order to obtain a new token and save the token into db (UserConfiguration). Retry detects an exception instance and when there is problem with a token, it enforces the stream going again. Unfortunately, register method uses the same instance of UserConfiguration so values there are outdated as well as token is not valid anymore.
What would be the better approach? Now I can see the only workaround to start a stream from getting UserConfiguration, so on retry we will have the latest state of db. It seems to work, but it's not perfect from a performance view.
Might be late, but in case it helps someone else: you could use the onErrorResume method to filter for 401 and 403 HTTP responses and in those cases, renew the token, save it in the database and call the register method with the new user configuration.

What is the correct way of retrieving an OAuth2 Bearer token in Spring

I am making service to service requests using Spring's WebClient that require an OAuth2 bearer token to be added as a header to the request. I Can do this relatively easily by creating an ExchangeFilterFunction that intercepts the request, retrieves an access token, adds it to the header, and continues on. Since this is not a user request, the SecurityContextHolder does not contain an Authentication that would hold an access token for me, so instead of retrieving from that, I would like to get an access token based on my Spring security configuration (currently defined in the spring.security.oauth2.client.registration and provider properties).
The way I'm doing this now is by Autowiring an OAuth2ClientContext and then getting the AccessToken from it. Reducing the code only to what I care about for this question, I have:
#Component
public class OAuth2WebClientFilter implements ExchangeFilterFunction {
#Autowired
private OAuth2ClientContext oAuth2ClientContext;
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
// simple retrieval of the token
String oAuth2Token = oAuth2ClientContext.getAccessToken().getValue();
// adding the token to the header of the request
request = ClientRequest.from(request).header(HttpHeaders.AUTHORIZATION, "Bearer " + oAuth2Token).build();
return next.exchange(request);
}
}
This does exactly what I want it to. However, I have recently upgraded spring-security-oauth2 to 2.5.0.RELEASE, and it is saying the OAuth2ClientContext is deprecated, but I haven't found a simple replacement for this process. So is there still a way to get an access token in a relatively simple fashion like above, and if so, how?
Also note: this concept is used elsewhere in the project and not just for the WebClient, so I'm looking to see how to properly replace an injected OAuth2ClientContext. Thanks!
Spring Security provides an exchange filter function called ServletOAuth2AuthorizedClientExchangeFilterFunction.
The ServletOAuth2AuthorizedClientExchangeFilterFunction provides a
simple mechanism for requesting protected resources by using an
OAuth2AuthorizedClient and including the associated OAuth2AccessToken
as a Bearer Token. It directly uses an OAuth2AuthorizedClientManager
and therefore inherits the following capabilities:
An OAuth2AccessToken will be requested if the client has not yet been
authorized.
authorization_code - triggers the Authorization Request redirect to
initiate the flow
client_credentials - the access token is obtained directly from the
Token Endpoint
password - the access token is obtained directly from the Token
Endpoint
If the OAuth2AccessToken is expired, it will be refreshed (or renewed)
if an OAuth2AuthorizedClientProvider is available to perform the
authorization
See https://docs.spring.io/spring-security/reference/servlet/oauth2/client/authorized-clients.html#oauth2Client-webclient-servlet for details.

Spring Security filters for JWT-based authentication, verification and authorization scheme, by example

Java + Spring (and Spring Security) here, interested in implementing a JWT-based auth mechanism for my web service using bearer tokens. My understanding of the proper way of using Spring Security for authentication and authorization is through the use of provided (or custom) filters as follows:
you specify which URLs in your app are authenticated (and thus require authenticated requests to access)
this is typically done in an #EnableWebSecurity-annotated web security class that extends WebSecurityConfigurerAdapter
for any unauthenticated URLs, no filters should block access to the resources being requested
an authentication filter effectively provides a "sign in" endpoint
request clients should hit this signin endpoint (authn filter) initially to obtain an auth token that can be used for making subsequent API calls
this filter should receive a type of "sign in request" object that contains a principal (e.g. username) and credential (e.g. password)
this authn filter should use the principal/credential contained in the sign in request to determine if they represents a valid user in the system
if so, an auth token (JWT, etc.) is generated and sent back to the requesters in the response somehow
else, if the principal/credential don't match a valid user in the system, an error response is returned and authentication fails
for authenticated URLs, a verification filter verifies that the request contains an auth token and that the auth token is valid (was signed correctly, contains user information such as JWT claims, is not expired, etc.)
if the auth token is valid, the request continues on to the authorization filter (see below)
else if the auth token is not valid, verification fails and the filter sends an error response back to the client
finally, an authorization filter verifies that the user associated with the valid auth token has the ability/permission to make such a request
if they do, then the request is allowed to continue on to whatever resources/controller was written to handle it, and that resource/controller provides the response back to the requester
if they don't, an error response is returned to the client
ideally the logic (code) inside this authz filter would have access to the permission annotations added to the resource method, so that I can add endpoints and specify permissions on them without having to modify the code of the authz filter
So to begin with, if anything I have stated above is a Spring Security (or web security in general) anti-pattern or is misled, please begin by providing course correction and steering me in the right direction!
Assuming I'm more or less understanding the "auth flow" above correctly...
Are there any specific Spring Security filters that take care of all of this for me already, or that can be extended and have a few methods overridden to behave this way? Or anything that comes really close? Looking at the list of authentication-specific Spring Security filters I see:
UsernamePasswordAuthenticationFilter -> looks like a decent candidate for the authn filter but expects a username and password parameter on the query string which is strange to me, and most importantly, does not generate a JWT
CasAuthenticationFilter -> looks like its used for CAS-based SSO and is not appropriate for use in non-SSO contexts
BasicAuthenticationFilter -> for HTTP basic authentication-based auth, not appropriate for more sophisticated setups
As for token verification and authorization, I (much to my surprise) don't see anything in the Spring Security landscape that could qualify.
Unless anyone knows of JWT-specific filters that I can use or subclass easily, I think I need to implement my own custom filters, in which case I'm wondering how to conigure Spring Security to use them and not use any of these other authentication filters (such as UsernamePasswordAuthenticationFilter) as part of the filter chain.
As I understand it, you want to:
Authenticate users via a username and password and respond with a JWT
On subsequent requests, authenticate users using that JWT
username/password -> JWT isn't an established authentication mechanism on its own, which is why Spring Security doesn't yet have direct support.
You can get it on your own pretty easily, though.
First, create a /token endpoint that produces a JWT:
#RestController
public class TokenController {
#Value("${jwt.private.key}")
RSAPrivateKey key;
#PostMapping("/token")
public String token(Authentication authentication) {
Instant now = Instant.now();
long expiry = 36000L;
// #formatter:off
String scope = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(" "));
JWTClaimsSet claims = new JWTClaimsSet.Builder()
.issuer("self")
.issueTime(new Date(now.toEpochMilli()))
.expirationTime(new Date(now.plusSeconds(expiry).toEpochMilli()))
.subject(authentication.getName())
.claim("scope", scope)
.build();
// #formatter:on
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).build();
SignedJWT jwt = new SignedJWT(header, claims);
return sign(jwt).serialize();
}
SignedJWT sign(SignedJWT jwt) {
try {
jwt.sign(new RSASSASigner(this.key));
return jwt;
}
catch (Exception ex) {
throw new IllegalArgumentException(ex);
}
}
}
Second, configure Spring Security to allow HTTP Basic (for the /token endpoint) and JWT (for the rest):
#Configuration
public class RestConfig extends WebSecurityConfigurerAdapter {
#Value("${jwt.public.key}")
RSAPublicKey key;
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.authorizeRequests((authz) -> authz.anyRequest().authenticated())
.csrf((csrf) -> csrf.ignoringAntMatchers("/token"))
.httpBasic(Customizer.withDefaults())
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
.accessDeniedHandler(new BearerTokenAccessDeniedHandler())
);
// #formatter:on
}
#Bean
UserDetailsService users() {
// #formatter:off
return new InMemoryUserDetailsManager(
User.withUsername("user")
.password("{noop}password")
.authorities("app")
.build());
// #formatter:on
}
#Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(this.key).build();
}
}
I think there's appetite to add support for something like this in spring-authorization-server to reduce the /token boilerplate, if you're interested in contributing your efforts!

Getting exception when requesting XSRF-TOKEN using Spring RestTemplate

I'm trying to call a service which has CSRF enabled and all it's endpoints are configured to request authentication header from the user.
I'm using Spring RestTemplate as follows:
ResponseEntity<String> responseEntity = getRestTemplate().exchange(
"localhost:9090/",
"HEAD",
entity,
String.class);
return responseEntity.getBody();
However, I'm not able to read the Headers from the response as I'm getting HTTP 401 error.
My workaround is to read the token from the exception that RestTemplate throws HttpClientErrorException. Like this:
exception.getResponseHeaders().get("Set-Cookie");
for (String header : headers) {
if (header.startsWith("XSRF-TOKEN")) {
token = header.split("=")[1];
break;
}
}
Is there any way to get XSRF-TOKEN token with out relying on reading it from the exception?
You are not getting an exception when accessing with GET method. Hence, I would create a get endpoint for retrieving the token and then use it for next POST calls.
Hope that approach makes sense.
the csrf only blocks requests of type post, put, delete ... that is, the get is free, therefore in order to obtain the token, first you have to make a request to a get method and extract the token from there that you would use to the next requests.
in case the token is not generated, add this to the configure of your security configuration:
http.csrf (). csrfTokenRepository (CookieCrsfTokenRepository.withHttpOnlyFalse) .any () ........
XSRF-TOKEN following spring specification is marker for header by default. So you should try get it in this way:
List tokenList = responseEntity.getHeaders().get("XSRF-TOKEN");
This collection consist of single element as usual, so first element should be your token.

Categories

Resources