【问题标题】:How to use OAuth2RestTemplate?如何使用 OAuth2RestTemplate?
【发布时间】:2015-03-07 23:51:52
【问题描述】:

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

我的 REST 服务的一个例子是:

http://localhost:8082/app/helloworld

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

要申请令牌,我会去:

http://localhost:8082/app/oauth/token?grant_type=password&client_id=restapp&client_secret=restapp&username=**USERNAME**&password=**PASSWORD**

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

http://localhost:8082/app/helloworld/?access_token=**4855f557-c6ee-43b7-8617-c24591965206**

现在我的问题是如何实现第二个应用程序,它可以使用这个 OAuth2 安全的 REST API?我真的没有找到任何工作示例,您可以提供用户名和密码(例如,来自登录表单),然后生成一个令牌,该令牌可重复用于从 REST API 获取数据。

我目前尝试了以下对象:

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

但这只是行不通:(

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

【问题讨论】:

    标签: spring spring-security oauth rest


    【解决方案1】:

    您可以在此处找到编写 OAuth 客户端的示例:

    在您的情况下,您不能只对所有内容使用默认类或基类,您有多个实现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();
        }
    }
    

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

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

    【讨论】:

    • 非常感谢您的评论。事实上,我也找不到任何例子,这就是我发表这篇文章的原因。 :) 我将在今天晚些时候或明天查看样品并提供反馈。再次感谢。
    • 上述 cmets 工作正常,我也使用了您的示例应用程序,谢谢。
    • @maruidog - 当我尝试访问此http://localhost:8005/authorized-results 时,我已经尝试过您的示例,它会将我重定向到登录页面,我将用户名设置为 roy,密码 spring 显示错误 bad credentials你能告诉我我需要使用什么用户名和密码吗?
    • @webgeek - 我已将其更改为“roy”和“spring”,所以它现在应该可以工作了。请再次从 github 下载它,它应该可以按预期工作。之前它只是“用户”和“密码”,没有遵循 Roys 的例子,因此即使它有效也是不正确的。感谢您指出了这一点。为给您带来的不便,欢呼并深表歉意。如果您还有其他问题,请在 git 存储库中提问,我会立即在电子邮件中收到通知。
    • @webgeek - 这只是一个例子,所以我试图使其尽可能简洁,我硬编码了一些东西,这就是它仍然有效的原因。我只是试图避免要求用户提供 ouath 的密码和用户名,所以我将其硬编码在源代码中只是为了这个目的。而且由于来自身份验证的密码永远不会存储在 spring 中,因此获取它的唯一方法是再次请求它。因此,用户提供的密码和用户名与 ouath 硬编码和使用的密码和用户名之间的区别只是忽略了我的注意。
    【解决方案2】:

    如果您想要访问令牌并在标头中使用访问令牌调用其他资源系统,我有不同的方法

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

    这是您的 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 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;
    }
    
    
    }
    

    【讨论】:

    • 如果您不将其存储在 .yml 文件中,您还会将其存储在哪里?如果您推送 application.yml,您的凭据将被暴露,不是吗?
    • @Jesse 您仍然可以从命令行注入这些变量。因此,在启动应用程序时,您不会暴露它,而是“注入”它。
    【解决方案3】:

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

    ...

    public ResourceOwnerPasswordResourceDetails() {
        setGrantType("password");
    }
    

    ...

    对我而言,关键是如果未设置 resource.setClientAuthenticationScheme(AuthenticationScheme.form);,则不会将 client_id 和 client_secret 添加到表单对象以发布到正文中。

    查看开关: org.springframework.security.oauth2.client.token.auth.DefaultClientAuthenticationHandler.authenticateTokenRequest()

    最后,当连接到 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();
        }
    }
    

    【讨论】:

    • 你如何安全地存储你的"@Value("${security.oauth2.client.client-secret}")"和@Value("${security.用户密码}") ?
    • 您可以使用 Jasypt 库。您对属性进行编码......然后在运行时传递密钥。请小心,因为您的密钥仍将在您的 shell 脚本中或当您执行 ps -ef 时。如果您限制对生产服务器的访问,应该不是问题
    • 什么是security.oauth2.client.password-token? Spring boot 2.1.7 没有这个属性。
    【解决方案4】:

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

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

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

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

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

    【讨论】:

    • 没有 'org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails' 类型的合格 bean。任何想法
    • 我会在周末修复它:)
    • 这个解决方案看起来很干净,但目前不起作用。在配置中需要 org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails 类型的 bean。
    猜你喜欢
    • 2016-12-07
    • 1970-01-01
    • 2016-12-25
    • 2015-06-08
    • 2021-05-19
    • 2013-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多