Spring Security OAuth 1.0流程 - 消费者验证

时间:2017-08-22 06:38:02

标签: java spring spring-security oauth oauth-1.0a

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

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

oauth_verifier=a02ebdc5433242e2b6e582e17b84e313

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

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

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

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

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

    这是到目前为止的代码(在上面提到的主题的帮助下,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;
        }
    }
    

1 个答案:

答案 0 :(得分: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;
    }
}