I have a SAAS server with microservice architecture. Authentication is done by the new Spring authorization server. For some domain situation, I want to be able to re-issue a JWT for a logged-in user without forcing the user to enter their password again to enrich their token with additional claims.
Having: Logged-in user with claim set A.
Required: Create a new token for the user with claim set B. (Without user intervention)
I'm looking for something like this:
#PostMapping("/renew")
public Authentication token() {
return jwtAuthenticationProvider.authenticate(
new BearerTokenAuthenticationToken(JwtUtil.getCurrentAuthenticationTokenValue())
);
}
Where JwtUtil.getCurrentAuthenticationTokenValue() extracts logged-in user token value from SecurityContextHolder. This setup creates no new token and returns the old one like no authentication process has been triggered.
But I cannot find a function/service that generates a new token in spring authorization server.
PS. I cannot use RefreshToken to get new AccessToken because my client is public and according to this, RefreshToken only is issued for confidential clients.
You can read about OAuth2TokenCustomizer in the docs. Here's an example of customizing the access token:
#Bean
public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer() {
return (context) -> {
if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
context.getClaims().claims((claims) -> {
claims.put("claim-1", "value-1");
claims.put("claim-2", "value-2");
});
}
};
}
In your case, you could issue a new request to the authorization endpoint (e.g. GET /oauth2/authorize?...) from the client to begin the authorization_code flow with different scopes or additional request parameters and use the customizer to add whatever claims you need. Based on the information you've provided, this would be the recommended way to use the authorization server to issue new tokens.
Adding custom endpoints to perform OAuth2-related actions (such as a custom /renew endpoint) without incorporating best practices and standards from the specification(s) would not be recommended.
Related
I have a SpringBoot OAuth2 Client and ResourceServer. My AuthorizationServer is Okta.
Now suppose that in a certain moment I want to add a claim to my token, for Example:
#GetMapping("/addIdUser")
public ResponseEntity<String> addUser(#AuthenticationPrincipal Jwt jwt) {
// here I want to add a claim to my Jwt, the token
// should return to the frontend and when I resend back to my resourceserver
// it should be validated again
return new ResponseEntity<String>(token, HttpStatus.OK);
}
I have read tons of docs of TokenEnhencer and TokenConverter but it not seems to be the solution I looking for.
Can I do something like this?
Seems like you want to authorize based on domain specific claims, and there are two main options here:
Get extra claims at the time of token issuance - Okta needs to either call an API or do a database lookup - this is the preferred option but not always possible
Look up claims in the resource server when an access token is first received, then cache them for subsequent requests with the same access token
Leave the Okta issued token alone in the client, then adopt one of the above two approaches in the API. Out of interest, here is a Spring Boot API of mine that uses a library based approach to OAuth handling and implements both approaches:
Standard Authorizer
Claims Caching Authorizer
I have a situation where i have to authorize the user to certain path (endpoint) depending on the role he has.
Problem: Once the user is authenticated, i see an empty set of authorities in authentication object (which is used by authorizing filter to determine access for the user.)
I am new to spring security and would need help to address this.
Background: We have a authorization server implemented in the organization which uses oauth2 and lets us do the single sign on. SSO happens in the front end which gets the token from the authorization server and embeds it to every request sent to my server. Now my server decodes the token and authenticates the user. the user claim doesn't have authorities information and i am not allowed to make any code changes in my authorization server..
What i am looking for : Once the user gets authenticated and before he proceeds for authorization (i am using filters in my web security config) i want to make a call to the DB and get the user respective roles and save it to my authentication object, so that the authorities are now updated, user will be authorized to the endpoint.
You can create a SecurityService and call it from #PreAuthorize annotation
#GetMapping()
#PreAuthorize("securityService.hasRole('ADMIN', authentication.principal.username)")
public String getData() {
return "response";
}
And call authorized server from SecurityService with rest template for example:
public class SecurityService {
public boolean hasRole(String role, String username) {
String role = restTemplate.getRoleByUserName...;
// check role
return false/true
}
}
I have a multi-tenant application (springboot keycloak adapter + spring security) secured by Keycloak. Given the multi-tenant nature of the project, I wrote a multi-client connector which works fine.
On the official Keycloak doc, it is recommended (for multi-tenant applications) to model each tenant as a new realm, but for me it works better to have multiple clients within the same same realm. This is due to following advantages:
Client scopes, groups and other configs can be shared
Users don't need to be duplicated on N different realms
SSO login works perfectly within same realm clients (by using bearer
services +CORS)
So, everything works fine except for 1 thing, my initial SSO access_token (which is then shared across all bearer-only services by means of CORS) is kind of big (it shows all the resources - tenants - and its roles within each resource/tenant).
I'd like to limit the size of the access_token, by means of using "scopes" to restrict the roles in the token to only those meaningful to the tenant where I'm logged in at that time. For this, I'm manually firing a Request to the auth server (outside of the standard functionality provided by springboot/spring security) with the goal of manually overwriting whatever access-token exists within my app, with the new one generated by my extra request.
My "new" token request looks similar to this:
SimpleKeycloakAccount currentUserAccount = (SimpleKeycloakAccount) auth.getDetails();
String authServerUrl = currentUserAccount.getKeycloakSecurityContext().getDeployment().getAuthServerBaseUrl();
String realm = currentUserAccount.getKeycloakSecurityContext().getDeployment().getRealm();
String resource = currentUserAccount.getKeycloakSecurityContext().getDeployment().getResourceName();
String refreshToken = currentUserAccount.getKeycloakSecurityContext().getRefreshToken();
String token = currentUserAccount.getKeycloakSecurityContext().getTokenString();
Http http = new Http( new Configuration(authServerUrl, realm, resource,
currentUserAccount.getKeycloakSecurityContext().getDeployment().getResourceCredentials()
, null),
(params, headers) -> {});
String url = authServerUrl + "/realms/" + realm + "/protocol/openid-connect/token";
AccessTokenResponse response = http.<AccessTokenResponse>post(url)
.authentication()
.client()
.form()
.param("grant_type", "refresh_token")
.param("refresh_token", refreshToken)
.param("client_id", resource)
.param("client_secret", "SOME_SECRET")
.param("scope", "SOME_SCOPE_TO_RESTRICT_ROLES")
.response()
.json(AccessTokenResponse.class)
.execute();
// :) - response.getToken() and response.getRefreshToken(), contain new successfully generated tokens
My question is, how can I force my-app to change/reset the standard access-token & refresh_token obtained by the usual means, with these "custom created" tokens? or is that possible at all?
Thx for any feedback!
Further Information
To clarify more, lets analyze the behavior of a typical springboot/spring security project integrated with Keycloak:
You protect your endpoints with "roles" via configurations (either on the application.properties, or on the SecurityContext)
You know that this Spring application talks in the back channel with the Keycloak authorization server, that's how you become the access_token (But all this is a black box for the developer, you only know a Principal was created, a Security Context, Credentials; etc - everything happens behind the curtains)
Considering those 2 points above, imagine that you use an Http library to basically request a new token towards the auth server token endpoint like in the code above (yes filtered by scopes and everything). So the situation now is that though you have created a valid access_token (and refresh_token); since they were created "manually" by firing a request towards the token endpoint, this new token hasn't been "incorporated" to the application because No new Principal has been created, no new security context has been generated, etc. In other words, to the springboot application this new token is non-existent.
What I'm trying to accomplish is to tell sprinboot/spring security: "Hey pal, I know you didn't generate this token yourself, but please accept it and behave as if you'd have created it".
I hope this clarifies the intent of my question.
You can revoke a token using org.springframework.security.oauth2.provider.token.ConsumerTokenServices#revokeToken method.
On the Autorization Server:
#Resource(name="tokenServices")
ConsumerTokenServices tokenServices;
#RequestMapping(method = RequestMethod.POST, value = "/tokens/revoke/{tokenId:.*}")
#ResponseBody
public String revokeToken(#PathVariable String tokenId) {
tokenServices.revokeToken(tokenId);
return tokenId;
}
Of course, you'll have to secure this method since is a very sensitive one.
In the case that each tenant is a separate client you can just use keycloak's "Scope" mapping at each client. Just turn off Full Scope Allowed and your tokens will only contain the user's roles for that specific client (tenant).
"Scope Mappings" is a a non intuitive way of saying "Define what roles should go into the access token" :-)
When turned off the UI changes and you even can configure what other roles of other clients should additionally go into the access token.
Just to give some closure to this question:
No, there doesn't seem to be any elegant or intended way to force a manual token renewal by means of using springboot/spring security keycloak connector.
The Javascript connector can do this trivially like this:
// for creating your keycloak connector
var keycloak = Keycloak({
url: 'http://localhost:8080/auth',
realm: '[YOUR_REALM]',
clientId: '[YOUR_CLIENT]'
});
// for login in (change scopes list to change access capabilities)
var options = {
scope: [EMPTY_STRING_SEPARATED_LIST_OF_SCOPES] // <-- here specify valid scopes
};
keycloak.login(options); // <-- receive a new token with correctly processed scopes
Given how easy it is to do this with the Keycloak client JS adapter, and how obscure it is to do this with the springboot/spring security adapter, it follows following:
Security design seems intended to have 2 (Keycloak security) layers; the first is a front-facing public client (usually password protected), and the 2nd layer is composed of several bearer-only services which would ussually only accept acces-tokens. If for those bearer-only services you want to implement finner grained control via scopes, you achieve that trivially by using a javascript based Keycloak client (other connectors as explained won't deal nicely with the header modification necessary to deal with OAuth2 scopes).
I've found similar issue but it's unanswered, so I suppose I'm going to duplicate question a little.
I am using Spring OAuth2 to implement separate resource and custom authentification servers.
I've already configured interaction with auth server through issuing&validating JWT tokens and everything seems fine.
Now I'm trying to add SSO functionality but really stuck with it. I've researched the official Spring examples and attached guide but it is very short worded when it comes to connecting SSO part with custom server authentication. And actually author uses only external provider resource ('user' info) to show process.
I think it is normal thing to have all this SSO means of authentication and also custom registration. I can see it works well with stackoverflow for example.
I am loking for directions where to find any info about handling on resource server different kind of tokens issued by multiply SSO providers and also from custom auth server.
Maybe I can use auth chain to do this and some mean to distinguish token format to know how to process it. Is it possible with Spring OAuth2? Or I need to do this magic somehow manually?
For now I have just one 'maybe strange' idea:
To not involve my own resource server with this SSO stuff at all. After receiving Facebook (for example) token - just exchange it for api JWT token with custom auth server (associating or creating user on the way) and then work with resource server on standard basics
EDITED:
I've found at least something. I've read about configuring filters in authorization chain and translate given social tokens to my custom JWT-s as 'post authenticate'(not a crazy idea after all). But it mostly done with SpringSocial.
So now question is: how to do that?
Forgot to say that I am using Password Grant for authentication on custom server. Clients will be only trusted application and I do not even sure about browser client (thinking about only native mobile options). Even if I decide to have browser client I'll make sure it's going to have backend to store sencetive information
Ok, so after struggling to implement such behavior I've stuck with two different libraries (Spring Social & OAuth2). I decided to go my own way and do it with just Spring OAuth2:
I have the resource server, authentication server and client(backed up by Java and uses OAuth2 Client library, but it can be any other client) - my resources can be consumed only with my own JWT auth token given by my own auth server
in a case of a custom registration: client obtains JWT token(with refresh token) from auth server and sends it to the res server. Res server validates it with public key and gives the resource back
in a case of SSO: client obtains Facebook(or other social platform token) and exchanges it for my custom JWT token with my custom auth server. I've implemented this on my auth server using custom SocialTokenGranter(currently handles facebook social token only. For every social network I'll need separate grant type). This class makes an additional call to facebook auth server to validate token and obtain user info. Then it retrieves the social user from my db or creates new and returns JWT token back to the client. No user merging is done by now. it is out of scope for now.
public class SocialTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "facebook_social";
GiraffeUserDetailsService giraffeUserDetailsService; // custom UserDetails service
SocialTokenGranter(
GiraffeUserDetailsService giraffeUserDetailsService,
AuthorizationServerTokenServices tokenServices,
OAuth2RequestFactory defaultOauth2RequestFactory,
ClientDetailsService clientDetailsService) {
super(tokenServices, clientDetailsService, defaultOauth2RequestFactory, GRANT_TYPE);
this.giraffeUserDetailsService = giraffeUserDetailsService;
}
#Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails clientDetails, TokenRequest request) {
// retrieve social token sent by the client
Map<String, String> parameters = request.getRequestParameters();
String socialToken = parameters.get("social_token");
//validate social token and receive user information from external authentication server
String url = "https://graph.facebook.com/me?access_token=" + socialToken;
Authentication userAuth = null;
try {
ResponseEntity<FacebookUserInformation> response = new RestTemplate().getForEntity(url, FacebookUserInformation.class);
if (response.getStatusCode().is4xxClientError()) throw new GiraffeException.InvalidOrExpiredSocialToken();
FacebookUserInformation userInformation = response.getBody();
GiraffeUserDetails giraffeSocialUserDetails = giraffeUserDetailsService.loadOrCreateSocialUser(userInformation.getId(), userInformation.getEmail(), User.SocialProvider.FACEBOOK);
userAuth = new UsernamePasswordAuthenticationToken(giraffeSocialUserDetails, "N/A", giraffeSocialUserDetails.getAuthorities());
} catch (GiraffeException.InvalidOrExpiredSocialToken | GiraffeException.UnableToValidateSocialUserInformation e) {
// log the stacktrace
}
return new OAuth2Authentication(request.createOAuth2Request(clientDetails), userAuth);
}
private static class FacebookUserInformation {
private String id;
private String email;
// getters, setters, constructor
}
}
And from class extending AuthorizationServerConfigurerAdapter:
private TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
List<TokenGranter> granters = new ArrayList<>(Arrays.asList(endpoints.getTokenGranter()));
granters.add(new SocialTokenGranter(giraffeUserDetailsService, endpoints.getTokenServices(), endpoints.getOAuth2RequestFactory(), endpoints.getClientDetailsService()));
return new CompositeTokenGranter(granters);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
...
.allowFormAuthenticationForClients() // to allow sending parameters as form fields
...
}
Every JWT token request is going to 'host:port + /oauth/token' url
Depending on 'Grant type' the server will handle such requests differently. Currently I have 'password'(default), 'refresh_token' and 'facebook_social'(custom) grant types
For default 'password' Grant type the client should send next parameters:
clientId
clientSecret (depends of the client type. Not for single-page clients)
username
password
scope (if not explicitly set in auth server configuration for current client)
grantType
For 'refresh_token' Grant type the client should send next parameters:
clientId
clientSecret (depends of the client type. Not for single-page clients)
refresh_token
grantType
For 'facebook_social' Grant type the client should send next parameters:
clientId
facebook_social_token (custom field)
grantType
Based on the client design the way to send these requests will be different.
In my case with test Java based client which uses Spring OAuth2 library to obtain the social token I do the token exchange procedure with the redirect in controller(controller being invoked using url defined in facebook dev page configuration).
It can be handled in two stages: after obtaining facebook social token JavaScript can make a separate explicit call to my auth server to exchange tokens.
You can see Java client implementation examples here, but I doubt that you're going to use Java client in production:https://spring.io/guides/tutorials/spring-boot-oauth2/
My application login is externalized into an external form based on OAuth2 Authorization Code Flow.
I wonder how to solve the problem explained in the Spring IO forum taking into account that AccessTokenProvider (org.springframework.security.oauth2.client.token.AccessTokenProviderChain) will always complain by an Anonymous user (obviously physical customer is trying to authenticate and no authentication object is set yet):
if (auth instanceof AnonymousAuthenticationToken) {
if (!resource.isClientOnly()) {
throw new InsufficientAuthenticationException(
"Authentication is required to obtain an access token (anonymous not allowed)");
}
}
I respond to myself, it looks like injecting an org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider directly instead of falling back to defaults (org.springframework.security.oauth2.client.token.AccessTokenProviderChain) is the easiest solution in my case