Spring WS UsernameToken authentication + session management via spring-boot - spring

I have published spring web services using spring boot. in WsConfigurerAdapter, I used XwsSecurityInterceptor with SimplePasswordValidationCallback to authenticate users and this went fine.
Web config,
#Bean
XwsSecurityInterceptor securityInterceptor() {
XwsSecurityInterceptor securityInterceptor = new XwsSecurityInterceptor();
securityInterceptor.setCallbackHandler(callbackHandler());
securityInterceptor.setPolicyConfiguration(new ClassPathResource("securityPolicy.xml"));
return securityInterceptor;
}
#Bean
CallBackHandlerHelper callbackHandler() {
CallBackHandlerHelper callbackHandler = new CallBackHandlerHelper();
callbackHandler.loadUsers(); // loading users from DB
return callbackHandler;
}
sample soap header.
<soapenv:Header>
<wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken wsu:Id="XWSSGID-14072105829651149256297" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>admin</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">1</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
Now I'm struggling with these 02 concerns,
1) How to use a password digest and nonce, than a plainText. (In my policy xml, i have set them to false and loading wss:usernameToken in plaintext format via SOAP header. But i prefer to get the password digest than a plainText format).
2) I need to setup this services as stateful by keeping a session key. Don't want to send each and every time user/pwd. Idea is to use Username token only to login request and hereafter manage the session with an unique key til user sends logoff request.(hoping to keep this generated session key in-memory until the session went off) How could I suppose to solve this in a given context?

For digests, you'd use the following in you security policy file:
<xwss:UsernameToken digestPassword="true" useNonce="true"/>
The reference docs for the XWSS configuration format can be found here.
I really think you should reconsider, as having stateful services is generally frowned upon. See this similar SO question for instance, or this one, or this one, and so on.

Related

How to handle OAuth2 access token refresh with synchronous API calls, in Spring Security 5

We are using Spring Gateway (Spring Boot 2.4.6) which uses Spring Security 5 and the Weblux/ reactive model within that to provide OAuth2 security and Keycloak as the IDP.
Refreshing of the Access Token is an issue when our front-end application, which has already [successfully] authenticated against the gateway/ IDP, issues multiple API calls after the session's access token has expired.
It appears that out of (for example) five API calls, only the last one gets re-authenticated against the Keycloak provider and the other four get 'lost', thereby causing issues within the front-end.
If the user refreshes the UI's page then the proper authentication flow happens seamlessly and the token stored in the session is refreshed, without a redirect to the Keycloak login screen (as expected), therefore the problem is only with synchronous API calls.
The SecurityWebFilterChain is setup with:
/*
* Enable oauth2 authentication on all requests, but use our custom
* RegistrationRepository
*/
.and()
.oauth2Login()
.authenticationSuccessHandler(new AuthSuccessHandler(requestCache)) // handle success login
.authenticationFailureHandler((exchange, excep) -> {
LOGGER.debug("Authentication failure: {}", excep.getMessage());
return Mono.empty();
})
.clientRegistrationRepository(clientReg);
// Add our custom filter to the security chain
final KeycloakClientLoginFilter keyclockLogin = new KeycloakClientLoginFilter(
clientReg,
redirectStrategy,
requestCache,
authClientService);
clientReg.setKeycloakClientLoginFilter(keyclockLogin);
http.addFilterBefore(keyclockLogin, SecurityWebFiltersOrder.LOGIN_PAGE_GENERATING);
return http.build();
With the ServerAuthenticationSuccessHandler configured with this:
private class AuthSuccessHandler implements ServerAuthenticationSuccessHandler {
private final ServerRequestCache requestCache;
private final URI defaultLocation = URI.create("/login");
private AuthSuccessHandler(ServerRequestCache requestCache) {
this.requestCache = requestCache;
}
#Override
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
final ServerWebExchange exchange = webFilterExchange.getExchange();
return requestCache.getRedirectUri(exchange)
.defaultIfEmpty(defaultLocation)
.flatMap(location -> {
LOGGER.debug("Authentication success. Redirecting request to {}", location.toASCIIString());
return redirectStrategy.sendRedirect(exchange, location);
});
}
}
Within the KeycloakClientLoginFilter there is a ServerWebExchangeMatcher that checks if the required details are present on the inbound exchange, and whether the AccessToken has (or is about to) expire. If it is, it runs through this code to redirect the request off to Keycloak for authentication and/ or refresh:
final ClientRegistration keycloakReg = clientReg.getRegistration(tenantId, appId);
if (!isError && loginRedirects.containsKey(keycloakReg.getRegistrationId())) {
final String contextPath = exchange.getRequest().getPath().contextPath().value();
final URI redirect = URI.create(contextPath + loginRedirects.get(keycloakReg.getRegistrationId()));
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("About to redirect to keycloak; for method {}, tenant={}",
exchange.getRequest().getMethod(),
tenantId);
}
// Save the request so the URL can be retreived on successful login
return requestCache.saveRequest(exchange)
.then(redirectStrategy.sendRedirect(exchange, redirect));
}
So, all API calls hit the above code, require a refresh, have their original exchanges saved in the requestCache and are then directed to Keycloak. When Keycloak responds with the updated token, the exchange(s) run through the AuthSuccessHandler, which pulls the original request URL from the requestCache and redirects the call to that original URL.
This part works for web requests and the one in five API calls.
The other four API calls never make it to the AuthSuccessHandler - They simply get 'lost'.
There are some ugly hacks that could be done, like blocking all calls until the one first one is re-authenticated, but that just isn't right and would be hard to get right anyway.
So can the gateway, CookieServerRequestCache or AuthenticationWebFilter only handle one request at a time? Is there a 'simple' implementation of waiting on one call from the same session to re-authenticate?
Any help would be greatly appreciated as the application simply doesn't work (from a user's perspective) until this is resolved.
I know quite some tutorials do so, but in my opinion, authenticating against the gateway is a mistake (see this answer for details why). Why not using an OAuth2 client library on your client(s) instead?
I personnaly use angular-auth-oidc-client, and I am convinced that there must be equivalents for React, Vue, Flutter, Android or iOS.
Such libraries can handle access-tokens refreshing for you (provided that you requested the offline_access scope and that the authorization-server supports refresh-token for your client).
Authenticate users on the client(s) with the help of a certified lib, have your gateway just forward Authorization header and configure your micro-services as resource-servers.

