0

我有一个使用 OAuth 1.0 保护某些资源的外部合作伙伴。我需要访问这些资源,我想使用 Spring Boot 和 Spring Security OAuth 来做到这一点。因为我不想使用 XML 配置,所以我已经搜索了一种通过 Java 配置设置所有内容的方法。我发现这个线程提供了一个如何做到这一点的例子。但是关于 OAuth 1.0 流程的一些事情对我来说并不清楚。

我的合作伙伴为 OAuth 提供了四个端点:一个提供消费者令牌的端点、一个request_token端点、一个授权端点和一个access_token端点。使用我当前的设置(如下所示),我可以获得请求令牌并调用授权端点。但是,授权端点不要求确认,而是期望作为 URL 参数的电子邮件和密码,并在检查凭据后返回以下内容:

oauth_verifier=a02ebdc5433242e2b6e582e17b84e313

这就是 OAuth 流程卡住的地方。

在阅读了一些关于 OAuth 1.0 的文章之后,通常的流程是这样的:

  1. 获取消费者令牌/密钥
  2. request_token通过端点使用消费者令牌获取 oauth 令牌
  3. 重定向到授权 URL 并要求用户确认
  4. 使用验证者令牌重定向到消费者
  5. 用户验证令牌和 oauth 令牌通过access_token端点获取访问令牌

首先:我不清楚第 3 步和第 4 步。我找到了Spring Security OAuth 示例,但我不清楚在确认访问后如何将用户/验证者令牌发送回消费者。有人可以解释一下这是怎么做到的吗?

第二:鉴于我的合作伙伴端点不要求确认,而是立即返回一个 oauth 验证器,我如何在此设置中使用 Spring Security OAuth?我正在考虑实现我自己的授权端点,该端点调用我的合作伙伴的授权端点,然后以某种方式让我的消费者知道验证者,但我不确定如何做后面的部分。

这是到目前为止的代码(在上面提到的线程的帮助下;ConsumerTokenDto因为它是微不足道的,所以被忽略了):

应用

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

端点

@RestController
public class Endpoint {
    @Autowired
    private OAuthRestTemplate oAuthRestTemplate;
    private String url = "https://....";

    @RequestMapping("/public/v1/meters")
    public String getMeters() {
        try {
            return oAuthRestTemplate.getForObject(URI.create(url), String.class);
        } catch (Exception e) {
            LOG.error("Exception", e);
            return "";
        }
    }
}

OAuth 配置

