OAuth2 client credentials flow via Spring Boot Keycloak integration - java

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)

Related

how to generate api-key for postman in rest controller spring boot

I have this spring boot build. I want to consume rest controller #GetMapping API.
I know the credentials. I can open its dashboard using login page.
But I am not able to consume Rest apis from postman due authorization setting.
Below is the REST api that I want to consume in postman client
#GetMapping("/students")
#ApiOperation(value = "", authorizations = {#Authorization(value = "apiKey")})
public Response getAllStudents() {
return Response
.ok()
.setPayload(studentService.getStudents());
}
As I mentioned I know the username/password. How can I generate this apiKey field and configure in my postman window.
In postman go to Authorization > API key. enter your credentials and it should work

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.

Jhipster UAA External Client

I use jhipster UAA and Gatewy App in my server running in EC2.
(https://www.jhipster.tech/images/microservices_architecture_detail.002.png)
I have application running external cloud to put data in this server, It use REST endpoint to make request.
I need turn on security in this endpoint, then use access token or client secret.
I turned on the security, and copy my secret token by FrontEnd application. My first request work, but the second request don't work.
My OAuth2RestTemplate.
#Bean
public OAuth2RestTemplate redditRestTemplate(OAuth2ProtectedResourceDetails resourceDetails, OAuth2ClientContext clientContext) {
clientContext.setAccessToken(new DefaultOAuth2AccessToken(applicationProperties.getAccessToken()));
OAuth2RestTemplate template = new OAuth2RestTemplate(resourceDetails, clientContext);
AccessTokenProvider accessTokenProvider = new AccessTokenProviderChain(
Arrays.<AccessTokenProvider>asList(
new ImplicitAccessTokenProvider(),
new ResourceOwnerPasswordAccessTokenProvider(),
new ClientCredentialsAccessTokenProvider())
);
template.setAccessTokenProvider(accessTokenProvider);
return template;
}
My request.
OAuth2RestTemplate b = a.getBean(OAuth2RestTemplate.class);
String c = b.getForEntity("https://cloud.com/api/app/v1/events", String.class).getBody();
First request Ok.
I need save the new accessToken?
Is there any way to do this integration using "client secret" or anything?
After making the authentication request /auth/login you have to save the accessToken received in the response. Subsequent requests must contain this access token in the header Authorization: Bearer <accessToken>

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.

Oauth2 Client in Spring security

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

Categories