I have been struggling while trying to create a sample client that uses the OAuth 2.0 Authorization Code Grant flow.
I am able to successfully use the Client Credentials flow but when I try to use the Authorization Code flow I do not get redirected to the correct uri.
When calling the OAuth2RestTmplate.exchange method, I get a redirect exception in the RestTemplate.doExecute(...) method. It is thrown from the finally clause. The response is null, but the if is not stopping it.
finally {
if (response != null) {
response.close();
}
I still get prompted for login and authorization, but am not directed to the response containing the data. I am just redirected back to the client home page. The same call from Postman using the authorization code flow with the same client credentials is successful so I know the client registration is correct.
I could use another pair of eyes to see what I'm missing. Thanks in Advance! Below are my code excerpts.
Working client using oauth2 client credentials flow:
Client App:
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class})
public class ClientExampleClientCredentials extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(ClientExampleClientCredentials.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(ClientExampleClientCredentials.class);
}
}
Controller:
@RestController
public class HomeController {
@Value("${security.oauth2.client.clientId}")
private String clientId;
@Value("${security.oauth2.client.clientSecret}")
private String clientSecret;
@Value("${security.oauth2.client.apiUrl}")
private String apiUrl;
@Value("${security.oauth2.client.scope}")
private List<String> scopes;
@Value("${security.oauth2.client.accessTokenUri}")
private String accessTokenUri;
/**
* Example of using the OAuth2RestTemplate to access external resources
*
* The OAuth2RestTemplate takes care of exchanging credentials with the auth server, as well as adding the
* bearer token to each request to the FHIR services.
*
*/
@RequestMapping("/ex-1")
public String retrievePatientByIdUsingRestTemplate(@RequestParam String id) {
OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(getClientCredentialsResourceDetails(), new DefaultOAuth2ClientContext());
ResponseEntity<String> response = oAuth2RestTemplate.exchange(apiUrl + "/Patient/" + id, HttpMethod.GET, null, String.class);
String responseBody = response.getBody();
return responseBody;
}
private ClientCredentialsResourceDetails getClientCredentialsResourceDetails() {
ClientCredentialsResourceDetails clientCredentialsResourceDetails = new ClientCredentialsResourceDetails();
clientCredentialsResourceDetails.setAccessTokenUri(accessTokenUri);
clientCredentialsResourceDetails.setAuthenticationScheme(AuthenticationScheme.header);
clientCredentialsResourceDetails.setClientId(clientId);
clientCredentialsResourceDetails.setClientSecret(clientSecret);
clientCredentialsResourceDetails.setScope(scopes);
return clientCredentialsResourceDetails;
}
}
application.yml
security:
oauth2:
client:
clientId: client_id
clientSecret: secret
apiUrl: http://localhost:8080/testData/data
accessTokenUri: http://localhost:8080/test-auth/token
scope: system/*.read
This works great authenticating me and then redirecting to my service url. However, The Authorization Code flow is not working.
Broken client using oauth2 authorization code flow:
Client App:
@SpringBootApplication (exclude = {SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class})
public class ClientExampleAccessToken extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(ClientExampleAccessToken.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(ClientExampleAccessToken.class);
}
}
Controller:
package org.ihc.clinical.controller;
@Configuration
@EnableOAuth2Client
@RestController
public class HomeController {
@Value("${security.oauth2.client.clientId}")
private String clientId;
@Value("${security.oauth2.client.clientSecret}")
private String clientSecret;
@Value("${security.oauth2.client.accessTokenUri}")
private String accessTokenUri;
@Value(("${security.oauth2.client.userAuthorizationUri}"))
private String userAuthorizationUri;
@Value("${security.oauth2.client.apiUrl}")
private String apiUrl;
@Value("${security.oauth2.client.redirectUri}")
private String redirectUri;
@RequestMapping("/ex-1")
public String retrievePatientByIdUsingRestTemplate(@RequestParam String empi) {
OAuth2ProtectedResourceDetails resource = resource();
String path = apiUrl + "/Patient/" + empi;
OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resource, new DefaultOAuth2ClientContext());
***/*error occurs here in RestTemplate.doExcute. error:org.springframework.security.oauth2.client.resource.UserRedirectRequiredException:
A redirect is required to get the users approval */***
ResponseEntity<String> response = oAuth2RestTemplate.exchange(path, HttpMethod.GET, null, String.class);
//AuthorizationCodeAccessTokenProvider provider = new //AuthorizationCodeAccessTokenProvider();
//Token Request
//AccessTokenRequest request = new DefaultAccessTokenRequest();
//String code = provider.obtainAuthorizationCode(resource, request);
//request.setAuthorizationCode(code);
//OAuth2AccessToken oAuth2AccessToken = //provider.obtainAccessToken(resource, request);
//Token Response
//String tokenValue = oAuth2AccessToken.getValue();
//return tokenValue;
}
//Call when ready to send token Request
private OAuth2ProtectedResourceDetails resource() {
AuthorizationCodeResourceDetails authorizationCodeResourceDetails = new AuthorizationCodeResourceDetails();
authorizationCodeResourceDetails.setClientId(clientId);
authorizationCodeResourceDetails.setClientSecret(clientSecret);
authorizationCodeResourceDetails.setAccessTokenUri(accessTokenUri);
authorizationCodeResourceDetails.setUserAuthorizationUri(userAuthorizationUri);
//authorizationCodeResourceDetails.setScope(scopes);
authorizationCodeResourceDetails.setPreEstablishedRedirectUri(redirectUri);
return authorizationCodeResourceDetails;
}
}
application.yml
security:
oauth2:
client:
clientId: clientid
clientSecret: secret
accessTokenUri: http://localhost:8080/test-auth/token
userAuthorizationUri: http://localhost:8080/test-auth/authorize
apiUrl: http://localhost:8080/test-fhir-cdr/data
redirectUri: http://localhost:8080/test-examples-access-token
I finally found the solution here: https://projects.spring.io/spring-security-oauth/docs/oauth2.html
I needed to add the below code to the controller:
@Autowired
private OAuth2ClientContext oauth2Context;
@Bean
public OAuth2RestTemplate getOauth2RestTemplate() {
return new OAuth2RestTemplate(resource(), oauth2Context);
}
Then call getOauth2RestTemplate() like below:
@RequestMapping("/ex-1")
public String retrievePatientByIdUsingRestTemplate(@RequestParam String empi) {
String path = apiUrl + "/Patient/" + empi;
OAuth2RestTemplate oAuth2RestTemplate = getOauth2RestTemplate();
ResponseEntity<String> response = oAuth2RestTemplate.exchange(path, HttpMethod.GET, null, String.class);
return response.getBody();
}