How to get google user email to whitelist users when authenticating using Spring Boot OAuth2 against Google - spring-boot

I want to whitelist users connecting to my OAuth2 client, and I can't figure out how to get the user name (specifically the Google email address).
I created a Spring Boot OAuth2 application based on a Spring Tutorial
https://spring.io/guides/tutorials/spring-boot-oauth2/#_social_login_manual
I'm authenticating against Google (successfully). I want to determine the user email address so I can whitelist authenticating users.
This website,
http://www.baeldung.com/spring-security-openid-connect#filter
suggests that I can unpack the "id_token" I get back from Google, something like this:
/**
* logic to unpack a ConnectID id_token like what we get from Google -
* see "Spring Security and OpenID Connect" - heading '4. Custom OpenID Connect Filter':
* http://www.baeldung.com/spring-security-openid-connect#filter
*
* #param oa2token
* #return
*/
private static UsernamePasswordAuthenticationToken getOpenIDDataForToken(OAuth2AccessToken oa2token)
{
try {
String idToken = oa2token.getAdditionalInformation().get("id_token").toString();
Jwt tokenDecoded = JwtHelper.decode(idToken);
Map<String, String> authInfo = new ObjectMapper().readValue(tokenDecoded.getClaims(), Map.class);
OpenIdConnectUserDetails user = new OpenIdConnectUserDetails(authInfo, oa2token);
return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
} catch (InvalidTokenException e) {
throw new BadCredentialsException("Could not obtain user details from token", e);
}
}
but I can't get this code to compile - I can't figure out how to get class JtwHelper!
I searched around and the following might be the right Maven dependency:
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-jwt -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
but adding this to my pom.xml doesn't help - I don't get a real Jar file back in my .m2 repository - I get a text file!!! and bottom line, Eclipse doesn't resolve the type JwtHelper.
Help? I'm not sure where I've gone wrong.

Looks like an answer on this SO page had my answer (thanks #user2802927):
How to get custom user info from OAuth2 authorization server /user endpoint
Here's the code:
Principal principal = servlet_request.getUserPrincipal();
try {
if (principal != null) {
OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) principal;
Authentication authentication = oAuth2Authentication.getUserAuthentication();
Map<String, String> details = new LinkedHashMap<>();
details = (Map<String, String>) authentication.getDetails();
Map<String, String> map = new LinkedHashMap<>();
map.put("email", details.get("email"));
logger.debug("details map is: {}", map);
}
} catch (Exception e) {
logger.error("dumping principal " + principal + "failed, exception: ", e );
}
The output showed that I found success - the user's email address!!!
2017-05-23 11:48:26.751 DEBUG 7687 --- [nio-8443-exec-1] ication$$EnhancerBySpringCGLIB$$91415b85 :
details map is: {email=myemailaddress#gmail.com}

Related

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 :)

Spring Oauth with multiple users tables

