Oauth2 Client in Spring security - java

I have troube finding example for OAuth2 client implemented using Spring.
I have OAuth2 authorization and resource server implemented using Spring. I want to get access token from that authorization server. I need an example how to get access token from my OAuth2 server using only client credentials. There is no user involved, just my client app getting access token using client credentials and then using it to access client resources.
I found only example using Java libraries, but I assume there is support for that in Spring's OAuth2 framework.
If possible, example should contain OAuth2 client, OAuth2 Authorization server and OAuth2 resource server, all communicating over TLS using self signed certificate, implemented using Spring, using no xml configuration.
Here is the sequence diagram:

It is fairly straightfoward to get an access token via Spring Security OAuth2 library as the sample code shown below. The only dependency you need in this case is
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
Sample Code:
#Test
public void getAccessTokenViaSpringSecurityOAuthClient() {
try{
ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails();
resourceDetails.setClientSecret(TestOAuthConstants.CLIENT_SECRET);
resourceDetails.setClientId(TestOAuthConstants.CLIENT_ID);
resourceDetails.setAccessTokenUri(TestOAuthConstants.TOKEN_REQUEST_URL);
resourceDetails.setScope(TestOAuthConstants.SCOPES);
OAuth2RestTemplate oAuthRestTemplate = new OAuth2RestTemplate(resourceDetails);
org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
headers.setContentType( MediaType.APPLICATION_JSON );
OAuth2AccessToken token = oAuthRestTemplate.getAccessToken();
System.out.println(oAuthRestTemplate.getResource());
System.out.println(oAuthRestTemplate.getOAuth2ClientContext());
System.out.println(token);
assertTrue(token != null);
} catch (Exception e) {
e.printStackTrace();
}
}

Related

Resolve #RegisteredOAuth2AuthorizedClient with the token obtained outside of spring server of Spring Security 5.2.x

I have a controller method that has an argument OAuth2AuthorizedClient with annotation #RegisteredOAuth2AuthorizedClient.
ResponseEntity<String> getFoo(
#RegisteredOAuth2AuthorizedClient("custom") OAuth2AuthorizedClient client) {
OAuth2AccessToken token = client.getAccessToken();
....
}
In order to resolve this, spring look for OAuth token on OAuth2AuthorizedClientService which actually stores the previously authenticated tokens in memory.
Now, I have a scenario where I obtain JWT token from OAuth server outside of this spring resource server, and trying to authenticate this server by passing token using Authorization header.
But when spring trying to resolve OAuth2AuthorizedClient it is looking for token in-memory with the principle of JWT (which will obviously not found since token is not obtained on this server). Hence send new login redirection for new token.
Overall question would be, is it possible to resolve OAuth2AuthorizedClient with the JWT token (obtained ourside of the server) passed in Authorization header?

AWS cognito user pool server side flow with spring boot

