How to access external URL which requires OAuth2 via Spring Boot? - java

Currently, the mechanism that we use for authenticating against a server which requires OAuth2 is to write a Java program which contains a main() method, which runs an HttpClient to generate an OAuth2 access token by using this call:
https://api.externalsite.com/v1/oauth/token?clientId=iLHuXeULFBdW4B1dmRY0MhFILRQnlfeK&clientSecret=RG3JanXEq2R1GhRvIQ2d2AKRx0SORvb3&grant_type=client_credentials
This returns the following JSON payload:
{
"access_token": "eyJhbGciOi786I1NiJ9.eyJ1c2VybmFtZSI6bnVsbCwiZGV2aWNlSWQiOm51bGwsImNsaWVudElkIjoiaUxIdVhlVUxGQmRXNEIxZG1SWTBNaFJPTVJRbmxmZUsiLCJhZElkIjpudWxsLCJleHAiOjE1MjU0MjY4LMYsImlhdCI6MTUyNTQyMzE0Nn0.Zz_uhXqOF2ykC24mNBWHnQ_Vmx-jfQs3X4qcmmN0-Sk",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": null,
"scope": null
}
After obtaining the access token, we are able to run queries using JSON against the authorized website / service.
Question(s):
Inside a Spring Boot Microservice (2.0.1.RELEASE), how can one use Spring Security or just an HttpClient to use clientId, clientSecret and grant_type to automatically provide a global access token inside each REST call (which might be an HTTP Post) from the REST controller layer?
Can someone show a code sample of how to use Spring Security or a different library to just send the clientId, clientSecret, and grant_type to obtain an OAuth2 access token?
What to do (using the library from question # 2) if the OAuth2 token expires?

1) You dont need spring security .Just use 'io.jsonwebtoken.Jwts'.
You can use any number of parameters to generate the JWT token .
You can use a component inside your Spring boot application to generate the JWT token .
Then create a Token service that will use this bean and perform :generate access token , validate access token and refresh the token .
2)
Sample :
#Component
public String createJwtToken(User user, TokenType type, ClientKey clientKey) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
String userName = user.getUsername();
Date currentTime = new Date();
String token = Jwts.builder()
.setSubject(userName)
.claim(Constants.NAME_KEY, Constants.NAME_VALUE)
.claim(Constants.USER_TOKEN_KEY, clientKey.getKey())
.claim(Constants.SCOPE_KEY, Constants.SCOPE_VALUE)
.claim(Constants.TOKEN_TYPE, type.name())
.setIssuer(tokenIssuer)
.setHeaderParam(Constants.TOKEN_TYP, Constants.TOKEN_JWT)
.setHeaderParam(Constants.TOKEN_TYPE, type.name())
.setIssuedAt(currentTime)
.setExpiration(timeout(type))
.signWith(SignatureAlgorithm.HS256, key)
.compact();
return encrypt(token);
}
3) Whenever you generate the token for the first time you generate 2 tokens : accessToken and Refresh Token .
AccessToken is short lived and expires soon . - say 5 mins .
The refresh token has a onger expiry duration : eg: 20 mins .
Purpose of refresh token is that you can use the refresh token to generate the new access token .
So when ur access token expires, just make a call to the refresh token method by passing ur refresh token . This method should return the user from redis with the new access token .
Regards,
R Rai

Found an OAuth2Client which open sourced and offered by IBM:
https://www.ibm.com/developerworks/library/se-oauthjavapt1/index.html#download

Also, just used RestTemplate:
String accessToken = OAuth2Client.generateAccessToken();
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "Bearer "+accessToken);
HttpEntity<String> entity = new HttpEntity<String>(request,headers);
String response = restTemplate.postForObject(url, entity, String.class);
Very easy!

Related

Keycloak impersonate with token-exchange

