【问题标题】:How to use Spring Boot/Spring Security to wrap a call to an OAuth2 bearer token request?如何使用 Spring Boot/Spring Security 包装对 OAuth2 不记名令牌请求的调用?
【发布时间】:2016-10-05 09:38:09
【问题描述】:

我遇到了以下问题:为了访问在线 API,我需要通过身份验证。现在,我用自己的代码做所有事情:

  1. 调用不记名令牌的令牌 URL
  2. 获取不记名令牌
  3. 使用不记名令牌调用真实服务
  4. 得到结果

代码如下:

@RestController
public class RandomController {

    private final Random random;

    public RandomController(Random random) {
        this.random = random;
    }

    @RequestMapping(value = "/get", method = GET)
    public int random(@RequestParam(value = "limit", defaultValue = "100") int limit) {
        String bearerToken = getBearerToken();
        int[] bounds = getBounds(bearerToken);
        return computeRandom(bounds[0], bounds[1]);
    }

    private String getBearerToken() {
        RestTemplate tokenTemplate = new RestTemplate();
        MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        body.add("client_id", "my id");
        body.add("client_secret", "my secret");
        body.add("grant_type", "client_credentials");
        HttpHeaders headers = new HttpHeaders();
        headers.add("Accept", "application/json");
        HttpEntity<?> entity = new HttpEntity<>(body, headers);
        ResponseEntity<String> res = tokenTemplate.exchange(
                "https://bearer.token/get", POST, entity, String.class);
        Map<String, Object> map = new BasicJsonParser().parseMap(res.getBody());
        return (String) map.get("access_token");
    }

    private int[] getBounds(String bearerToken) {
        RestTemplate configurationTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "Bearer " + bearerToken);
        HttpEntity<?> entity = new HttpEntity<>(headers);
        ResponseEntity<String> res = configurationTemplate.exchange(
                "https://configurations.com/bounds", HttpMethod.GET, entity, String.class);
        Map<String, Object> map = new BasicJsonParser().parseMap(res.getBody());
        Map<String, Long> value = (Map<String, Long>) map.get("value");
        int lowerBound = value.get("lower").intValue();
        int upperBound = value.get("upper").intValue();
        return new int[]{lowerBound, upperBound};
    }

    private int computeRandom(int lowerBound, int upperBound) {
        int difference = upperBound - lowerBound;
        int raw = random.nextInt(difference);
        return raw + lowerBound;
    }
}

它有效,但我在每次调用时都在浪费对令牌 URL 的调用。这就是我希望它的工作方式:

  1. 调用真正的服务
  2. 如果收到 401
    1. 调用不记名令牌的令牌 URL
    2. 获取不记名令牌
    3. 使用不记名令牌调用服务
  3. 得到结果

我可以在我的代码中做到这一点,但我已经在使用 Spring Boot。我想知道如何实现这一目标。是否有现成的过滤器、拦截器之类的?

感谢您的见解。

【问题讨论】:

    标签: java spring spring-security spring-boot oauth-2.0


    【解决方案1】:

    尝试使用 Spring Security 的 OAuth2RestTemplate。如果可能,它应该负责获取令牌并缓存它。它应该在配置文件中进行配置,并在您调用 API 的任何地方注入。

    【讨论】:

    • 我会赞成你的答案,因为:a)它是唯一的 - 谢谢 :) b)它让我朝着正确的方向前进。不过,我期待更详细的东西。我终于找到了一种用最少的代码实现我想要的方法。为了记录,我将在下面回答我自己的问题。
    【解决方案2】:

    A jny 提到,要使用的类是OAuth2RestTemplate。但是,它的构造函数需要实现OAuth2ProtectedResourceDetails

    有几个可用的实现,使用client_credential 的一个是ClientCredentialsResourceDetails。我希望只需将@EnableOAuth2Client 添加到我的配置类并在application.yml 中配置所需的信息就足够了:

    security:
        oauth2:
            client:
                grant-type: client_credentials
                client-id: my id
                client-secret: my secret
                access-token-uri: https://bearer.token/get
    

    不幸的是,它不起作用。原因可以在OAuth2RestOperationsConfiguration类中找到:

    @Configuration
    @ConditionalOnClass(EnableOAuth2Client.class)
    @Conditional(OAuth2ClientIdCondition.class)
    public class OAuth2RestOperationsConfiguration {
    
        @Configuration
        @ConditionalOnNotWebApplication
        protected static class SingletonScopedConfiguration {
    
            @Bean
            @ConfigurationProperties("security.oauth2.client")
            @Primary
            public ClientCredentialsResourceDetails oauth2RemoteResource() {
                ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
                return details;
            }
            ...
        }
        ...
    }
    

    似乎 Spring 框架假设 - 错误地,只有非 Web 应用程序可以使用客户端凭据身份验证。而就我而言,这是提供商提供的唯一方式。

    相关的 sn-p 可以复制粘贴 :-)

    我的最终代码如下:

    @SpringBootApplication
    public class RandomApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(RandomApplication.class, args);
        }
    
        @Bean
        @ConfigurationProperties("security.oauth2.client")
        ClientCredentialsResourceDetails clientCredentialsResourceDetails() {
            return new ClientCredentialsResourceDetails();
        }
    
        @Bean
        OAuth2RestTemplate oAuth2RestTemplate() {
            return new OAuth2RestTemplate(clientCredentialsResourceDetails());
        }
    
        @Bean
        Random random() {
            return new SecureRandom();
        }
    }
    
    @RestController
    public class RandomController {
    
        private final OAuth2RestTemplate oAuth2RestTemplate;
        private final Random random;
    
        public RandomController(OAuth2RestTemplate oAuth2RestTemplate, Random random) {
            this.oAuth2RestTemplate = oAuth2RestTemplate;
            this.random = random;
        }
    
        @RequestMapping(value = "/get", method = GET)
        public int random() {
            int[] bounds = getBounds();
            return computeRandom(bounds[0], bounds[1]);
        }
    
        private int[] getBounds() {
            ResponseEntity<String> res = oAuth2RestTemplate.getForEntity(
                    "https://configurations.com/bounds", String.class);
            Map<String, Object> map = new BasicJsonParser().parseMap(res.getBody());
            Map<String, Long> value = (Map<String, Long>) map.get("value");
            int lowerBound = value.get("lower").intValue();
            int upperBound = value.get("upper").intValue();
            return new int[]{lowerBound, upperBound};
        }
    
        private int computeRandom(int lowerBound, int upperBound) {
            int difference = upperBound - lowerBound;
            int raw = random.nextInt(difference);
            return raw + lowerBound;
        }
    }
    

    请注意,由于我将 RestTemplate 替换为 OAuth2RestTemplate,因此控制器的样板文件要少得多。

    【讨论】:

      猜你喜欢
      • 2014-11-05
      • 2016-04-21
      • 2016-08-12
      • 2018-12-01
      • 2017-01-01
      • 2016-07-19
      • 2017-09-11
      • 2017-04-06
      • 2015-03-09
      相关资源
      最近更新 更多