I want to implement AWS Cognito server side flow with spring boot. I don't quite understand what the flow should be. Should I use spring oauth along with it ?
Requirement is something like this.
As an admin create user and give access to these created users to use my API from API Gateway (Let's ignore API Gateway part and say we just need access token from cognito for now)
Here is what I think should happen if I use AWS cognito with spring oauth2
user hits localhost:8000/oauth/token - with basic authentication (username and password)
which will do an API call with user credentials. User receives the token and uses it however he/she needs it.
Is this flow secure ? Should I use spring oauth along ?
How to handle respond to auth challenge ? Should user pass new password for first time when calling my application API ?
#RestController
public class Oauth {
#PostMapping(path = "/oauth/token")
public AdminInitiateAuthResult token(#RequestHeader("username") String username, #RequestHeader("password") String password) {
AWSCognitoIdentityProvider provider = AWSCognitoIdentityProviderClientBuilder
.standard()
.withRegion(Regions.US_WEST_2)
.withCredentials(new AWSStaticCredentialsProvider()).build();
Map<String, String> authParams = new HashMap<>();
authParams.put("USERNAME", username);
authParams.put("PASSWORD", password);
AdminInitiateAuthRequest adminInitiateAuthRequest = new AdminInitiateAuthRequest()
.withClientId("{client-id}")
.withUserPoolId("{user-pool-id}")
.withAuthFlow(AuthFlowType.ADMIN_USER_PASSWORD_AUTH)
.withAuthParameters(authParams);
AdminInitiateAuthResult authResult = provider.adminInitiateAuth(adminInitiateAuthRequest);
return authResult.getAuthenticationResult().getIdToken();
}
}
Business requirement is quite simple there needs to be a pool of users (cognito in this case) who can get some kind of a token to access few APIs. I want to achieve this using spring boot, since the API is written using spring boot and also I use AWS Api Gateway
Should I use spring oauth along with it ?
No. Authorization is done by API Gateway.
API clients need to obtain token from Cognito (i.e. authenticate themselves there) before using API. There is no need to do anything on application (Spring) side.
Details are here.
If you want to implement authentication for API clients using Cognito, then see Cognito docs for examples and manuals.
FYI Application Load Balancer can be used to handle all authentication flow for API.

OAuth2 client credentials flow via Spring Boot Keycloak integration

My application consists of:
backend/resource server
UI webapp
keycloak
The UI is talking with the backend server via RESTful API using the keycloak client with authorization code grant flow. This is working fine.
Now, I need the additional possibility to access resource of the backend using a system/service account (with usually more permissions than the user). How would you implement this requirement? I thought the client credentials flow would be useful here.
Is it possible to use the OAuth2 client credentials flow with the keycloak client for Spring Boot? I found examples that used the Spring Security OAuth2 client features to achieve a client credentials flow but that feels weird because I already use the keycloak client for the OAuth thing.
Edit: Solution
Thanks for your answers which helped me a lot. In my UI webapp, I am now able to communicate with the backend either by using the authenticated user OAuth2 token or by using the token from the client credentials flow of my UI service account. Each way has its own RestTemplate, the first is done via the keycloak integration and second is done by Spring Security OAuth2 as explained here.
Yes, you can use OAuth 2.0 Client Credentials flow and Service Accounts.
Keycloak suggest 3 ways to secure SpringBoot REST services:
with Keycloak Spring Boot Adapter
with keycloak Spring Security Adapter
with OAuth2 / OpenID Connect
Here is a good explanation about this with an example in the OAuth2/OIDC way:
Tutorial by Arun B Chandrasekaran
Code sample by Arun B Chandrasekaran
If you follow this example, keep in mind:
Take care to configure your client as:
Access Type: Confidential
Authorization: Enabled
Service Account (OAuth Client Credentials Flow): Enabled
Take care to configure your target service as:
Access Type: Bearer-only
So, caller should be confidential and target service should be bearer-only.
Create your users, roles, mappers... and assign roles to your users.
Check that you have this dependencies in your spring project:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
Configure authentication to be used in the REST client (application.properties)
e.g.:
security.oauth2.client.client-id=employee-service
security.oauth2.client.client-secret=68977d81-c59b-49aa-aada-58da9a43a850
security.oauth2.client.user-authorization-uri=${rest.security.issuer-uri}/protocol/openid-connect/auth
security.oauth2.client.access-token-uri=${rest.security.issuer-uri}/protocol/openid-connect/token
security.oauth2.client.scope=openid
security.oauth2.client.grant-type=client_credentials
Implement your JwtAccessTokenCustomizer and SecurityConfigurer (ResourceServerConfigurerAdapter) like Arun's sample.
And finally implement your service Controller:
#RestController
#RequestMapping("/api/v1/employees")
public class EmployeeRestController {
#GetMapping(path = "/username")
#PreAuthorize("hasAnyAuthority('ROLE_USER')")
public ResponseEntity<String> getAuthorizedUserName() {
return ResponseEntity.ok(SecurityContextUtils.getUserName());
}
#GetMapping(path = "/roles")
#PreAuthorize("hasAnyAuthority('ROLE_USER')")
public ResponseEntity<Set<String>> getAuthorizedUserRoles() {
return ResponseEntity.ok(SecurityContextUtils.getUserRoles());
}
}
For a complete tutorial, please read the referred Arun's tutorial.
Hope it helps.
Following #dmitri-algazin you to implement the workflow you have basically two options:
If you want to cover other IdMs besides Keycloak which solves somehow the Single Responsibility principle, I would use RestTemplate. Below you can find the variables:
//Constants
#Value("${keycloak.url}")
private String keycloakUrl;
#Value("${keycloak.realm}")
private String keycloakRealm;
#Value("${keycloak.client_id}")
private String keycloakClientId;
RestTemplate restTemplate = new RestTemplate();
private static final String BEARER = "BEARER ";
First you need to generate the access token:
#Override
public AccessTokenResponse login(KeycloakUser user) throws NotAuthorizedException {
try {
String uri = keycloakUrl + "/realms/" + keycloakRealm +
"/protocol/openid-connect/token";
String data = "grant_type=password&username="+
user.getUsername()+"&password="+user.getPassword()+"&client_id="+
keycloakClientId;
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/x-www-form-urlencoded");
HttpEntity<String> entity = new HttpEntity<String>(data, headers);
ResponseEntity<AccessTokenResponse> response = restTemplate.exchange(uri,
HttpMethod.POST, entity, AccessTokenResponse.class);
if (response.getStatusCode().value() != HttpStatus.SC_OK) {
log.error("Unauthorised access to protected resource", response.getStatusCode().value());
throw new NotAuthorizedException("Unauthorised access to protected resource");
}
return response.getBody();
} catch (Exception ex) {
log.error("Unauthorised access to protected resource", ex);
throw new NotAuthorizedException("Unauthorised access to protected resource");
}
}
And then with the token you can retrieve information from the users:
#Override
public String user(String authToken) throws NotAuthorizedException {
if (! authToken.toUpperCase().startsWith(BEARER)) {
throw new NotAuthorizedException("Invalid OAuth Header. Missing Bearer prefix");
}
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", authToken);
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<AccessToken> response = restTemplate.exchange(
keycloakUrl + "/realms/" + keycloakRealm + "/protocol/openid-connect/userinfo",
HttpMethod.POST,
entity,
AccessToken.class);
if (response.getStatusCode().value() != HttpStatus.SC_OK) {
log.error("OAuth2 Authentication failure. "
+ "Invalid OAuth Token supplied in Authorization Header on Request. Code {}", response.getStatusCode().value());
throw new NotAuthorizedException("OAuth2 Authentication failure. "
+ "Invalid OAuth Token supplied in Authorization Header on Request.");
}
log.debug("User info: {}", response.getBody().getPreferredUsername());
return response.getBody().getPreferredUsername();
}
You can substitute this URL by the one provided by #dimitri-algazin to retrieve all the users information.
It is possible to use the Keycloak dependencies:
<!-- keycloak -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId>
<version>3.4.3.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
<version>3.1.4.Final</version>
</dependency>
And use the classes to generate the token:
Keycloak keycloak = KeycloakBuilder
.builder()
.serverUrl(keycloakUrl)
.realm(keycloakRealm)
.username(user.getUsername())
.password(user.getPassword())
.clientId(keycloakClientId)
.resteasyClient(new ResteasyClientBuilder().connectionPoolSize(10).build())
.build();
return keycloak.tokenManager().getAccessToken();
The examples are extracted from here. We also uploaded the image to Docker Hub to facilitate the interaction with Keycloak. For this reason we started with option 2). Right now we are in the process to cover other IdMs and we went for option 1) in order to avoid including extra dependencies. Conclusion:
I would go for option 2 if you stick to Keycloak because classes include extra functionalities for Keycloak tool.
I would go for option 1 for further coverage and other OAuth 2.0 tools.
We had similar requirement, get user e-mail by user uuid.
Create service user, make sure user has role "realm-management"->"view-users" (might be query-users as well)
Process is simple: do login to keycloak with service user (keep password and/or user name encoded in the properties file), make request to keycloak with accessToken in authorisation header to
GET http://{yourdomainadress}/auth/admin/realms/{yourrealmname}/users/{userId}
A way to login to keycloak using REST API:
POST http://{yourdomainadress}/auth/realms/{yourrealmname}/protocol/openid-connect/token
Headers:
Content-Type: application/x-www-form-urlencoded
Body x-www-form-urlencoded:
client_id: your-client
username: user-you-are-using
password: password-for-user
grant_type: password
client_secret: 11112222-3333-4444-5555-666666666666 (client secret is required if client "Access Type"="confidential")
Shortly: make sure your service user has right role assigned to do operation,
do login, do query keycloak (check docs to get right query url and params, always challenging)

