Convert Spring Boot and OAuth2 client from using Client Credentials flow to Authorization Code Grant flow - spring-boot

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

Related

Why did secure Spring WS integration return Unauthorized [401]?

I'm trying to consume a secure third-party SOAP web service in Spring boot, and I'm following Spring WS plus an online resource describing secure integration Spring WS - HTTPS Client-Server Example. After locating the certificate .cer and .jks files under resources folder, and used this code to wrap my configuration:
#Configuration
public class WSGeneralConfig {
#Autowired
#Qualifier("WifiTouristConfig")
CredentailsConfig wifiTouristConfig;
#Autowired
#Qualifier("TradeinWSConfig")
CredentailsConfig tradeinWSConfig;
#Autowired
ClientInterceptor[] interceptors;
#Bean
public WifiToursitWrapper wifiToursitWrapper(#Value("${tibco.wifi.tourist.url}") String uri) throws Exception {
WifiToursitWrapper wifiToursitWrapper = new WifiToursitWrapper();
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("com.usp.tibco.wifi");
wifiToursitWrapper.setMarshaller(marshaller);
wifiToursitWrapper.setUnmarshaller(marshaller);
wifiToursitWrapper.setDefaultUri(uri);
wifiToursitWrapper.setInterceptors(interceptors);
wifiToursitWrapper.setMessageSender(
httpComponentsMessageSender(wifiTouristConfig.getUsername(), wifiTouristConfig.getPassword()));
return wifiToursitWrapper;
}
public HttpComponentsMessageSender httpComponentsMessageSender(String userName, String password) throws Exception {
HttpComponentsMessageSender httpComponentsMessageSender = new HttpComponentsMessageSender();
httpComponentsMessageSender.setCredentials(new UsernamePasswordCredentials(userName, password));
httpComponentsMessageSender.setHttpClient(httpClient());
httpComponentsMessageSender.setAuthScope(AuthScope.ANY);
httpComponentsMessageSender.afterPropertiesSet();
return httpComponentsMessageSender;
}
public HttpClient httpClient() throws Exception {
return HttpClientBuilder
.create()
.setSSLSocketFactory(new SSLConnectionSocketFactory(sslContext(), NoopHostnameVerifier.INSTANCE))
.addInterceptorFirst(new RemoveSoapHeadersInterceptor()).build();
}
public SSLContext sslContext() throws Exception {
File file = new ClassPathResource("cert/cacerts.jks").getFile();
return SSLContextBuilder.create().loadTrustMaterial(file, "jksPassword".toCharArray()).build();
}
}
The client class also like this
public class WifiToursitWrapper extends WebServiceGatewaySupport {
#SoapOperationName(systemName = SystemName.TIBCO, operationName = "RegisterByIntlNo")
public RegisterByIntlNoResponse registerByIntlNo(RegisterByIntlNoRequest request) {
return (RegisterByIntlNoResponse) getWebServiceTemplate().marshalSendAndReceive(request,
message -> ((SoapMessage) message)
.setSoapAction("/RegisterByIntlNo"));
}
}
Unfortunately I got Unauthorized exception, and I don't know what the root cause of that? here is a snapshot from Exception stack trace
org.springframework.ws.client.WebServiceTransportException: Unauthorized [401]
at org.springframework.ws.client.core.WebServiceTemplate.handleError(WebServiceTemplate.java:699)
at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:609)
at org.springframework.ws.client.core.WebServiceTemplate.sendAndReceive(WebServiceTemplate.java:555)
at org.springframework.ws.client.core.WebServiceTemplate.marshalSendAndReceive(WebServiceTemplate.java:390)
at org.springframework.ws.client.core.WebServiceTemplate.marshalSendAndReceive(WebServiceTemplate.java:383)
at com.etisalat.account.wrapper.WifiToursitWrapper.registerByIntlNo(WifiToursitWrapper.java:25)
I solved the issue by adding Base Authentication to my request, with a good help from this question's answer Consuming SOAP WS returns Error 401 unauthorized , I changed some little code for my case.
private HttpComponentsMessageSender httpComponentsMessageSender(String userName, String password) throws Exception {
HttpComponentsMessageSender httpComponentsMessageSender = new HttpComponentsMessageSender();
httpComponentsMessageSender.setHttpClient(httpClient(userName, password));
return httpComponentsMessageSender;
}
private HttpClient httpClient(String username, String password) throws Exception {
List<Header> headers = new ArrayList<>();
BasicHeader authHeader = new BasicHeader("Authorization", "Basic " + base64authUserPassword(username, password));
headers.add(authHeader);
RequestDefaultHeaders reqHeader = new RequestDefaultHeaders(headers);
return HttpClientBuilder.create()
.setSSLSocketFactory(new SSLConnectionSocketFactory(sslContext(), NoopHostnameVerifier.INSTANCE))
.addInterceptorFirst(new RemoveSoapHeadersInterceptor()).addInterceptorLast(reqHeader).build();
}
private String base64authUserPassword(String username, String password) {
String userpassword = username + ":" + password;
String encodedAuthorization = new String(Base64.getEncoder().encode(userpassword.getBytes()));
return encodedAuthorization;
}

