如何使用Spring Boot / Spring Security来包含对OAuth2承载令牌请求的调用?

时间:2016-10-05 09:38:09

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

我遇到了以下问题:为了访问在线API,我需要进行身份验证。现在,我用自己的代码做了一切:

  1. 调用令牌网址以获取持有者令牌
  2. 获取持有人令牌
  3. 使用持票人令牌呼叫真实服务
  4. 获取结果
  5. 以下是代码:

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

    它有效,但我在每次通话时都会浪费对令牌网址的调用。这就是我喜欢它的方式:

    1. 致电真实服务
    2. 如果获得401
      1. 调用令牌网址以获取持有者令牌
      2. 获取持有人令牌
      3. 使用不记名令牌召回服务
    3. 获取结果
    4. 我可以在我的代码中执行此操作,但我已经在使用Spring Boot了。我想知道如何实现这一目标。是否有现有的过滤器,拦截器等等?

      感谢您的见解。

2 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:1)

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应用程序才能使用客户端凭据身份验证。而在我的情况下,这是提供商提供的唯一方式。

可以复制粘贴相关代码段: - )

我的最终代码如下:

@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