Spring Framework - Where to parse JWT for custom claim? - spring

I have created a Spring JWT authorization application. JWT contains some custom claims. On a resource server side, I wonder, where should I parse the JWT token to collect and check these claims? Should I do this in a controller or in some filter? Whats the best practice? Maybe you have some example?

You can use a combination of a Jackson Object Mapper and Spring Security classes, namely Jwt, JwtHelper and Authentication. You can get the authentication by using Spring Security's static context object and then parse the token you receive using the JwtHelper.
ObjectMapper objectMapper = new ObjectMapper();
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
Map<String, Object> map =
objectMapper.convertValue(authentication.getDetails(), Map.class);
// create a token object to represent the token that is in use.
Jwt jwt = JwtHelper.decode((String) map.get("tokenValue"));
// jwt.getClaims() will return a JSON object of all the claims in your token
// Convert claims JSON object into a Map so we can get the value of a field
Map<String, Object> claims = objectMapper.readValue(jwt.getClaims(), Map.class);
String customField = (String) claims.get("you_custom_field_name");
I would suggest debugging and putting a breakpoint on the third line in the code above. At that point, expose the authentication object. I might have some useful details you'll need later.
This can all be done on the controller. I'm not sure how to use the filter to do so.

you can also use springframework.boot.json.JsonParser:
JsonParser parser = JsonParserFactory.getJsonParser();
Map<String, ?> tokenData = parser.parseMap(JwtHelper.decode(token).getClaims());
> tokenData.get("VALID_KEY");

I'm using this:
private Claim getClaim(String claimKey) {
Authentication token = SecurityContextHolder.getContext().getAuthentication();
try {
DecodedJWT jwt = JWT.decode(token.getCredentials().toString());
return jwt.getClaim(claimKey);
} catch (JWTVerificationException ex) {
throw new RuntimeException(ex);
}
}

Related

Differentiate requests originating from different clients in OAuth2 framework of springboot

I have 3 different clients say mobile, web, iot. I am using grant_type = password and obtaining accessToken. I get requests GET /access/resource from all the clients. I want to process them differently based on their client ID. I know /oauth/check_token reponds with client_id but how to extract it in resource server
Use JWT, when authorization server creates token, default AccessTokenConverter implementation DefaultAccessTokenConverter's convertAccessToken method does: "response.put(this.clientIdAttribute, clientToken.getClientId());" for the token to also include client id. Above mentioned response is just a hashmap which will be converted to JWT.
When your resource server gets hit on GET /access/resource:
#RequestMapping("/access/resource")
public #ResponseBody Map<String,Object> getRes() throws IOException {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
ObjectMapper objMapper = new ObjectMapper();
Map<String,Object> map = objMapper.convertValue(auth.getDetails(),Map.class);
Jwt jwt = JwtHelper.decode((String) map.get("tokenValue"));
Map<String,Object> claims = objMapper.readValue(jwt.getClaims(),Map.class);
// This is what you want
String clnt_id = (String) claims.get("client_id"); <<------- here
// your logic here based on clnt_id
// ex: if(clnt_id.equals("Specific client"){}
...
return Collections.emptyMap();;
}
OR
OAuth2Request also includes resolved client id:
Authentication auth =
SecurityContextHolder.getContext().getAuthentication();
String cliend_id = ((OAuth2Authentication) auth).getOAuth2Request().getClientId()
This option can be applied even if JWT is not used as Oauth2request is always there.
Take a look here to understand better:

Are there any endpoint for check token in ADFS?

I am using Spring Oauth2 and ADFS for security purpose. However I can not find the endpoint for checking token from response of ADFS.
I also have Spring Authorization Provider which is written in Java. And my application called it by using these properties:
security.oauth2.client.clientId=myclient
security.oauth2.client.client-secret= mysecret
security.oauth2.client.userAuthorizationUri= http://127.0.0.1:9999/oauth/authorize?resource=https://localhost:8443/login
security.oauth2.client.accessTokenUri= http://127.0.0.1:9999/oauth/token
security.oauth2.resource.user-info-uri= http://127.0.0.1:9999/login
security.oauth2.resource.token-info-uri= http://127.0.0.1:9999/oauth/check_token
security.oauth2.client.tokenName=code
security.oauth2.client.authenticationScheme=query
security.oauth2.client.clientAuthenticationScheme=form
security.oauth2.client.grant-type=authorization_code
And I have changed the values of the properties to connect with ADFS
security.oauth2.client.clientId=myclient
security.oauth2.client.client-secret= myclient
security.oauth2.client.userAuthorizationUri= https://adfs.local/adfs/oauth2/authorize?resource=https://localhost:8443/login
security.oauth2.client.accessTokenUri= https://adfs.local/adfs/oauth2/token
security.oauth2.resource.user-info-uri= https://adfs.local/adfs/oauth2/userinfo
security.oauth2.resource.token-info-uri= https://adfs.local/adfs/oauth2/check_token
security.oauth2.client.tokenName=code
security.oauth2.client.authenticationScheme=query
security.oauth2.client.clientAuthenticationScheme=form
security.oauth2.client.grant-type=authorization_code
However, I found that https://adfs.local/adfs/oauth2/check_token is invalid in ADFS.
How can I get the check_token in ADFS? check_token is Token Introspection Endpoint, however, this endpoint doesn't return node 'active' according to OAuth 2 Extension which is mandatory. See this link
This is what Spring Authorization Provider do when return check_token endpoint
#RequestMapping(value = "/oauth/check_token", method = RequestMethod.POST)
#ResponseBody
public Map<String, ?> checkToken(#RequestParam("token") String value) {
OAuth2AccessToken token = resourceServerTokenServices.readAccessToken(value);
if (token == null) {
throw new InvalidTokenException("Token was not recognised");
}
if (token.isExpired()) {
throw new InvalidTokenException("Token has expired");
}
OAuth2Authentication authentication = resourceServerTokenServices.loadAuthentication(token.getValue());
Map<String, Object> response = (Map<String, Object>)accessTokenConverter.convertAccessToken(token, authentication);
// gh-1070
response.put("active", true); // Always true if token exists and not expired
return response;
}
ADFS has no such endpoint and I don't believe it's part of the spec?
You could use:
https://[Your ADFS hostname]/adfs/.well-known/openid-configuration
to get the keys to check the JWT yourself which is the usual practice.
There are many resources on how to check the JWT e.g. this.