Technical Details:
Keycloak Version: 12.0.2
Java Version: 1.8
Java Admin Client: 12.0.2
Keycloak Spring boot starter
Description
I have a Spring Boot application where internal staff can create and modify Keycloak users, using its official java dependency. This application should also contain an impersonation function. I tried impersonating with the Java Admin Client, the impersonate function returns a map, which contains the Boolean “sameRealm” and the redirect URL to the account page of Keycloak. With these 2 values I can’t access the session of the impersonated User nor can I really use these attributes for something else. Afterwards I tried the impersonate function of the Keycloak Rest API, the API returned some Cookies in the header unfortunately I can’t seem to figure out how to use these cookies, but I tried creating these cookies and set them but unfortunately it didn’t work. Lastly, I tried a token exchange to receive a valid access token and fortunately the function works.
token-exchange function
Keycloak keycloakService = KeycloakBuilder.builder()
.serverUrl(serverUrl)
.realm(realm)
.clientId(clientId)
.grantType(OAuth2Constants.CLIENT_CREDENTIALS)
.clientSecret(clientSecret)
.build();
BasicCookieStore cookieStore = new BasicCookieStore();
CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultCookieStore(cookieStore).build();
HttpUriRequest reqBuild = RequestBuilder.post()
.setUri(serverUrl + "/realms/intern/protocol/openid-connect/token")
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.addParameter("client_id", "example")
.addParameter("client_secret", "example") //
.addParameter("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange")
.addParameter("subject_token", keycloakService.tokenManager().getAccessTokenString())
.addParameter("requested_token_type", "urn:ietf:params:oauth:token-type:access_token")
.addParameter("requested_subject", userId)
.addParameter("audience", "target-client")
.build();
HttpResponse res = httpClient.execute(reqBuild);
String resBody = EntityUtils.toString(res.getEntity());
System.out.println(resBody);
With the above code I receive a valid access-token from Keycloak. But unfortunately, I don’t know how I can use this token to complete the Impersonation process.
The setup is as following: id.example.com provides the Spring Boot Application, sso.id.example.com runs KeyCloak instance. For my understanding, I should create a cookie for sso.id.example.com
If someone has another solution on how I can Impersonate a user and then acquire the associated session. I would appreciate the help.
Your resBody.content is of type AccessTokenResponse (assuming you got a 200 response) which has an token property. The token should be the auth_token for the user you want to impersonate. As a sanity check you can use https://jwt.io/ to debug the jwt token returned to make sure it is for the correct user.
You can use this token for subsequent calls as the person you are impersonating.
I don't believe you need to set a cookie store, i am certain you can remove this and it should continue to work.
import org.keycloak.admin.client.Keycloak
...
val keycloakService = KeycloakBuilder.builder()
.serverUrl(serverUrl)
.realm(realm)
.clientId(clientId)
.grantType(OAuth2Constants.CLIENT_CREDENTIALS)
.clientSecret(clientSecret)
.build();
val httpClient = HttpClientBuilder.create().build();
val reqBuild = RequestBuilder.post()
.setUri(serverUrl + "/realms/intern/protocol/openid-connect/token")
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.addParameter("client_id", "example")
.addParameter("client_secret", "example") //
.addParameter("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange")
.addParameter("subject_token", keycloakService.tokenManager().getAccessTokenString())
.addParameter("requested_token_type", "urn:ietf:params:oauth:token-type:access_token")
.addParameter("requested_subject", userId)
.addParameter("audience", "target-client")
.build();
val response = httpClient.execute(reqBuild)
val entity = if (response.statusLine.statusCode == 200) {
val mapper = ObjectMapper().registerModule(KotlinModule())
mapper.readValue(response.entity.content, AccessTokenResponse::class.java)
} else {
// handle error
}
// this should log you in as the impersonated user
val impersonated = Keycloak.getInstance(serverUrl, realm, clientId, entity.token).realm(realm)

keycloak offline token with Java

I am using authentication of users in Java 8 against Keycloak, with the Keycloak adapter API for Java.
In this case, the class KeycloakBuilder (keycloak-admin-client-6.0.0.jar) builds a Keycloak instance to perform authentication operations.
how can I request an offline token rather than a normal Bearer token using this API?
Have not found parameter or way to request it. I need tokens with 1 month expiration time, which cannot get unless change the "SSO Session Max" field, but I don´t want this to affect other Clients or users in the same Realm / client.
I am not sure if there are any specialties with the Keycloak Java adapter but I already implemented this with other clients. On the Authorization server side, you need to add a role offline_access to the users, which are allowed to request an offline session (this can be done explicitly or as a default role mapping). On the client side, you have to add another scope offline_access to the auth request. This can also be done by default (see default scopes). Please refer to the official Keycloak documentation about Offline Sessions for further details.
I post a possible solution using keycloak-authz-client library instead.
As stated by #Philipp , it is also necessary that the user you log in with has the role offline_access.
public String login(String username, String password) {
String authServerUrl = "http://localhost:18080/auth"; // Your keycloak auth entpoint
String realm = "realm"; // Realm
String clientId = "client"; // Client
Map<String, Object> clientCredentials = new LinkedHashMap<String, Object>();
clientCredentials.put("secret", "clientSecret"); // Client secret (Access Type: Confidential)
Configuration configuration = new Configuration(
authServerUrl,
realm,
clientId,
clientCredentials,
null
);
AuthzClient authzClient = AuthzClient.create(configuration);
AuthorizationRequest request = new AuthorizationRequest();
request.setScope("offline_access");
AuthorizationResponse response = authzClient.authorization(username, password).authorize(request);
return response.getRefreshToken(); // response.getToken() returns the bearer token
}

How to generate JWT Token using Facebook credentials in Spring boot