Spring boot Oauth2 : Token relay from a client using Feign, Ribbon, Zull and Eureka to a ressource

I have an oauth2 client that get a token from an authorization server successfully. (not always has been the case but now it is... :))
The client, the zuul gateway and the resource server are all registered in Eureka.
My client use a Proxy to access to a remote ressource service named microservice-files.
#RestController
#FeignClient(name = "zuul-server")
#RibbonClient(name = "microservice-files")
public interface ProxyMicroserviceFiles {
#GetMapping(value = "microservice-files/root")
FileBean getUserRoot();
}
So I'd like to relay the token to Zull and then to the resource server.
I can relay the token this way to contact Zuul and apparently the load balancing is managed too (I've just test I didn't know and it's great) also zuul can relay the token, but it's not very convenient I'd prefer the previous approach.
#EnableConfigurationProperties
#SpringBootApplication
#EnableFeignClients("com.clientui")
public class ClientUiApplication {
#Bean
public OAuth2RestOperations restOperations(
OAuth2ProtectedResourceDetails resource,
OAuth2ClientContext context) {
return new OAuth2RestTemplate(resource, context);
}
public static void main(String[] args) {
SpringApplication.run(ClientUiApplication.class, args);
}
}
here is the test controler
#Controller
public class ClientController {
#Autowired
private RestOperations restOperations;
#RequestMapping("/root")
public ResponseEntity userRootTest() {
String rootUrl = "http://localhost:9004/microservice-files/root";
return restOperations.getForEntity(rootUrl,FileBean.class);
}
}
If I correctly understand your problem then you can use a RequestInterceptor to add a token in each request by the feign. In order to do it you can use the next configuration:
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor(OAuth2ClientContext oauth2ClientContext,
OAuth2ProtectedResourceDetails resource) {
return new OAuth2FeignRequestInterceptor(oauth2ClientContext, resource);
}
#Bean
protected OAuth2ProtectedResourceDetails resource() {
AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails();
resource.setAccessTokenUri("http://127.0.0.1:9000/auth/login");
resource.setUserAuthorizationUri("http://127.0.0.1:9000/auth/authorize");
resource.setClientId("my-client");
resource.setClientSecret("my-secret");
return resource;
}
This is what I did to make it work.
#Bean(name = "oauth2RestTemplate")
#LoadBalanced
public OAuth2RestTemplate restTemplate(SpringClientFactory clientFactory) {
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails());
RibbonLoadBalancerClient ribbonLoadBalancerClient = new RibbonLoadBalancerClient(clientFactory);
LoadBalancerInterceptor loadBalancerInterceptor = new LoadBalancerInterceptor(ribbonLoadBalancerClient);
ClientCredentialsAccessTokenProvider accessTokenProvider = new ClientCredentialsAccessTokenProvider();
accessTokenProvider.setInterceptors(Arrays.asList(loadBalancerInterceptor));
restTemplate.setAccessTokenProvider(accessTokenProvider);
return restTemplate;
}
public ClientCredentialsResourceDetails resourceDetails() {
ClientCredentialsResourceDetails clientCredentialsResourceDetails = new ClientCredentialsResourceDetails();
clientCredentialsResourceDetails.setId("1");
clientCredentialsResourceDetails.setClientId("my-ms");
clientCredentialsResourceDetails.setClientSecret("123");
clientCredentialsResourceDetails.setAccessTokenUri("http://oauth-server/oauth/token");
clientCredentialsResourceDetails.setScope(Arrays.asList("read"));
clientCredentialsResourceDetails.setGrantType("client_credentials");
return clientCredentialsResourceDetails;
}

