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.
Related
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
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)
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>
I am attempting to programmatically retrieve an OAuth Access Token back from my OAuth authorization server. I am able to retrieve a token using the following steps:
Direct browser to http://localhost:8080/oauth/authorize?client_id=clientId&response_type=code&redirect_uri=http://localhost:10000/client/landing This endpoint requires authentication to access, so the browser is redirected to a login form, where I enter a user name & password that are validated by a custom user details service I wrote.
Browser is now on a page that verifies what scopes I want to give the client access to for my protected resources. I make my selection and hit the authorize button.
Browser is now redirected to the provided redirect uri with the authorization code as a query parameter. (Ex. http://localhost:10000/client/landing?code=J23Dxx)
I then take the code parameter and use it in a POST (in postman) to the /oauth/token endpoint. (Ex. localhost:8080/oauth/token?grant_type=authorization_code&username=clientId&password=clientSecret&code=J23Dxx&redirect_uri=http://localhost:10000/client/landing)
This returns an access token, and all is good.
The issue arises when try to access the token using a OAuth2RestTemplate from my client app. Here is my client config class:
#Configuration
#EnableOAuth2Client
public class ClientConfig {
#Value("${app.clientId}")
private String clientId;
#Value("${app.clientSecret}")
private String clientSecret;
#Value("${app.resourceId}")
private String resourceId;
#Value("${app.clientAuthUri}")
private String clientAuthUri;
#Value("${app.tokenUri}")
private String tokenUri;
#Value("${app.directUri}")
private String redirectUri;
#Bean
public OAuth2ProtectedResourceDetails getResourceDetails() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setId(resourceId);
details.setClientId(clientId);
details.setClientSecret(clientSecret);
details.setScope(Arrays.asList("read", "write"));
details.setUserAuthorizationUri(clientAuthUri);
details.setAccessTokenUri(tokenUri);
details.setPreEstablishedRedirectUri(redirectUri);
details.setUseCurrentUri(false);
return details;
}
#Autowired
private OAuth2ClientContext oAuth2ClientContext;
#Bean
public OAuth2RestOperations oAuthTemplate(OAuth2ClientContext clientContext) {
OAuth2RestTemplate template = new OAuth2RestTemplate(getResourceDetails(), oAuth2ClientContext);
return template;
}
}
Then I attempt to get an access token using the OAuth rest template:
#Service
public class OAuthService {
#Autowired
private OAuth2RestOperations oauthTemplate;
public OAuth2AccessToken getToken() {
return oauthTemplate.getAccessToken();
}
}
Running the getToken() method results in an InsufficientAuthenticationException, which makes sense, because I have not entered a username & password to access the oauth/authorize endpoint. But since I am using an authorization code flow, setting a username & password on the resource details is not an option. I have tried getting a token after going to the oauth/authorize endpoint in the same manner, but in that case I get an error about a possible CSRF attack, which I believe is due to the authorization server expecting a state parameter on the token request. Since the oauth/authorize endpoint was hit with the browser and the token endpoint hit from the OAuth2RestTemplate, no state is preserved from the authorization.
How do I utilize the OAuth2RestTemplate to access my token, or are there more fundamental problems with my design which are preventing this from working?
I am surprised that I am not finding more information about this. Am I wrong in believing that requiring authentication for the authorize & token endpoints is recommended practice? What is the standard way to utilize the OAuth2RestTemplate in a case where authentication is necessary before the resource details would be considered?
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.