@Configuration
@EnableWebSecurity
public class OAuthConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private RestTemplateBuilder restTemplateBuilder;
    private ConsumerTokenDto consumerTokenDto;

    private static final String ID = "meters";

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/**").permitAll();
        http.addFilterAfter(this.oauthConsumerContextFilter(), SwitchUserFilter.class);
        http.addFilterAfter(this.oauthConsumerProcessingFilter(), OAuthConsumerContextFilterImpl.class);
    }

    private OAuthConsumerContextFilter oauthConsumerContextFilter() {
        OAuthConsumerContextFilter filter = new OAuthConsumerContextFilter();
        filter.setConsumerSupport(this.consumerSupport());
        return filter;
    }

    private OAuthConsumerProcessingFilter oauthConsumerProcessingFilter() {
        OAuthConsumerProcessingFilter filter = new OAuthConsumerProcessingFilter();
        filter.setProtectedResourceDetailsService(this.prds());

        LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> map = new LinkedHashMap<>();

        // one entry per oauth:url element in xml
        map.put(
                new AntPathRequestMatcher("/public/v1/**", null),
                Collections.singletonList(new SecurityConfig(ID)));

        filter.setObjectDefinitionSource(new DefaultFilterInvocationSecurityMetadataSource(map));

        return filter;
    }

    @Bean
    OAuthConsumerSupport consumerSupport() {
        CoreOAuthConsumerSupport consumerSupport = new CoreOAuthConsumerSupport();
        consumerSupport.setProtectedResourceDetailsService(prds());
        return consumerSupport;
    }

    @Bean
    ProtectedResourceDetailsService prds() {
        InMemoryProtectedResourceDetailsService service = new InMemoryProtectedResourceDetailsService();
        Map<String, ProtectedResourceDetails> store = new HashMap<>();
        store.put(ID, prd());
        service.setResourceDetailsStore(store);
        return service;
    }

    ProtectedResourceDetails prd() {
        ConsumerTokenDto consumerToken = getConsumerToken();
        BaseProtectedResourceDetails resourceDetails = new BaseProtectedResourceDetails();
        resourceDetails.setId(ID);
        resourceDetails.setConsumerKey(consumerToken.getKey());
        resourceDetails.setSharedSecret(new SharedConsumerSecretImpl(consumerToken.getSecret()));
        resourceDetails.setRequestTokenURL("https://.../request_token");
        // the authorization URL does not prompt for confirmation but immediately returns an OAuth verifier
        resourceDetails.setUserAuthorizationURL(
                "https://.../authorize?email=mail&password=pw");
        resourceDetails.setAccessTokenURL("https://.../access_token");
        resourceDetails.setSignatureMethod(HMAC_SHA1SignatureMethod.SIGNATURE_NAME);
        return resourceDetails;
    }

    // get consumer token from provider
    private ConsumerTokenDto getConsumerToken() {
        if (consumerTokenDto == null) {
            MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
            body.add("client", "Client");

            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

            HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers);

            RestTemplate restTemplate = restTemplateBuilder.setConnectTimeout(1000).setReadTimeout(1000).build();
            restTemplate.getInterceptors().add(interceptor);
            restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
            ResponseEntity<ConsumerTokenDto> response = restTemplate
                    .exchange("https://.../consumer_token", HttpMethod.POST, request,
                            ConsumerTokenDto.class);

            consumerTokenDto = response.getBody();
        }
        return consumerTokenDto;
    }

    // create oauth rest template
    @Bean
    public OAuthRestTemplate oAuthRestTemplate() {
        OAuthRestTemplate oAuthRestTemplate = new OAuthRestTemplate(prd());
        oAuthRestTemplate.getInterceptors().add(interceptor);
        return oAuthRestTemplate;
    }
}
4

1 回答 1

0

我想我找到了解决办法。诀窍是实现我自己OAuthConsumerContextFilter的并将重定向调用替换为对授权端点的直接调用。我已经评论了下面有趣的部分(从 开始//!!!!)。

CustomOAuthConsumerContextFilter

public class CustomOAuthConsumerContextFilter extends OAuthConsumerContextFilter {

    private static final Logger LOG = LoggerFactory.getLogger(CustomOAuthConsumerContextFilter.class);

    private RestTemplateBuilder restTemplateBuilder;


    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        OAuthSecurityContextImpl context = new OAuthSecurityContextImpl();
        context.setDetails(request);

        Map<String, OAuthConsumerToken> rememberedTokens =
                getRememberMeServices().loadRememberedTokens(request, response);
        Map<String, OAuthConsumerToken> accessTokens = new TreeMap<>();
        Map<String, OAuthConsumerToken> requestTokens = new TreeMap<>();
        if (rememberedTokens != null) {
            for (Map.Entry<String, OAuthConsumerToken> tokenEntry : rememberedTokens.entrySet()) {
                OAuthConsumerToken token = tokenEntry.getValue();
                if (token != null) {
                    if (token.isAccessToken()) {
                        accessTokens.put(tokenEntry.getKey(), token);
                    } else {
                        requestTokens.put(tokenEntry.getKey(), token);
                    }
                }
            }
        }

        context.setAccessTokens(accessTokens);
        OAuthSecurityContextHolder.setContext(context);
        if (LOG.isDebugEnabled()) {
            LOG.debug("Storing access tokens in request attribute '" + getAccessTokensRequestAttribute() + "'.");
        }

        try {
            try {
                request.setAttribute(getAccessTokensRequestAttribute(), new ArrayList<>(accessTokens.values()));
                chain.doFilter(request, response);
            } catch (Exception e) {
                try {
                    ProtectedResourceDetails resourceThatNeedsAuthorization = checkForResourceThatNeedsAuthorization(e);
                    String neededResourceId = resourceThatNeedsAuthorization.getId();
                    //!!!! store reference to verifier here, outside of loop
                    String verifier = null;
                    while (!accessTokens.containsKey(neededResourceId)) {
                        OAuthConsumerToken token = requestTokens.remove(neededResourceId);
                        if (token == null) {
                            token = getTokenServices().getToken(neededResourceId);
                        }

                        // if the token is null OR
                        // if there is NO access token and (we're not using 1.0a or the verifier is not null)
                        if (token == null || (!token.isAccessToken() &&
                                (!resourceThatNeedsAuthorization.isUse10a() || verifier == null))) {
                            //no token associated with the resource, start the oauth flow.
                            //if there's a request token, but no verifier, we'll assume that a previous oauth request failed and we need to get a new request token.
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("Obtaining request token for resource: " + neededResourceId);
                            }

                            //obtain authorization.
                            String callbackURL = response.encodeRedirectURL(getCallbackURL(request));
                            token = getConsumerSupport().getUnauthorizedRequestToken(neededResourceId, callbackURL);
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("Request token obtained for resource " + neededResourceId + ": " + token);
                            }

                            //okay, we've got a request token, now we need to authorize it.
                            requestTokens.put(neededResourceId, token);
                            getTokenServices().storeToken(neededResourceId, token);
                            String redirect =
                                    getUserAuthorizationRedirectURL(resourceThatNeedsAuthorization, token, callbackURL);

                            if (LOG.isDebugEnabled()) {
                                LOG.debug("Redirecting request to " + redirect +
                                        " for user authorization of the request token for resource " +
                                        neededResourceId + ".");
                            }

                            request.setAttribute(
                                    "org.springframework.security.oauth.consumer.AccessTokenRequiredException", e);
                            //                            this.redirectStrategy.sendRedirect(request, response, redirect);
                            //!!!! get the verifier from the authorization URL
                            verifier = this.getVerifier(redirect);
                            //!!!! start next iteration of loop -> now we have the verifier, so the else statement below shoud get executed and an access token retrieved
                            continue;
                        } else if (!token.isAccessToken()) {
                            //we have a presumably authorized request token, let's try to get an access token with it.
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("Obtaining access token for resource: " + neededResourceId);
                            }

                            //authorize the request token and store it.
                            try {
                                token = getConsumerSupport().getAccessToken(token, verifier);
                            } finally {
                                getTokenServices().removeToken(neededResourceId);
                            }

                            if (LOG.isDebugEnabled()) {
                                LOG.debug("Access token " + token + " obtained for resource " + neededResourceId +
                                        ". Now storing and using.");
                            }

                            getTokenServices().storeToken(neededResourceId, token);
                        }

                        accessTokens.put(neededResourceId, token);

                        try {
                            //try again
                            if (!response.isCommitted()) {
                                request.setAttribute(getAccessTokensRequestAttribute(),
                                        new ArrayList<>(accessTokens.values()));
                                chain.doFilter(request, response);
                            } else {
                                //dang. what do we do now?
                                throw new IllegalStateException(
                                        "Unable to reprocess filter chain with needed OAuth2 resources because the response is already committed.");
                            }
                        } catch (Exception e1) {
                            resourceThatNeedsAuthorization = checkForResourceThatNeedsAuthorization(e1);
                            neededResourceId = resourceThatNeedsAuthorization.getId();
                        }
                    }
                } catch (OAuthRequestFailedException eo) {
                    fail(request, response, eo);
                } catch (Exception ex) {
                    Throwable[] causeChain = getThrowableAnalyzer().determineCauseChain(ex);
                    OAuthRequestFailedException rfe = (OAuthRequestFailedException) getThrowableAnalyzer()
                            .getFirstThrowableOfType(OAuthRequestFailedException.class, causeChain);
                    if (rfe != null) {
                        fail(request, response, rfe);
                    } else {
                        // Rethrow ServletExceptions and RuntimeExceptions as-is
                        if (ex instanceof ServletException) {
                            throw (ServletException) ex;
                        } else if (ex instanceof RuntimeException) {
                            throw (RuntimeException) ex;
                        }

                        // Wrap other Exceptions. These are not expected to happen
                        throw new RuntimeException(ex);
                    }
                }
            }
        } finally {
            OAuthSecurityContextHolder.setContext(null);
            HashMap<String, OAuthConsumerToken> tokensToRemember = new HashMap<>();
            tokensToRemember.putAll(requestTokens);
            tokensToRemember.putAll(accessTokens);
            getRememberMeServices().rememberTokens(tokensToRemember, request, response);
        }
    }


    private String getVerifier(String authorizationURL) {
        HttpEntity request = HttpEntity.EMPTY;

        RestTemplate restTemplate = restTemplateBuilder.setConnectTimeout(1000).setReadTimeout(1000).build();
        ResponseEntity<String> response =
                restTemplate.exchange(authorizationURL, HttpMethod.GET, request, String.class);
        //!!!! extract verifier from response
        String verifier = response.getBody().split("=")[1];
        return verifier;
    }


    void setRestTemplateBuilder(RestTemplateBuilder restTemplateBuilder) {
        this.restTemplateBuilder = restTemplateBuilder;
    }
}
于 2017-08-22T10:58:11.087 回答