Spring security Oauth client - Request like a postman example

I am trying to do a JUnit Tests that executes an OAuth flow.
My customer built a OAuth provider, when I make a test using postman, the postman show me a screen to fill down the credentials, after that, the postman store the information (access_token, id_token, all JWT informations), it is ok.
See the example:
My code to test is:
#Test
public void getAccessTokenViaSpringSecurityOAuthClient() {
try {
OAuth2ProtectedResourceDetails resourceDetails = googleOAuth2Details();
OAuth2RestTemplate oAuthRestTemplate = new OAuth2RestTemplate(resourceDetails);
org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
OAuth2AccessToken token = oAuthRestTemplate.getAccessToken();
System.out.println(oAuthRestTemplate.getResource());
System.out.println(oAuthRestTemplate.getOAuth2ClientContext());
System.out.println(token);
Assert.assertTrue(token != null);
} catch (Exception e) {
e.printStackTrace();
}
}
public OAuth2ProtectedResourceDetails googleOAuth2Details() {
AuthorizationCodeResourceDetails googleOAuth2Details = new AuthorizationCodeResourceDetails();
googleOAuth2Details.setClientId("xxxxx");
googleOAuth2Details.setUserAuthorizationUri("https://xxx/yyy/oauth2/authorize");
googleOAuth2Details.setAccessTokenUri("https://xxx/yyy/oauth2/token");
googleOAuth2Details.setScope(Arrays.asList("openid"));
googleOAuth2Details.setPreEstablishedRedirectUri("https://www.getpostman.com/oauth2/callback");
googleOAuth2Details.setAuthenticationScheme(AuthenticationScheme.query);
googleOAuth2Details.setClientAuthenticationScheme(AuthenticationScheme.form);
return googleOAuth2Details;
}
When I run the task, this exception happens:
org.springframework.security.oauth2.client.resource.UserRedirectRequiredException: A redirect is required to get the users approval
Is it possible to test this flow?
How can I do it?
The exception explains the problem : A redirect is required to get the users approval.
Postman is hiding the fact that once the authorization process is over, the authorization server redirects your browser to a redirect_uri, or Callback URL as Postman names it. This URL collects the authorization code delivered by the authorization server, and requests a token.
See the authorization code flow for more details :
OAuth 2.0 RFC 6749
a simplfied diagram
This means that you cannot "unit test" the authorization code grant of an authorization server. You need some kind of web server to process the callback of the authorization code flow.
You could probably test your authorization a lot faster by creating a #SpringBootTest with a Spring Boot application using #EnableOAuth2Sso. Spring Boot will auto-configure an OAuth2RestTemplate for you, and you can have it #Autowired in your JUnit test.