I have one requirement that have to develop Spring boot Rest API for login using Facebook credentials and have to generate JWT token for this credentials and validate every request.
Thanks.
When the user logs in using facebook credentials, the user will be authenticated by the facebook and will issue the token that can be used for authorisation. My point is that once you authenticate the user on facebook, you get the credential. Instead of wrapping it into the JWT, store it somewhere with id and wrap that ID in the JWT. On each request, you will receive the JWT token and while authorising, you can get id from the JWT and get it from the persistence or store and authorise the user.
Let say you have the facebook tokens after login, then you have to store it somewhere using some id
String fbTokenId = storeFBToken(fbToken);. Using this token you can generate JWT token like:
Algorithm algorithm = Algorithm.HMAC256(key);
String token = JWT.create().withIssuer(author)
.withClaim("fbTokenId", fbTokenId)
.withSubject("user")
.sign(algorithm);
When you authorise the request, you will have the JWT token. Now you have to get verify the token and read the fbTokenId to verify it:
Algorithm algorithm = Algorithm.HMAC256(key);
JWTVerifier verifier = JWT.require(algorithm).withIssuer(author)
.build();
DecodedJWT jwt = verifier.verify(token);
String fbTokenId = jwt.getClaim("fbTokenId").as(String.class);
String fbToken = getFBTokenFromStore(fbTokenId);
Before you jump into this, make sure you understand stateless authentication and authorisation concepts especially with tokens.
Find a sample authentication application here

How to use refresh token by using Keycloak Admin REST API

I'm trying to use Keycloak Admin REST API SDK.
Then, I want to use refresh token to generate access token.
I know that the following code code can generate access token by refresh token.
val token = Keycloak.getInstance(serverUrl,
realmName,
userName,
password,
clientId,
clientSecret)
.tokenManager()
.refreshToken()
But this code can only work on TokenManager.
I want to use any refresh token like following code by admin user.
// I hope to call token endpoint -> /realms/{realm}/protocol/openid-connect/token?grant_type=refresh_token...
val accessToken = tokenManager.refresh(String refreshToken);
Could any SDK Class refresh token as I want?

troubleshooting scribe for social api authentication

I just started looking at scribe for authentication with social networks such as twitter/facebook etc. I am using the example for twitter as reference. However, I don't seem to get oauth_verifier from twitter for some reason (even though the callback is registered through the service builder - I am using localhost in the callback as it worked with another social api). Any helpful suggestions would be quite welcome. thanks in advance.
OAuthService service = new ServiceBuilder()
.provider(TwitterApi.class)
.apiKey(consumerKey)
.apiSecret(consumerSecret)
.callback("http://localhost/oauth/twitter")
.build();
//get the token
Token requestToken = service.getRequestToken();
String authUrl = service.getAuthorizationUrl(requestToken);
Logger.info("authurl::" + authUrl); // not getting the oauth_verifier
Debug output from scribe (I changed the token info):
setting oauth_callback to http://localhost/oauth/twitter
generating signature...
base string is: POST&http%3A%2F%2Fapi.twitter.com%2Foauth%2Frequest_token&oauth_callback%3Dhttp%253A%252F%252Flocalhost%252Foauth%252Ftwitter%26oauth_consumer_key%3DAAACCCV6ASDFGHJCgYBCD%26oauth_nonce%3D607134093%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1353965859%26oauth_version%3D1.0
signature is: +mSqKJIC1Q0pMEFs/gIJViF7kbg=
appended additional OAuth parameters: { oauth_callback -> http://localhost/oauth/twitter , oauth_signature -> +mSqKJIC1Q0pMEFs/gIJViF7kbg= , oauth_version -> 1.0 , oauth_nonce -> 607134093 , oauth_signature_method -> HMAC-SHA1 , oauth_consumer_key -> AAACCCV6ASDFGHJCgYBCD , oauth_timestamp -> 1353965859 }
using Http Header signature
sending request...
response status code: 200
response body: oauth_token=itJrpOP3KLeD7Ha6oy0IRr4HysFut5eAOpIlj8OmNE&oauth_token_secret=X8LmhAUpvIkfEd7t7P1lvwwobC3JJIhUabcEs0Rn5w&oauth_callback_confirmed=true
authurl::https://api.twitter.com/oauth/authorize?oauth_token=itJrpOP3KLeD7Ha6oy0IRr4HysFut5eAOpIlj8OmNE
obtaining access token from http://api.twitter.com/oauth/access_token
setting token to: Token[itJrpOP3KLeD7Ha6oy0IRr4HysFut5eAOpIlj8OmNE , X8LmhAUpvIkfEd7t7P1lvwwobC3JJIhUabcEs0Rn5w] and verifier to: org.scribe.model.Verifier#55ac8c3d
generating signature...
Update:
I am able to receive the oauth_verifier now. I will update this post once I am done testing.
pilot error mostly. I was able to get oauth working with twitter using scribe. After getting the service, the request Token from the service & then the authorizationUrl from the service (while passing in the request token), I was able to redirect to the authorization URL. Once there, I was able to authenticate myself against twitter using my twitter ID which redirected me to the callback URL specified when I created the service. Upon authentication, I received the oauth_verifier which I was able to use to create the verifier & then receive the access token from the service using the verifier and the request token. Then the oauth request was made & signed which resulted in the response from twitter with the user details. Worked. Hope it helps.

Categories

Resources