Spring Boot Authorization Server + Google OAuth2/OpenId Connect should work with access_token or id_token?

I'm a bit confused regarding whether I should be accessing my Spring Boot Resource Server via an access_token or an id_token.
First, let me quickly explain my setup:
Spring Boot app as an OAuth 2.0 Resource Server. This is configured as described in the Spring docs: Minimal Configuration for JWTs This app provides secured #Controllers that will provide data for a JavaScript SPA (eg. React)
Google's OAuth 2.0 AP / OpenID Connect already configured (Credentials, Client Id, Client Secret)
A JavaScript SPA app (eg. React) that logs the user into Google and makes requests to the Spring Boot Resource Server for secured data. These requests include the Authorization header (with Bearer token obtained from Google) for the logged in user.
For development purposes, I'm also using Postman to make requests to the Spring Boot Resource Server
I can easily configure Postman to get a token from Google. This token response from Google includes values for access_token, id_token, scope, expries_in and token_type.
However, my requests to the Resource Server are denied when Postman tries to use the value from retrieved token's access_token field as the Bearer in the Authorization header
The only way I'm able to successfully access the secured #Controllers is by using the id_token as the Bearer in the Authorization header.
Is it expected that I should use the id_token as the Bearer in the Authorization header? Or is it expected that I should use the access_token?
Some additional relevant info:
The value of the id_token is a JWT token. The value of the access_token is not a JWT token. I know this because I can decode the id_token on jwt.io but it is unable to decode the value of the access_token. Further, the Spring Boot Resource Server fails with the following when I send the access_token as the Bearer in the Authorization header:
An error occurred while attempting to decode the Jwt: Invalid unsecured/JWS/JWE header: Invalid JSON: Unexpected token ɭ� at position 2.
This blog post Understanding identity tokens says the following:
You should not use an identity token to authorize access to an API.
To access an API, you should be using OAuth’s access tokens, which are intended only for the protected resource (API) and come with scoping built-in.
Looking at at the spring-security-samples for using OAuth2 Resource Server, I see the value of there hard-coded access_token (for testing purposes) is indeed a valid JWT. As opposed to the access_token returned from Google which is not a JWT.
In summary:
I can access my Spring Boot Resource Server using the value of the id_token obtained from Google. The value of the access_token is not a JWT and fails to parse by Spring Boot.
Is there something wrong with my understanding, my configuration or what? Does Google's OpenId Connect behave differently regarding how the access_token works?
Happy to clarify or add more info if needed. Thanks for your consideration and your patience!
The blog post you mentioned is correct in my view, and I believe the OpenID Connect 1.0 spec does not intend for an id_token to be used for access purposes.
Like you, I expected that using Google as an Authorization Server would work out of the box, because Spring Security works with Google as a common OAuth2 provider for providing social login. However, this is not the case, and I believe it is not really intended, because Google is not really your authorization server. For example, I don't believe you can configure Google to work with scopes/permissions/authorities of your domain-specific application. This is different from something like Okta, where there are many options for configuring things in your own tenant.
I would actually recommend checking out Spring Authorization Server, and configuring Google as a federated identity provider. I'm working on a sample for this currently and it will be published within the next week or so (see this branch).
Having said that, if you're still interested in a simple use case where Google access tokens are used for authenticating with your resource server, you would need to provide your own opaque token introspector that uses Google's tokeninfo endpoint. It doesn't match what Spring Security expects, so it's a bit involved.
#EnableWebSecurity
public class SecurityConfiguration {
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// #formatter:off
http
.authorizeRequests((authorizeRequests) -> authorizeRequests
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken);
// #formatter:on
return http.build();
}
#Bean
public OpaqueTokenIntrospector introspector() {
return new GoogleTokenIntrospector("https://oauth2.googleapis.com/tokeninfo");
}
}
public final class GoogleTokenIntrospector implements OpaqueTokenIntrospector {
private final RestTemplate restTemplate = new RestTemplate();
private final String introspectionUri;
public GoogleTokenIntrospector(String introspectionUri) {
this.introspectionUri = introspectionUri;
}
#Override
public OAuth2AuthenticatedPrincipal introspect(String token) {
RequestEntity<?> requestEntity = buildRequest(token);
try {
ResponseEntity<Map<String, Object>> responseEntity = this.restTemplate.exchange(requestEntity, new ParameterizedTypeReference<>() {});
// TODO: Create and return OAuth2IntrospectionAuthenticatedPrincipal based on response...
} catch (Exception ex) {
throw new BadOpaqueTokenException(ex.getMessage(), ex);
}
}
private RequestEntity<?> buildRequest(String token) {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("access_token", token);
return new RequestEntity<>(body, headers, HttpMethod.POST, URI.create(introspectionUri));
}
}
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://accounts.google.com
jwk-set-uri: https://www.googleapis.com/oauth2/v3/certs

