Differentiate requests originating from different clients in OAuth2 framework of springboot - spring-boot

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:

Related

Generate JWT OAuth2 Access token without password

My Spring Boot authorization service is able to create JWT tokens for users with correct credentials. It shall also be possible to get a token via social login (Facebook in this case).
The Facebook authentication already works and I find myself redirected to the following endpoint after I get the users's Facebook data.
I could create the JWT token completely from scratch, but that is not what I want. I want to use the already configured (with key pair and so on) TokenServices from my authentication server.
The only way I found was via TokenEndpoint. The problem is that I need the user's password, which I don't have and shouldn't have at this point.
How can I generate the token from what I already configured?
This endpoint is where I end up after Facebook's redirect:
#GetMapping("/loginSuccess")
fun getLoginInfo(authentication: OAuth2AuthenticationToken): ResponseEntity<OAuth2AccessToken> {
val client = authorizedClientService.loadAuthorizedClient<OAuth2AuthorizedClient>(authentication.authorizedClientRegistrationId, authentication.name)
val userInfoEndpointUri = client.clientRegistration.providerDetails.userInfoEndpoint.uri
if (!StringUtils.isEmpty(userInfoEndpointUri)) {
val restTemplate = RestTemplate()
val headers = HttpHeaders()
headers.add(HttpHeaders.AUTHORIZATION, "Bearer " + client.accessToken.tokenValue)
val entity = HttpEntity("", headers)
val response = restTemplate.exchange(userInfoEndpointUri, HttpMethod.GET, entity, Map::class.java)
// At this point I have the email address of the user and I am able to
// map it to my own User Entity
// This is where I would like to create a token and return it
// However, the following generation process requires the user's password
return authService.generateToken((response.body as Map<*, *>)["email"] as String)
}
throw AuthenticationException("Error")
}
I would like to use getAccessToken(OAuth2Authentication authentication) in JwtTokenStore but its implementation returns null:
#Override
public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
// We don't want to accidentally issue a token, and we have no way to
// reconstruct the refresh token
return null;
}

How to validate facebook authorization token and sign up user using Spring(java)

