52

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.

Henke
  • 4,445
  • 3
  • 31
  • 44
Joachim Seminck
  • 721
  • 1
  • 8
  • 10

4 Answers4

64

You can find examples for writing OAuth clients here:

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 .

ℛɑƒæĿᴿᴹᴿ
  • 4,983
  • 4
  • 38
  • 58
mariubog
  • 1,498
  • 15
  • 16
  • 2
    Thanks a lot for your comment. Indeed I also couldn't find any examples which is why I made this post. :) I will go through the samples later today or tomorrow and provide feedback. Thanks again. – Joachim Seminck Jan 11 '15 at 09:47
  • The above comments worked and I also used your sample application, thanks for that. – Joachim Seminck Jan 19 '15 at 11:31
  • @mariudog - I have tried your examples when I am trying to access this `http://localhost:8005/authorized-results` it is redirecting me to the login page I am putting the username as roy and password spring it showing error `bad credentials` will you please tell me what username and password do I need to use ? – Suleman khan Aug 24 '15 at 18:49
  • 2
    @webgeek - I have changed it to "roy" and "spring" so it should work now. Please download it from github again it should work as expected. Before it was just "user" and "password" which did not follow the Roys example and was therefore incorrect even though it worked. Thanks for pointing that out. Cheers and sorry for inconvenience. If you have any more questions please ask them in the git repository I'll get notification in my email right away. – mariubog Aug 24 '15 at 19:35
  • @webgeek - It is just an example so trying to make it as condensed as possible I hard coded some stuff that's why it still worked. I just tried to avoid asking user for providing the password and user name for ouath so I hard coded it in the source just for that purpose. And since password from the authentication is never stored in spring only way to get it would be to ask for it again. Therefore difference between user provided password and username and the one that was hard-coded and used by ouath just skipped my attention. – mariubog Aug 24 '15 at 19:48
  • Thanks for your quick response! so now It will work with roy and spring after fresh download ? – Suleman khan Aug 24 '15 at 19:51
  • @mariubog - i have downloaded both project you have mention in your github, are these both projects are internally communicating ? and is it mandatory to have royclarkson project along with yours for the implementation of oAuth ? – Suleman khan Aug 24 '15 at 20:01
  • Just as it says in read-me. In order to use my template example project you have to have ouath server that it will connect to and it is the second project. – mariubog Aug 24 '15 at 20:07
  • @webgeek - Just as it says in read-me. In order to use my template project you have to have ouath server that it will connect to and it is the second project. Before I was just using royclarkson's repo but then I made some changes to demonstrate `clientOnly` authentication and these changes are not included in oryginal Roys oauth example , only in my fork. If you follow read-me from my client example that points to my fork of the oauth server example everything should work just fine. Summing up you need to have ouath server from first project started before you start using oauth template . – mariubog Aug 24 '15 at 20:16
  • @mariubog - I have made it working now when I get loggedIn i am able to get the response with that greeting. I am just little curious that how these apps are communicating ? well you made it a lot simpler otherwise it was getting nightmare to me ;) – Suleman khan Aug 24 '15 at 20:35
  • @mariubog - will please tell me where you are setting bearer access_token in order to access the protected resources ? I have gone through your readme I haven't this answer. – Suleman khan Aug 25 '15 at 19:44
  • @webgeek - I think If you have more specific questions you should move this discussion to this repo issues https://github.com/mariubog/oauth-client-sample/issues before someone deletes it – mariubog Aug 25 '15 at 23:40
  • @mariubog Is this the best practice when implementing oauth2 in spring? I currently have template being resolved inside my controller class. – JayC Apr 27 '17 at 18:04
  • If your resource is configured via application.properties or application.yml, how can you still use OAuth2RestTemplate in a fashion similar to this? I need to override the setAccessToken() method of DefaultOAuth2ClientContext because of https://github.com/spring-projects/spring-security-oauth/issues/457 but everything I have is currently defined in resource files (and I'd prefer to keep it that way). – michaelgulak Jun 13 '17 at 21:37
  • I followed the same steps, but I am getting the following exception. Can someone please help? {{ org.springframework.security.authentication.InsufficientAuthenticationException: User must be authenticated with Spring Security before authorization can be completed. at org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.authorize(AuthorizationEndpoint.java:138) ~[spring-security-oauth2-2.0.14.RELEASE.jar:na] }} – Murali Mohan Jul 14 '18 at 03:47
  • [EnableOAuth2Client](https://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/config/annotation/web/configuration/EnableOAuth2Client.html) says " you need a global servlet filter in your application of the DelegatingFilterProxy that delegates to a bean named "oauth2ClientContextFilter"" – [What is it talking about?](https://stackoverflow.com/questions/55461345/understanding-spring-security-enableoauth2client-annotation) – Patrick M Apr 02 '19 at 05:18
  • Hey, thanks for your code. You didn't use authorizeUrl in your code. How can we use authorizeUrl in a rest client? – cem çetin Jun 20 '21 at 10:28
6

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();
    }
}
theINtoy
  • 3,388
  • 2
  • 37
  • 60
  • How do you *securely* store your "@Value("${security.oauth2.client.client-secret}")" and @Value("${security.user.password}") ? – granadaCoder Mar 28 '19 at 17:21
  • You can use Jasypt library. You encode your properties ... then pass the key in during run time. Just be careful because you key will still be in your shell script or when you do a ps -ef. Should not be a problem if you restrict access to your production server – Micho Rizo Apr 08 '19 at 22:40
  • 1
    What is security.oauth2.client.password-token? Spring boot 2.1.7 does not have this property. – polis Aug 11 '19 at 13:59
4

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


}
Martin Tournoij
  • 26,737
  • 24
  • 105
  • 146
  • If you dont store it in .yml file where else would you store it? If you push application.yml your credentials would be exposed wouldn't it? – JayC Apr 27 '17 at 18:05
  • @Jesse you can still inject these variables from command line as well. Therefore you will not expose it, but "inject" it, when starting the application. – Yosh Oct 27 '17 at 11:29
-1

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>
Koroslak
  • 643
  • 1
  • 6
  • 12
  • No qualifying bean of type 'org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails' . Any idea – Ravi MCA Dec 26 '19 at 07:20
  • I'll fix it over the weekend :) – Koroslak Feb 12 '20 at 09:28
  • This solution looks clean but does not work at the moment. Needs bean of type org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails in configuration. – Old Nick Feb 19 '20 at 11:37