spring 如何使用 OAuth2RestTemplate?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27864295/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 00:17:10  来源:igfitidea点击:

How to use OAuth2RestTemplate?

springapirestoauthspring-security

提问by Joachim Seminck

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...)

我试图了解如何使用 OAuth2RestTemplate 对象来使用我的 OAuth2 安全 REST 服务(它在不同的项目下运行,让我们假设也在不同的服务器上等......)

f.e. my rest service is:

我的休息服务是:

http://localhost:8082/app/helloworld

-> Accessing this URL generates an error as I am not authenticated

-> 访问此 URL 会生成错误,因为我未通过身份验证

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)

收到令牌后,我可以使用以下 URL(插入的示例令牌)连接到 REST API

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.

现在我的问题是如何实现可以使用此 OAuth2 安全 REST API 的第二个应用程序?我真的没有找到任何工作示例,您提供用户名和密码(例如,来自登录表单)然后生成一个令牌,该令牌可以重新用于从 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 is greatly appreciated or links to working examples and tutorials is greatly appreciated.

非常感谢任何想法或工作示例和教程的链接。

回答by mariubog

You can find examples for writing OAuth clients here:

您可以在此处找到编写 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:

在您的情况下,您不能只对所有内容使用默认类或基类,您有多个类 Implementing OAuth2ProtectedResourceDetails。配置取决于您如何配置 OAuth 服务,但假设我建议使用 curl 连接:

@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 @EnableOAuth2Clienton 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 loggerwith debugenabled set. Good luck

不要忘记@EnableOAuth2Client你的配置类,我也建议你尝试使用你正在使用的 url 首先使用 curl,也尝试使用调试器跟踪它,因为很多异常只是被消耗了,并且由于安全原因从未打印出来,所以很难找到问题所在。您应该loggerdebug启用集一起使用。祝你好运

I uploaded sample springboot app on github https://github.com/mariubog/oauth-client-sampleto depict your situation because I could not find any samples for your scenario .

我在 github https://github.com/mariubog/oauth-client-sample上上传了示例 springboot 应用程序 来描述您的情况,因为我找不到您的场景的任何示例。

回答by theINtoy

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.

在@mariubog ( https://stackoverflow.com/a/27882337/1279002)的回答中,我也在示例中使用了密码授予类型,但需要设置客户端身份验证方案以形成。密码端点不支持范围,并且不需要设置授权类型,因为 ResourceOwnerPasswordResourceDetails 对象在构造函数中自行设置。

...

...

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.

对我来说,关键是 client_id 和 client_secret 没有被添加到表单对象中,如果resource.setClientAuthenticationScheme(AuthenticationScheme.form);没有设置,则不会在正文中发布。

See the switch in: org.springframework.security.oauth2.client.token.auth.DefaultClientAuthenticationHandler.authenticateTokenRequest()

查看开关: 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.

最后,当连接到 Salesforce 端点时,需要将密码令牌附加到密码中。

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

回答by Java_Fire_Within

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

Spring Security 具有自动安全性:每个请求都从 application.yml 文件访问 oauth2 属性,并且每个请求都有 SESSIONID,它通过 Principal 读取和拉取用户信息,因此您需要确保在 OAuthUser 中注入 Principal 并获取 accessToken 并调用资源服务器

This is your application.yml, change according to your auth server:

这是您的 application.yml,根据您的身份验证服务器进行更改:

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

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


}

回答by Koroslak

My simple solution. IMHO it's the cleanest.

我的简单解决方案。恕我直言,这是最干净的。

First create a application.yml

首先创建一个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

创建主类: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

然后创建控制器类: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

Maven 依赖项

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