I am developing an app whose frontend is written using React.js and the backend REST API is written using the Spring framework. I wanted to add social logins to my website, so after days of googling and research, I understood that OAuth2 is the solution. I came to know that the frontend should handle getting the authorization token from the Resource Server(Facebook here) and my backend(java) should validate that token and connect with Facebook to get an access token. Then that access token should be stored in my database along with the user details(e.g email).
Here is my requirement, once the user clicks on the "Continue with Facebook" button, my app should create there account in my own database using details - email and Name(the signup feature). And later whenever they click on this button again, they will be logged in not sign up. The way other websites handle it.
As of now, I have the button working in my app, which brings me the authorization token from Facebook.
Can someone please guide me the path I should follow here.
Also, any special attention to some error handling I should follow.
Here's the general approach using Spring Boot as a REST API backed by Spring Data JPA and Spring Security that works for iOS and ember.js together. There's probably libraries and what not that you can use but I'm just going to outline the fundamental flow.
Your user object needs a one to one mapping to a facebook account. Best practice would involve encrypting the authToken before storing in the DB
#Entity
class FacebookAccount {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
Long id
String facebookUserId
String authToken
#OneToOne
#JoinColumn(name="user_id")
User user
}
#Entity
class User{
...
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
FacebookAccount facebookAccount
}
Use the facebook Javascript SDK to get a User Access Token and the User's Facebook User ID. You'll get a response back from facebook in your react app that looks like this in the successful case:
{
status: 'connected',
authResponse: {
accessToken: '...',
expiresIn:'...',
reauthorize_required_in:'...'
signedRequest:'...',
userID:'...'
}
}
Hit some login endpoint with the info received in step 2 like /login/facebook. I cannot predict how your app is structured. In my app, this code is handled by my Authentication Filter that implements GenericFilterBean. I pass a header X-Auth-Facebook with the token.
Verify the token. I'm doing this in a class that implements AuthenticationProvider within the Authentication authenticate(Authentication authentication) throws AuthenticationException method. This class will need your App's Access Token accessToken and the user's Token userAccessToken:
URIBuilder builder = URIBuilder.fromUri(String.format("%s/debug_token", "https://graph.facebook.com"))
builder.queryParam("access_token", accessToken)
builder.queryParam("input_token", userAccessToken)
URI uri = builder.build()
RestTemplate restTemplate = new RestTemplate()
JsonNode resp = null
try {
resp = restTemplate.getForObject(uri, JsonNode.class)
} catch (HttpClientErrorException e) {
throw new AuthenticationServiceException("Error requesting facebook debug_token", e)
}
Boolean isValid = resp.path("data").findValue("is_valid").asBoolean()
if (!isValid)
throw new BadCredentialsException("Token not valid")
String fbookUserId = resp.path("data").findValue("user_id").textValue()
if (!fbookUserId)
throw new AuthenticationServiceException("Unable to read user_id from facebook debug_token response")
// spring data repository that finds the FacebookAccount by facebook user id
FacebookAccount fbookAcct = facebookAccountRepository.findByFacebookUserId(fbookUserId)
if(!fbookAcct){
// create your user here
// save the facebook account as well
} else{
// update the existing users token
fbookAcct.authToken = userAccessToken
facebookAccountRepository.save(fbookAcct)
}
// finish the necessary steps in creating a valid Authentication
I, personally, then create a token that my client's use when accessing my API (rather than have them continue to pass the facebook token with all requests).
I also need more user provided information to create the user (a chosen username, agreeing to terms and conditions, etc). So my actual implementation throws an EntityNotFoundException instead of creating the user, which my clients then use to pop up a registration form that provides only the fields I cannot get from facebook. On submit of this from the client, I hit my /signup/facebook endpoint with the facebook token and what's needed to create my user. I fetch the profile from facebook and create the user (automatically logging them in the process).
Edit: If you want to use Spring 0Auth, you could follow the example for creating a Spring 2 Oauth Rest Template
#Bean
public OAuth2ProtectedResourceDetails facebook() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setId("facebook");
details.setClientId("233668646673605");
details.setClientSecret("33b17e044ee6a4fa383f46ec6e28ea1d");
details.setAccessTokenUri("https://graph.facebook.com/oauth/access_token");
details.setUserAuthorizationUri("https://www.facebook.com/dialog/oauth");
details.setTokenName("oauth_token");
details.setAuthenticationScheme(AuthenticationScheme.query);
details.setClientAuthenticationScheme(AuthenticationScheme.form);
return details;
}
#Bean
public OAuth2RestTemplate facebookRestTemplate(OAuth2ClientContext clientContext) {
OAuth2RestTemplate template = new OAuth2RestTemplate(facebook(), clientContext);
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON,
MediaType.valueOf("text/javascript")));
template.setMessageConverters(Arrays.<HttpMessageConverter<?>> asList(converter));
return template;
}
and then in use:
public String photos(Model model) throws Exception {
ObjectNode result = facebookRestTemplate
.getForObject("https://graph.facebook.com/me/friends", ObjectNode.class);
ArrayNode data = (ArrayNode) result.get("data");
ArrayList<String> friends = new ArrayList<String>();
for (JsonNode dataNode : data) {
friends.add(dataNode.get("name").asText());
}
model.addAttribute("friends", friends);
return "facebook";
}
I took the above request for friends from the project. it shouldn't be hard to tailor the above code I showed with debug_token to use the Spring OAuth rest template. Hope this helps :)

Share SPRING_SECURITY_CONTEXT between two applications