How to set access_type=offline using spring boot security and oauth flow to generate a refresh tokenn

I am using Sprint boot security + OAuth2 with google to integrate the with google. I have registered google as a oauth2 client in InMemoryClientRegistrationRepository using following propperties.
private ClientRegistration googleClientRegistration() {
Map<String ,Object> configMap = new HashMap<>();
configMap.put("access_type","offline");
return ClientRegistration.withRegistrationId("g_mail")
.clientId(googleClientId)
.clientSecret(googleSecret)
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri(redirectURI.replace("source", "G_MAIL"))
.scope("openid","email","profile","https://www.googleapis.com/auth/contacts.readonly","https://www.googleapis.com/auth/gmail.readonly","https://www.googleapis.com/auth/calendar.readonly")
.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
.tokenUri("https://www.googleapis.com/oauth2/v4/token")
.providerConfigurationMetadata(configMap)
.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
.userNameAttributeName(IdTokenClaimNames.SUB)
.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
.clientName("Google").build();
}
After user has successfully authorised and authenticated, we are receiving the authorization code in the call back handler. When using this authorization code to generate a access token, I am unable to get refresh token. Could you please help me in resolving this issue. Thanks in advance.
Note:- I have configured these client registration repository as a bean. As we will be using these access tokens to access user's data. Let me know if you need more inputs.
i have solved by my self. Following is my solution. I have update the authorization api and it worked
authorizationUri("https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent")

Spring Security multiple calls to different OAuth servers requiring multiple tokens in SecurityContext