I am creating an application using Spring with Oauth2 as a backend for two apps (provider app and a consumer app). I have two different types of users; Providers, and consumers, each with its own db table.
The problem I am facing is that I cannot find a way to know if the request is coming from a provider or a customer, as each one will be in a different db table.
The username is Not unique between the two tables. So, a provider and a consumer can have the same username (and password).
I think any of the following solutions will suffice, however, I can’t find any way to implement any of them.
Having two different endpoints for each user class. e.g. “/provider/oauth/token” and “/consumer/oauth/token”. Each with its custom authentication manager.
Or: Having two authorization servers in the same Spring application, and then mapping their “/oauth/token” to different endpoints.
Or: Sending custom data in the oauth request to know where the request is coming from, and then dynamically selecting an authentication manager.
Or: Associating different authentication manager to different OAuth clients, and then ensuring that each app will have its respective client ID.
If any of these solutions is possible, or if there is another way to accomplish this, please let me know.
Any help is appreciated.
Edit - Solution
Following the answer below, I added another client with a different client ID, check the id in the UserDetailsService and then decide which db to use. Here is the code:
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
User user = (User) authentication.getPrincipal();
String username = user.getUsername();
if (username.equals(OAuth2Configuration.provider_app))
// Load from provider db
else if (username.equals(OAuth2Configuration.consumer_app))
// Load from consumer db
else
throw new UsernameNotFoundException("ClientID " + username + " not found.");
}
};
}
UsernamePasswordAuthenticationToken is used as /oauth/token is protected with Basic Oauth using the client id and secret.
I think you should be able to look inside SecurityContextHolder.getContext().getAuthentication.
This should be an instance of OAuth2Authentication, from which you can (after you cast) call getOAuth2Request() to get the original Oauth2Request details.
With this information you can have a single UserDetailsService that can delegate lookups to the correct db tables. You could use scopes or resourceIds to help determine what db table to use.
You could use the third option. but this is not a good principal to follow. you can send a custom param in the oauth/token end point. it can be accessed by AutoWiring HttpServletRequest in the userDetailsService.
UserDetailsService
#Autowired
private HttpServletRequest httpServletRequest;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
try {
String userType = httpServletRequest.getParameter("user_type");
LOGGER.info("Load user method \n Username : " + username + "\nuser_type : " + userType);
if (userType == null) {
throw new CustomOauthException("User type is required !");
}
if (userType.equals(String.valueOf(MOBILE_USER))) {
//get user..
} else if (userType.equals(String.valueOf(DRIVER))) {
//get driver..
} else if (userType.equals(String.valueOf(ADMIN))) {
//get admin
}
throw new CustomOauthException("User type is not valid !");
} catch (Exception e) {
e.printStackTrace();
LOGGER.error("Exception : " + e.getMessage());
throw new CustomOauthException(e.getMessage());
}
}

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.

Migrating to keycloak for an app that uses spring security

I'm looking for steps to keycloak for an Spring MVC app that uses spring security currently.
I wanted to use keycloak in Sitewhere.
I guess this is so simple if I would have read keycloak's document fully:). Any how here are the steps that I followed while migrating to keycloak in Sitewhere .
Follow the steps as given in keycloak doc for spring-security
Add the dependency to sitewhere-core & sitewhere-web pom.xml as stated in adapter installation
Also add the jboss-logging dependency in sitewhere-web's pom.xml since, keycloak spring adapter has a hardcode dependency for jboss-logging.
Modify applicationcontext.xml so that it can uses keycloak for both web & api, following the sample for api
<sec:http pattern="/api/**" entry-point-ref="keycloakAuthenticationEntryPoint">
<sec:custom-filter ref="keycloakPreAuthActionsFilter" before="LOGOUT_FILTER" />
<sec:custom-filter ref="keycloakAuthenticationProcessingFilter" before="FORM_LOGIN_FILTER" />
Modify LoginManager.java as follows
public static IUser getCurrentlyLoggedInUser() throws SiteWhereException {
Authentication KeyCloakAuth = SecurityContextHolder.getContext().getAuthentication();
if (KeyCloakAuth == null) {
throw new SiteWhereSystemException(ErrorCode.NotLoggedIn, ErrorLevel.ERROR,
HttpServletResponse.SC_FORBIDDEN);
}
KeycloakAccount keyAccount = ((KeycloakAuthenticationToken) KeyCloakAuth).getAccount();
String username = keyAccount.getKeycloakSecurityContext().getIdToken().getPreferredUsername();
String password = "";
IUser user = SiteWhere.getServer().getUserManagement().authenticate(username, password);
List<IGrantedAuthority> auths =
SiteWhere.getServer().getUserManagement().getGrantedAuthorities(user.getUsername());
SitewhereUserDetails details = new SitewhereUserDetails(user, auths);
Authentication auth = new SitewhereAuthentication(details, password);
if (!(auth instanceof SitewhereAuthentication)) {
throw new SiteWhereException("Authentication was not of expected type: "
+ SitewhereAuthentication.class.getName() + " found " + auth.getClass().getName()
+ " instead.");
}
return (IUser) ((SitewhereAuthentication) auth).getPrincipal();
}
Since, we have migrated our authentication to keycloak and for the fact that we will not get credentials of user in siterwhere it's better to void the code related to password validation in authentication method of IUserManagement. Following is the sample from MongoUserManagement.java
public IUser authenticate(String username, String password) throws SiteWhereException {
if (password == null) {
throw new SiteWhereSystemException(ErrorCode.InvalidPassword, ErrorLevel.ERROR,
HttpServletResponse.SC_BAD_REQUEST);
}
DBObject userObj = assertUser(username);
String inPassword = SiteWherePersistence.encodePassoword(password);
User match = MongoUser.fromDBObject(userObj);
//nullify authentication since we are using keycloak
/*if (!match.getHashedPassword().equals(inPassword)) {
throw new SiteWhereSystemException(ErrorCode.InvalidPassword, ErrorLevel.ERROR,
HttpServletResponse.SC_UNAUTHORIZED);
}*/
// Update last login date.
match.setLastLogin(new Date());
DBObject updated = MongoUser.toDBObject(match);
DBCollection users = getMongoClient().getUsersCollection();
BasicDBObject query = new BasicDBObject(MongoUser.PROP_USERNAME, username);
MongoPersistence.update(users, query, updated);
return match;}
Make sure you have respective roles for the users in keycloak that are more specific to sitewhere.
Change your home page so that it redirects to keycloak for authentication purpose. Following is the sample for redirection:
Tracer.start(TracerCategory.AdminUserInterface, "login", LOGGER);
try {
Map<String, Object> data = new HashMap<String, Object>();
data.put("version", VersionHelper.getVersion());
String keycloakConfig = environment.getProperty("AUTHSERVER_REDIRECTION_URL");
if (SiteWhere.getServer().getLifecycleStatus() == LifecycleStatus.Started) {
return new ModelAndView("redirect:"+keycloakConfig);
} else {
ServerStartupException failure = SiteWhere.getServer().getServerStartupError();
data.put("subsystem", failure.getDescription());
data.put("component", failure.getComponent().getLifecycleError().getMessage());
return new ModelAndView("noserver", data);
}
} finally {
Tracer.stop(LOGGER);
}