Setting OAuth2 token for RestTemplate in an app that uses both #ResourceServer and #EnableOauth2Sso

On my current project I have an app that has a small graphical piece that users authenticate using SSO, and a portion that is purely API where users authenticate using an Authorization header.
For example:
/ping-other-service is accessed using SSO.
/api/ping-other-service is accessed using a bearer token
Being all cloud native our app communicates with other services that uses the same SSO provider using JWT tokens (UAA), so I figured we'd use OAuth2RestTemplate since according to the documentation it can magically insert the authentication credentials. It does do that for all endpoints that are authenticated using SSO. But when we use an endpoint that is authed through bearer token it doesn't populate the rest template.
My understanding from the documentation is that #EnableOAuth2Client will only extract the token from a SSO login, not auth header?
What I'm seeing
Failed request and what it does:
curl -H "Authorization: Bearer <token>" http://localhost/api/ping-other-service
Internally uses restTemplate to call http://some-other-service/ping which responds 401
Successful request and what it does:
Chrome http://localhost/ping-other-service
Internally uses restTemplate to call http://some-other-service/ping which responds 200
How we worked around it
To work around this I ended up creating the following monstrosity which will extract the token from the OAuth2ClientContext if it isn't available from an authorization header.
#PostMapping(path = "/ping-other-service")
public ResponseEntity ping(#PathVariable String caseId, HttpServletRequest request, RestTemplate restTemplate) {
try {
restTemplate.postForEntity(adapterUrl + "/webhook/ping", getRequest(request), Map.class);
} catch (HttpClientErrorException e) {
e.printStackTrace();
return new ResponseEntity(HttpStatus.SERVICE_UNAVAILABLE);
}
return new ResponseEntity(HttpStatus.OK);
}
private HttpEntity<?> getRequest(HttpServletRequest request) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + getRequestToken(request));
return new HttpEntity<>(null, headers);
}
private String getRequestToken(HttpServletRequest request) {
Authentication token = new BearerTokenExtractor().extract(request);
if (token != null) {
return (String) token.getPrincipal();
} else {
OAuth2AccessToken accessToken = oAuth2ClientContext.getAccessToken();
if (accessToken != null) {
return accessToken.getValue();
}
}
throw new ResourceNotFound("No valid access token found");
}
In the /api/** resources there is an incoming token, but because you are using JWT the resource server can authenticate without calling out to the auth server, so there is no OAuth2RestTemplate just sitting around waiting for you to re-use the context in the token relay (if you were using UserInfoTokenServices there would be one). You can create one though quite easily, and pull the incoming token out of the SecurityContext. Example:
#Autowired
private OAuth2ProtectedResourceDetails resource;
private OAuth2RestTemplate tokenRelayTemplate(Principal principal) {
OAuth2Authentication authentication = (OAuth2Authentication) principal;
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
details.getTokenValue();
OAuth2ClientContext context = new DefaultOAuth2ClientContext(new DefaultOAuth2AccessToken(details.getTokenValue()));
return new OAuth2RestTemplate(resource, context);
}
You could probably turn that method into #Bean (in #Scope("request")) and inject the template with a #Qualifier if you wanted.
There's some autoconfiguration and a utility class to help with this pattern in Spring Cloud Security, e.g: https://github.com/spring-cloud/spring-cloud-security/blob/master/spring-cloud-security/src/main/java/org/springframework/cloud/security/oauth2/client/AccessTokenContextRelay.java
I came across this problem when developing a Spring resource server, and I needed to pass the OAuth2 token from a request to the restTemplate for a call to a downstream resource server. Both resource servers use the same auth server, and I found Dave's link helpful but I had to dig a bit to find out how to implement this. I ended up finding the documentation here, and it turn's out the implemetation was very simple. I was using #EnableOAuth2Client, so I had to create the restTemplate bean with the injected OAuth2ClientContext and create the appropriate resource details. In my case it was ClientCredentialsResourceDetails. Thanks for all great work Dave!
#Bean
public OAuth2RestOperations restTemplate (OAuth2ClientContext context) {
ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
// Configure the details here
return new OAuth2RestTemplate(details, context)
}
#Dave Syer
My UAA service is also an oauth2 client, which needs to relay JWT tokens coming in from Zuul. When configuring the oauth2 client the following way
#Configuration
#EnableOAuth2Client
#RibbonClient(name = "downstream")
public class OAuthClientConfiguration {
#Bean
public OAuth2RestTemplate restTemplate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext context) {
return new OAuth2RestTemplate(resource, context);
}
}
I do get a 401 response from the downstream service as my access token has a very short validity and the AccessTokenContextRelay does not update an incoming access token (Zuul does renew expired access tokens by the refresh token).
The OAuth2RestTemplate#getAccessToken will never acquire a new access token as the isExpired on the access token stored by the AccessTokenContextRelay drops the validity and refresh token information.
How can this by solved?

Using "spring-security-oauth2," can custom parameters be passed in the "Authorization Phase" of OAuth2?

I am implementing the Authorization Code flow + JWT.
I would like to know if and how it may be possible to add additional custom parameters to the Authorization Phase of the flow.
Essentially, I am looking to do the following:
When redirecting the user to the /oauth/authorize endpoint I would like to
pass in an additional parameter (customParemphasized textameter) in the
GET url
http://.../oauth/authorize?...customParameter=[VALUE] such that
VALUE is dynamic
I will need to retrieve VALUE when creating the
JWT, populating the JWT with that VALUE
Is this possible? How can I implement?
My idea is add your parameter during AuthorizationRequest in custom OAuth2RequestFactory at createAuthorizationRequest method like here:
#Override
public AuthorizationRequest createAuthorizationRequest(Map<String, String> authorizationParameters) {
//here
authorizationParameters.put("your", "parameter");
//
AuthorizationRequest request = super.createAuthorizationRequest(authorizationParameters);
if (securityContextAccessor.isUser()) {
request.setAuthorities(securityContextAccessor.getAuthorities());
}
return request;
}
You can populate request and inject Request or Session in your custom OAuth2RequestFactory and retrieve it
The custom parameters passed will also get stored in HttpSession.
Below is sample code to retrieve them.
HttpSession httpSession = httpServletRequest.getSession(false);
DefaultSavedRequest savedRequest = (DefaultSavedRequest)
httpSession.getAttribute("SPRING_SECURITY_SAVED_REQUEST");
Map<String, String[]> parametersMap = savedRequest.getParameterMap();

spring security saml and OBSSOCookie

our company is using Oracle access system for SAML single sign on. I implemented spring security with Spring Security SAML library, it worked great until I just found one issue recently.
Oracle Access System is using OBSSOCookie as identifier, but when saml response post back, I have no way to retrieve this cookie.
Have a look at this code:
#RequestMapping(value = "/callback")
public void callback(HttpServletRequest request, HttpServletResponse response)
throws IOException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
SAMLCredential credential = (SAMLCredential) authentication.getCredentials();
try {
XMLHelper.nodeToString(SAMLUtil.marshallMessage(credential.getAuthenticationAssertion()));
} catch (MessageEncodingException e) {
e.printStackTrace();
}
String nameID = credential.getNameID().getValue();
List<Attribute> attributes = credential.getAttributes();
JSONObject jso = new JSONObject();
String uid;
String employeeType="";
String company_name="";
String FirstName;
String roles_entitled="";
String LastName;
String primary_role="";
jso.put("nameID", nameID);
jso.put("uid", uid);
jso.put("company_name", company_name);
jso.put("roles_entitled", roles_entitled);
jso.put("primary_role", primary_role);
jso.put("employeeType", employeeType);
jso.put("FirstName", FirstName);
jso.put("LastName", LastName);
String frontend_url = sideCarService.getFrontendNodeUrl();
String token = KeyGenerator.createUserToken(jso, 3600 * 24 * 30);
String encoded = new String(Base64.encodeBase64(jso.toString().getBytes()));
response.sendRedirect(frontend_url + "#t/" + token + "/atts/" + encoded);
}
Looking at this code, I can retrieve all the info from saml response, then generate a token, giving back to frontend cookie for use.
But I really want to get OBSSOCookie, so that I can use with other microservice to retrieve data from other applicaiton which is using same saml login solution.
I tried to user request.getHeaders(), but response is empty. No OBSSOCookie at all.
Any idea for how to obtain OBSSOCookie from spring saml library?
Thanks
Presuming the cookie is available to Spring SAML during validation of the SAML Response sent from IDP you can use the following approach.
Extend class WebSSOProfileConsumerImpl and implement method processAdditionalData which should return value of the OBSSOCookie. You can access the HTTP request and its HTTP headers/cookies through the SAMLMessageContext which is provided as a parameter.
The value you return will then be available under additionalData field in the SAMLCredential - which is indented for exactly these kinds of use-cases.

Resources