Configure Spring Zuul to do an OAuth2 "resource owner password grant" for requests with a Basic Authentication header

I have created a very basic Spring Cloud Zuul application to act as a token relay to a resource server. This is extremely simple to do for authorization_code grant. The below does exactly what I need:
package zuul;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
#SpringBootApplication
#EnableOAuth2Sso
#EnableZuulProxy
public class RelayProxy {
public static void main(String[] args) {
SpringApplication.run(RelayProxy.class, args);
}
}
And in the application.yml (my authorization server runs on port 9999):
server:
port: 8000
security:
oauth2:
client:
accessTokenUri: http://localhost:9999/oauth/token
userAuthorizationUri: http://localhost:9999/oauth/authorize
clientAuthenticationScheme: form
clientId: client
clientSecret: secret
resource:
jwt:
key-uri: http://localhost:9999/oauth/token_key
zuul:
routes:
resource:
path: /resource/**
url: http://localhost:8002/
To allow a legacy system that only supports Basic Authentication to get access to the resource server, I would like a request with a basic authentication header to be intercepted and a password grant performed to obtain and relay the access token. I have tried to configure this using Spring Security / OAuth2 configuration, but was unable to get it to work. The zuul filter below is what I am currently using and it works fine, but I would like to know if it is possible to achieve the same result through the correct Spring configuration:
public class PasswordGrantZuulFilter extends ZuulFilter {
#Value("${security.oauth2.client.accessTokenUri}")
private String accessTokenUri;
#Value("${security.oauth2.client.clientId}")
private String clientId;
#Value("${security.oauth2.client.clientSecret}")
private String clientSecret;
#Autowired
private OAuth2ClientContext clientContext;
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 10;
}
#Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
String header = ctx.getRequest().getHeader(HttpHeaders.AUTHORIZATION);
return header != null && header.toLowerCase().startsWith("basic");
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
if (clientContext.getAccessToken() == null) {
String header = ctx.getRequest().getHeader(HttpHeaders.AUTHORIZATION);
String base64Credentials = header.substring("Basic".length()).trim();
String credentials = new String(Base64.getDecoder().decode(base64Credentials), Charset.forName("UTF-8"));
final String[] values = credentials.split(":", 2);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.put(HttpHeaders.AUTHORIZATION, Collections.singletonList("Basic " + Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes())));
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", "password");
map.add("username", values[0]);
map.add("password", values[1]);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
ResponseEntity<OAuth2AccessToken> response = new RestTemplate().postForEntity(accessTokenUri, request, OAuth2AccessToken.class);
clientContext.setAccessToken(response.getBody());
}
ctx.addZuulRequestHeader(HttpHeaders.AUTHORIZATION, OAuth2AccessToken.BEARER_TYPE + " " + clientContext.getAccessToken().getValue());
return null;
}
}

Spring Boot OAuth2 manually create new JWT token

At my Spring Boot application I have configured Spring OAuth2 server with JWT tokens.
Also I have added Spring Social configuration in order to be able authenticate users via various Social Networks like Twitter, Facebook and so on.
This is my SpringSocial config:
#Configuration
#EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {
#Bean
public ProviderSignInController providerSignInController(ConnectionFactoryLocator connectionFactoryLocator, UsersConnectionRepository usersConnectionRepository) {
return new ProviderSignInController(connectionFactoryLocator, usersConnectionRepository, new SimpleSignInAdapter(authTokenServices, "client_id", userService));
}
...
}
Also, based on the folksinging answer Integrate Spring Security OAuth2 and Spring Social I have implemented SimpleSignInAdapter in order to handle successful authentication with 3rdparty Social Networks:
public class SimpleSignInAdapter implements SignInAdapter {
final static Logger logger = LoggerFactory.getLogger(SimpleSignInAdapter.class);
public static final String REDIRECT_PATH_BASE = "/#/login";
public static final String FIELD_TOKEN = "access_token";
public static final String FIELD_EXPIRATION_SECS = "expires_in";
private final AuthorizationServerTokenServices authTokenServices;
private final String localClientId;
private final UserService userService;
public SimpleSignInAdapter(AuthorizationServerTokenServices authTokenServices, String localClientId, UserService userService){
this.authTokenServices = authTokenServices;
this.localClientId = localClientId;
this.userService = userService;
}
#Override
public String signIn(String userId, Connection<?> connection, NativeWebRequest request) {
UserDetails userDetails = loadUserById(Long.parseLong(userId));
OAuth2AccessToken oauth2Token = authTokenServices.createAccessToken(convertAuthentication(userDetails));
String redirectUrl = new StringBuilder(REDIRECT_PATH_BASE)
.append("?").append(FIELD_TOKEN).append("=")
.append(encode(oauth2Token.getValue()))
.append("&").append(FIELD_EXPIRATION_SECS).append("=")
.append(oauth2Token.getExpiresIn())
.toString();
return redirectUrl;
}
private OAuth2Authentication convertAuthentication(UserDetails userDetails) {
OAuth2Request request = new OAuth2Request(null, localClientId, null, true, null, null, null, null, null);
return new OAuth2Authentication(request, new UsernamePasswordAuthenticationToken(userDetails, "N/A", userDetails.getAuthorities()));
}
private String encode(String in) {
String res = in;
try {
res = UriUtils.encode(in, "UTF-8");
} catch(UnsupportedEncodingException e){
logger.error("ERROR: unsupported encoding: " + "UTF-8", e);
}
return res;
}
public UserDetails loadUserById(Long id) throws UsernameNotFoundException {
User user = userService.findUserById(id);
if (user == null) {
throw new UsernameNotFoundException("User " + id + " not found.");
}
Set<Permission> permissions = userService.getUserPermissions(user);
return new DBUserDetails(user, permissions);
}
}
Everything works fine except one thing - the following line of code produces plain OAuth2 access token:
OAuth2AccessToken oauth2Token = authTokenServices.createAccessToken(convertAuthentication(userDetails));
but I need to create JWT token instead.
How to create or convert this token to JWT based ? I suppose I can use JwtAccessTokenConverter class for this purpose but don't know how at this moment.
This worked for me, after i wanted my own Custom JWT token.
DefaultTokenServices service = new DefaultTokenServices();
service.setTokenStore(jwtAccessTokenConverter);
service.setTokenEnhancer(jwtAccessTokenConverter);
OAuth2AccessToken token = service.createAccessToken(authentication);
Autowire the jwtAccessTokenConverter
#Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
After debugging I have found a solution:
private final TokenEnhancer tokenEnhancer;
...
OAuth2Authentication authentication = convertAuthentication(userDetails);
OAuth2AccessToken accessToken = authTokenServices.createAccessToken(authentication);
accessToken = tokenEnhancer.enhance(accessToken, authentication);

How to use OAuth2RestTemplate?

I'm trying to understand how to use a OAuth2RestTemplate object to consume my OAuth2 secured REST service (which is running under a different project and let's assume also on a different server etc...)
An example of my REST service is:
http://localhost:8082/app/helloworld
-> Accessing this URL generates an error as I am not authenticated
To request a token I would go to:
http://localhost:8082/app/oauth/token?grant_type=password&client_id=restapp&client_secret=restapp&username=**USERNAME**&password=**PASSWORD**
After I receive the token I can then connect to the REST API by using the following URL (example token inserted)
http://localhost:8082/app/helloworld/?access_token=**4855f557-c6ee-43b7-8617-c24591965206**
Now my question is how do I implement a second application which can consume this OAuth2 secured REST API? I really haven't found any working examples where you provide the user name and password (e.g. coming from a login form) and then a token is generated which can be re-used to get data from the REST API.
I currently tried something with the following objects:
BaseOAuth2ProtectedResourceDetails baseOAuth2ProtectedResourceDetails = new BaseOAuth2ProtectedResourceDetails();
baseOAuth2ProtectedResourceDetails.setClientId("restapp");
baseOAuth2ProtectedResourceDetails.setClientSecret("restapp");
baseOAuth2ProtectedResourceDetails.setGrantType("password");
// how to set user name and password ???
DefaultAccessTokenRequest accessTokenRequest = new DefaultAccessTokenRequest();
OAuth2ClientContext oAuth2ClientContext = new DefaultOAuth2ClientContext(accessTokenRequest());
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(baseOAuth2ProtectedResourceDetails,oAuth2ClientContext);
But this just isn't working :(
Any ideas, links to working examples or tutorials are greatly appreciated.
You can find examples for writing OAuth clients here:
https://github.com/spring-projects/spring-security-oauth
In your case you can't just use default or base classes for everything, you have a multiple classes Implementing OAuth2ProtectedResourceDetails. The configuration depends of how you configured your OAuth service but assuming from your curl connections I would recommend:
#EnableOAuth2Client
#Configuration
class MyConfig{
#Value("${oauth.resource:http://localhost:8082}")
private String baseUrl;
#Value("${oauth.authorize:http://localhost:8082/oauth/authorize}")
private String authorizeUrl;
#Value("${oauth.token:http://localhost:8082/oauth/token}")
private String tokenUrl;
#Bean
protected OAuth2ProtectedResourceDetails resource() {
ResourceOwnerPasswordResourceDetails resource;
resource = new ResourceOwnerPasswordResourceDetails();
List scopes = new ArrayList<String>(2);
scopes.add("write");
scopes.add("read");
resource.setAccessTokenUri(tokenUrl);
resource.setClientId("restapp");
resource.setClientSecret("restapp");
resource.setGrantType("password");
resource.setScope(scopes);
resource.setUsername("**USERNAME**");
resource.setPassword("**PASSWORD**");
return resource;
}
#Bean
public OAuth2RestOperations restTemplate() {
AccessTokenRequest atr = new DefaultAccessTokenRequest();
return new OAuth2RestTemplate(resource(), new DefaultOAuth2ClientContext(atr));
}
}
#Service
#SuppressWarnings("unchecked")
class MyService {
#Autowired
private OAuth2RestOperations restTemplate;
public MyService() {
restTemplate.getAccessToken();
}
}
Do not forget about #EnableOAuth2Client on your config class, also I would suggest to try that the urls you are using are working with curl first, also try to trace it with the debugger because lot of exceptions are just consumed and never printed out due security reasons, so it gets little hard to find where the issue is. You should use logger with debug enabled set.
Good luck
I uploaded sample springboot app on github https://github.com/mariubog/oauth-client-sample
to depict your situation because I could not find any samples for your scenario .
In the answer from #mariubog (https://stackoverflow.com/a/27882337/1279002) I was using password grant types too as in the example but needed to set the client authentication scheme to form. Scopes were not supported by the endpoint for password and there was no need to set the grant type as the ResourceOwnerPasswordResourceDetails object sets this itself in the constructor.
...
public ResourceOwnerPasswordResourceDetails() {
setGrantType("password");
}
...
The key thing for me was the client_id and client_secret were not being added to the form object to post in the body if resource.setClientAuthenticationScheme(AuthenticationScheme.form); was not set.
See the switch in:
org.springframework.security.oauth2.client.token.auth.DefaultClientAuthenticationHandler.authenticateTokenRequest()
Finally, when connecting to Salesforce endpoint the password token needed to be appended to the password.
#EnableOAuth2Client
#Configuration
class MyConfig {
#Value("${security.oauth2.client.access-token-uri}")
private String tokenUrl;
#Value("${security.oauth2.client.client-id}")
private String clientId;
#Value("${security.oauth2.client.client-secret}")
private String clientSecret;
#Value("${security.oauth2.client.password-token}")
private String passwordToken;
#Value("${security.user.name}")
private String username;
#Value("${security.user.password}")
private String password;
#Bean
protected OAuth2ProtectedResourceDetails resource() {
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
resource.setAccessTokenUri(tokenUrl);
resource.setClientId(clientId);
resource.setClientSecret(clientSecret);
resource.setClientAuthenticationScheme(AuthenticationScheme.form);
resource.setUsername(username);
resource.setPassword(password + passwordToken);
return resource;
}
#Bean
public OAuth2RestOperations restTemplate() {
return new OAuth2RestTemplate(resource(), new DefaultOAuth2ClientContext(new DefaultAccessTokenRequest()));
}
}
#Service
#SuppressWarnings("unchecked")
class MyService {
#Autowired
private OAuth2RestOperations restTemplate;
public MyService() {
restTemplate.getAccessToken();
}
}
I have different approach if you want access token and make call to other resource system with access token in header
Spring Security comes with automatic security: oauth2 properties access from application.yml file for every request and every request has SESSIONID which it reads and pull user info via Principal, so you need to make sure inject Principal in OAuthUser and get accessToken and make call to resource server
This is your application.yml, change according to your auth server:
security:
oauth2:
client:
clientId: 233668646673605
clientSecret: 33b17e044ee6a4fa383f46ec6e28ea1d
accessTokenUri: https://graph.facebook.com/oauth/access_token
userAuthorizationUri: https://www.facebook.com/dialog/oauth
tokenName: oauth_token
authenticationScheme: query
clientAuthenticationScheme: form
resource:
userInfoUri: https://graph.facebook.com/me
#Component
public class OAuthUser implements Serializable {
private static final long serialVersionUID = 1L;
private String authority;
#JsonIgnore
private String clientId;
#JsonIgnore
private String grantType;
private boolean isAuthenticated;
private Map<String, Object> userDetail = new LinkedHashMap<String, Object>();
#JsonIgnore
private String sessionId;
#JsonIgnore
private String tokenType;
#JsonIgnore
private String accessToken;
#JsonIgnore
private Principal principal;
public void setOAuthUser(Principal principal) {
this.principal = principal;
init();
}
public Principal getPrincipal() {
return principal;
}
private void init() {
if (principal != null) {
OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) principal;
if (oAuth2Authentication != null) {
for (GrantedAuthority ga : oAuth2Authentication.getAuthorities()) {
setAuthority(ga.getAuthority());
}
setClientId(oAuth2Authentication.getOAuth2Request().getClientId());
setGrantType(oAuth2Authentication.getOAuth2Request().getGrantType());
setAuthenticated(oAuth2Authentication.getUserAuthentication().isAuthenticated());
OAuth2AuthenticationDetails oAuth2AuthenticationDetails = (OAuth2AuthenticationDetails) oAuth2Authentication
.getDetails();
if (oAuth2AuthenticationDetails != null) {
setSessionId(oAuth2AuthenticationDetails.getSessionId());
setTokenType(oAuth2AuthenticationDetails.getTokenType());
// This is what you will be looking for
setAccessToken(oAuth2AuthenticationDetails.getTokenValue());
}
// This detail is more related to Logged-in User
UsernamePasswordAuthenticationToken userAuthenticationToken = (UsernamePasswordAuthenticationToken) oAuth2Authentication.getUserAuthentication();
if (userAuthenticationToken != null) {
LinkedHashMap<String, Object> detailMap = (LinkedHashMap<String, Object>) userAuthenticationToken.getDetails();
if (detailMap != null) {
for (Map.Entry<String, Object> mapEntry : detailMap.entrySet()) {
//System.out.println("#### detail Key = " + mapEntry.getKey());
//System.out.println("#### detail Value = " + mapEntry.getValue());
getUserDetail().put(mapEntry.getKey(), mapEntry.getValue());
}
}
}
}
}
}
public String getAuthority() {
return authority;
}
public void setAuthority(String authority) {
this.authority = authority;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getGrantType() {
return grantType;
}
public void setGrantType(String grantType) {
this.grantType = grantType;
}
public boolean isAuthenticated() {
return isAuthenticated;
}
public void setAuthenticated(boolean isAuthenticated) {
this.isAuthenticated = isAuthenticated;
}
public Map<String, Object> getUserDetail() {
return userDetail;
}
public void setUserDetail(Map<String, Object> userDetail) {
this.userDetail = userDetail;
}
public String getSessionId() {
return sessionId;
}
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
public String getTokenType() {
return tokenType;
}
public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
#Override
public String toString() {
return "OAuthUser [clientId=" + clientId + ", grantType=" + grantType + ", isAuthenticated=" + isAuthenticated
+ ", userDetail=" + userDetail + ", sessionId=" + sessionId + ", tokenType="
+ tokenType + ", accessToken= " + accessToken + " ]";
}
#RestController
public class YourController {
#Autowired
OAuthUser oAuthUser;
// In case if you want to see Profile of user then you this
#RequestMapping(value = "/profile", produces = MediaType.APPLICATION_JSON_VALUE)
public OAuthUser user(Principal principal) {
oAuthUser.setOAuthUser(principal);
// System.out.println("#### Inside user() - oAuthUser.toString() = " + oAuthUser.toString());
return oAuthUser;
}
#RequestMapping(value = "/createOrder",
method = RequestMethod.POST,
headers = {"Content-type=application/json"},
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public FinalOrderDetail createOrder(#RequestBody CreateOrder createOrder) {
return postCreateOrder_restTemplate(createOrder, oAuthUser).getBody();
}
private ResponseEntity<String> postCreateOrder_restTemplate(CreateOrder createOrder, OAuthUser oAuthUser) {
String url_POST = "your post url goes here";
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add("Authorization", String.format("%s %s", oAuthUser.getTokenType(), oAuthUser.getAccessToken()));
headers.add("Content-Type", "application/json");
RestTemplate restTemplate = new RestTemplate();
//restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
HttpEntity<String> request = new HttpEntity<String>(createOrder, headers);
ResponseEntity<String> result = restTemplate.exchange(url_POST, HttpMethod.POST, request, String.class);
System.out.println("#### post response = " + result);
return result;
}
}
My simple solution. IMHO it's the cleanest.
First create a application.yml
spring.main.allow-bean-definition-overriding: true
security:
oauth2:
client:
clientId: XXX
clientSecret: XXX
accessTokenUri: XXX
tokenName: access_token
grant-type: client_credentials
Create the main class: Main
#SpringBootApplication
#EnableOAuth2Client
public class Main extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll();
}
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
#Bean
public OAuth2RestTemplate oauth2RestTemplate(ClientCredentialsResourceDetails details) {
return new OAuth2RestTemplate(details);
}
}
Then Create the controller class: Controller
#RestController
class OfferController {
#Autowired
private OAuth2RestOperations restOperations;
#RequestMapping(value = "/<your url>"
, method = RequestMethod.GET
, produces = "application/json")
public String foo() {
ResponseEntity<String> responseEntity = restOperations.getForEntity(<the url you want to call on the server>, String.class);
return responseEntity.getBody();
}
}
Maven dependencies
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>
</dependencies>

Resources