Not getting Client Authority/Role while using RemoteTokenService

I am using Spring-Security-OAuth2 for implementing my own oauth server and resource server. I am using RemoteTokenService as my ResourceServerTokenService on my ResourceServer which will authenticate any accessToken using the CheckTokenEndpoint (/oauth/check_token) on OAuth Server.
I have added a antMatcher for an api url e.g. /data/list which will need client application Role / Authority: "ROLE_ADMIN" like this .antMatcher('/data/list').access("#oauth2.clientHasRole('ROLE_ADMIN')")
but it is not working.
I have done some trial and error on this end point and what I get is following :::
When oauth grant is client only i.e. client_credential grant.
what we get from /oauth/check_token
{
"scope":["read"],
"exp":1412955393,
"client_id":"sample_test_client_app"
}
we dont get any client authority. so how can spring security will perform above authorization check of "#oauth2.clientHasRole('ROLE_ADMIN')"
When oauth grant is user + client i.e. Authorization_code grant
what we get from /oauth/check_token
{
"aud":["resource_id"],
"exp":1412957540,
"user_name":"developer",
"authorities":["ROLE_USER"],
"client_id":"sample_test_client_app",
"scope":["read"]
}
and for authorization_code grnat we are still not getting client authority/role. so can any one tell me how can we perform clientHasRole authentication on any api url?
For "#oauth2.clientHasRole('ROLE_ADMIN')" to work we have to implemented our AccessTokenConverter and inject it into auth server and resource server.
so create a new class which extends DefaultAccessTokenConverter and override convertAccessToken and extractAuthentication methods.
In convertAccessToken method just add
Map<String, Object> response = (Map<String, Object>) super.convertAccessToken(token, authentication);
OAuth2Request clientToken = authentication.getOAuth2Request();
response.put("clientAuthorities", clientToken.getAuthorities());
and in extractAuthentication method add
Collection<HashMap<String, String>> clientAuthorities = (Collection<HashMap<String, String>>) map.get("client_authority");
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
for (HashMap<String, String> grantedAuthority : clientAuthorities) {
for (String authority : grantedAuthority.values()) {
grantedAuthorities.add(new SimpleGrantedAuthority(authority));
}
}
Set<String> resourceIds = new LinkedHashSet<String>(map.containsKey(AUD) ? (Collection<String>) map.get(AUD) : Collections.<String> emptySet());
OAuth2Request request = new OAuth2Request(parameters, clientId, grantedAuthorities, true, scope, resourceIds, null, null, null);
At auth server :
set this class in AuthorizationServerEndpointsConfigurer
At resource server :
set this class in RemoteTokenServices

Resources