Spring Boot OAuth2 Single Sign Off (Logout)

I'm considering to use OAuth2 for my application. The architecture I'm trying to implement is as follows:
I will have my own (and only this) Authorization Server
Some Resource Apps validating access to their resources using the Authorization Server
Some client apps (web, mobile) which will redirect the user to the Authorization Server for authentication and on success will consume the api's on the Resource Apps.
So far I have managed to implement this interaction between 3 basic apps (1 auth server, 1 resource server and 1 client). The thing I don't get working is the logout functionality. I have read of the "notoriously tricky problem" that Dave Syer describes in his tutorial, but in this case I really need the user to re-login after loging out. I have tried giving few seconds to the access token and the refresh token, but instead of being prompted to login again when the expiration arrives, I'm getting a NPE on the client app. I have also tried the solutions proposed in this post to remove the token from the token store, but it doesn't work. The single sign off is for me the desirable behaviour for this implementation. How can I achieve this using Spring Boot Oauth2. If it is not possible for some reason, which alternatives I could use to implement a centralized security using Spring Boot?
Thanks in advance.
After a lot of tests I have realized that this can be solved just with a redirect to the AuthServer and doing logout programmatically like this:
In the client app (WebSecurityConfigurerAdapter):
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.logout()
.logoutSuccessUrl("http://your-auth-server/exit");
}
In the authorization server:
#Controller
public class LogoutController {
#RequestMapping("/exit")
public void exit(HttpServletRequest request, HttpServletResponse response) {
// token can be revoked here if needed
new SecurityContextLogoutHandler().logout(request, null, null);
try {
//sending back to client app
response.sendRedirect(request.getHeader("referer"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
I have posted a sample app on github with a full example of this implementation.

Categories