I have a spring application that verifies a JWT token on the rest endpoint.
Using SecurityChain
.oAuth2ResourceServer()
.jwt()
This seems to create a JwtAuthenticationToken in the ReactiveSecurityContextHolder.
I then want to flow the input from this endpoint where the client is authenticated by checking the bearer token. And then call another rest service using a webClient. This web client needs to authenticate with grant type password with the external service using a different OAuth server and get is own bearer token.
The problem is that the web client uses the ReactiveSecurityContextHolder that contains the authenticated JWT. And tries to use this information rather than connect and authenticate my app to the rest endpoint.
I have set up the Yaml to register my client
spring:
security:
oauth2:
client:
registration:
Myapp:
client-id:
client-secret:
token-uri:
authorization-grant-type:
Then adding a filter function of
ServerOAuth2AuthorizedClientExchangeFilterFunction
But I get principalName cannot be empty as it seems to reuse the security context from verifying the caller on the rest endpoint in my application.
How should it be designed or samples to show how you can use different security contexts or get tokens differently between service to service calls?
You are correct that the design of ServerOAuth2AuthorizedClientExchangeFilterFunction is designed to be based on the currently-authorized client, which you've explained that you don't want to use in this case.
You've indicated that you want to use the client's credentials as the username and password for the Resource Owner Password Grant. However, there's nothing in Spring Security that is going to do that.
However, you can use WebClientReactivePasswordTokenResponseClient directly in order to formulate the custom request yourself.
Briefly, this would be a custom ExchangeFilterFunction that would look something like:
ClientRegistrationRespository clientRegistrations;
ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest>
accessTokenResponseClient = new WebClientReactivePasswordTokenResponseClient();
Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
return this.clientRegistrations.findByRegistrationId("registration-id")
.map(clientRegistration -> new OAuth2PasswordGrantRequest(
clientRegistration,
clientRegistration.getClientId(),
clientRegistration.getClientSecret())
.map(this.accessTokenResponseClient::getTokenResponse)
.map(tokenResponse -> ClientRequest.from(request)
.headers(h -> h.setBearerAuth(tokenResponse.getAccessToken().getTokenValue())
.build())
.flatMap(next::exchange);
}
(For brevity, I've removed any error handling.)
The above code takes the following steps:
Look up the appropriate client registration -- this contains the provider's endpoint as well as the client id and secret
Construct an OAuth2PasswordGrantRequest, using the client's id and secret as the resource owner's username and password
Perform the request using the WebClientReactivePasswordTokenResponseClient
Set the access token as a bearer token for the request
Continue to the next function in the chain
Note that to use Spring Security's OAuth 2.0 Client features, you will need to configure your app also as a client. That means at least changing your DSL to include .oauth2Client() in addition to .oauth2ResourceServer(). It will also mean configuring a ClientRegistrationRepository. To keep my comment focused on filter functions, I've left that detail out, but I'd be happy to help there, too, if necessary.

Retrieve Entire SAML Response in Spring Security SAML Extension

I have a Spring Boot application that is setup as a Service Provider. My end goal is to be able to call the AWS STS Assume Role with SAML service to generate AWS temporary credentials on behalf of the user with the SAML response used to initially authenticate users of my application.
I found this other question. With that answer I am able to get only the assertion, not the entire response. From my testing, the AWS API call linked above wants the entire response, not just the assertion piece.
I used this Chrome Extension to view the SAML response. When I include everything (outline below)
<samlp:Response>
...
<saml:Assertion>
...
</saml:Assertion>
</samlp:Response>
The AWS STS Assume Role with SAML works. The other related question's answer only provides me the
<saml:Assertion>...</saml:Assertion>
block and the AWS STS Assume Role with SAML fails.
So my question is how do I get the entire SAML Response XML object back in a controller of my Spring Boot application?
I don't know any direct way in spring-security-saml, but maybe you could try to implement your own SAMLProcessingFilter ie simply extending the existing one and overriding the method attemptAuthentication().
Principle:
In this method, you have access to the response returned from the IdP and post back to the SP (at least in a Redirect-POST profile)
You probably have a way to extract what you need from the httpRequest
Then you can store (session, ThreadLocal variable, ...)
And finally you delegate the authentication process to the parent (by calling super.attemptAuthentication())
`
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if ("POST".equalsIgnoreCase(request.getMethod())) {
String samlResponse = request.getParameter("SAMLResponse");
System.out.println("Original SAML Response (base64 decoded) : " + new
String(Base64.getDecoder().decode(samlResponse), StandardCharsets.UTF_8));
}
return super.attemptAuthentication(request, response);
}
`

Resources