I have two different Spring Boot Applications that run on localhost on different ports (8080, 8081) and different configs (application.yml). These apps use SSO with OAuth 2.0 to get authorization token from Authorization Server. I log in to my first application, get authorization and everything works great here. Now I need to share these authentication details with second Spring Boot App (on port 8081) to authorize second app in Authorization Server. Googled and found 2 aproaches: I can try to share HttpSession between two apps (but I think it's redundant) OR HttpSessionSecurityContextRepository as SecurityContextRepository which seems more convenient. The problem here is that I can't manage to do so and I'm still not sure that it's a good idea to share Security Context between 2 apps.
What I tried for now:
Share authorization token from first app via headers in GET request (custom-built in accordance with specification for requests for Authorization Server), but it didn't work - second app doesn't take in mind this token.
Share authorized cookie from first app to second, but it didn't work, too.
I can't do authorization through Authorization Server on second app because it may be not a Spring Boot App with #Controller but any other app without HTML forms, so I need to authorize on first app (with UI), get all the data which is needed to perform authorized requests and pass it to second app (third, fourth...) so they will be able to do authorized requests too.
Thanks in advance!
I presume that your authorization/resource server is external application.And you can login successfully with your first application so flow is working.You have two client application with own client_id, client_secret and etc. parameters.If these parameters are different then authorization/resource server will return different bareer token and sessionid cookie for first and second client application.Otherwise you need to authorize both of them in authorization/resource server.
I would offer when user do login to first app then in background you do login also for second application.
For automatically authorizing second application you can try to do oauth2 login flow manually for second application with own parameters when after successful first application login and send cookies to frontend which you got from oauth2 login.
For manual oauth2 login you can try below code:
private Cookie oauth2Login(String username, String password, String clientId, String clientSecret) {
try {
String oauthHost = InetAddress.getByName(OAUTH_HOST).getHostAddress();
HttpHeaders headers = new HttpHeaders();
RestTemplate restTemplate = new RestTemplate();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
// Basic Auth
String plainCreds = clientId + ":" + clientSecret;
byte[] plainCredsBytes = plainCreds.getBytes();
byte[] base64CredsBytes = org.apache.commons.net.util.Base64.encodeBase64(plainCredsBytes);
String base64Creds = new String(base64CredsBytes);
headers.add("Authorization", "Basic " + base64Creds);
// form param
map.add("username", username);
map.add("password", password);
map.add("grant_type", GRANT_TYPE);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map,
headers);
// CALLING TOKEN URL
OauthTokenRespone res = null;
try {
res = restTemplate.postForObject(OAUTH_HOST, request,
OauthTokenRespone.class);
} catch (Exception ex) {
ex.printStackTrace();
}
Optional<OauthTokenRespone> optRes = Optional.ofNullable(res);
String accessToken = optRes.orElseGet(() -> new OauthTokenRespone("", "", "", "", "", ""))
.getAccess_token();
// CALLING RESOURCE
headers.clear();
map.clear();
headers.setContentType(MediaType.APPLICATION_JSON);
map.add("access_token", accessToken);
request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
Cookie oauthCookie = null;
if (accessToken.length() > 0) {
HttpEntity<String> response = restTemplate.exchange(
OAUTH_RESOURCE_URL.replace(OAUTH_HOST, oauthHost) + "?access_token=" + accessToken,
HttpMethod.POST, request, String.class);
String cookie = Optional.ofNullable(response.getHeaders().get("Set-Cookie"))
.orElseGet(() -> Arrays.asList(new String(""))).get(0);
if (cookie.length() > 0) {
String[] c = cookie.split(";")[0].split("=");
oauthCookie = new Cookie(c[0], c[1]);
oauthCookie.setHttpOnly(true);
}
}
return Optional.ofNullable(oauthCookie).orElseGet(() -> new Cookie("Ops", ""));
} catch (Throwable t) {
return new Cookie("Ops", "");
}
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class OauthTokenRespone {
private String access_token;
private String token_type;
private String refresh_token;
private String expires_in;
private String scope;
private String organization;
// getter and setter
}
And call this method after first app login as follows :
Cookie oauthCookie = oauth2Login(authenticationRequest.getUsername(), authenticationRequest.getPassword(),
CLIENT_ID, CLIENT_SECRET);
After getting cookie you need change its name (for example JSESSIONID-SECOND) because same cookies will override each other and also need to change its domain path to second app domain.
response.addCookie(oauthCookie);
Last you need add cookie to response (it is HttpServletResponse reference).
Hope it helps!

Spring Framework - Where to parse JWT for custom claim?

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);
}
}

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