zoukankan      html  css  js  c++  java
  • spring security oauth2 资源服务器WebAsyncTask/DeferredResult接口调用报错InsufficientAuthenticationException

    异常现象

    • 访问非WebAsyncTask接口正常
    • 访问WebAsyncTask/DeferredResult接口成功执行代码逻辑,但返回信息抛出异常InsufficientAuthenticationException
    • 服务报错:
      Could not fetch user details: class org.springframework.beans.factory.BeanCreationException, Error creating bean with name ‘scopedTarget.oauth2ClientContext’: Scope ‘request’ is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found:
      Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

    spring security 版本2.3.8

    资源服务配置

    security:
      oauth2:
        client:
          client-id: client1
          client-secret: client1pwd
          access-token-uri: 'http://localhost:11000/oauth/token'
          user-authorization-uri: 'http://localhost:11000/oauth/authorize'
          scope: all
        resource:
          token-info-uri: 'http://localhost:11000/oauth/check_token'
          user-info-uri: 'http://localhost:11000/oauth/check_user'
          prefer-token-info: false
    

    接口定义

    @GetMapping("/result1")
    public static Object result1() {
        return new WebAsyncTask(() -> {
            return "hello1";
        });
    }
    @GetMapping("/result2")
    public static Object result2() {
        return "hello2";
    }
    

    源码跟踪

    调用WebAsyncTask/DeferredResult接口时会进项两次认证:

    1. 处理传入请求时,可以正常完成认证获取用户信息。
    2. 处理请求响应时,由于使用了WebAsyncTask,响应处理使用了另一个线程,而非web请求处理线程,此线程中无法获取oauth2ClientContext
    • org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices
    public class UserInfoTokenServices implements ResourceServerTokenServices {
    ...
    	private Map<String, Object> getMap(String path, String accessToken) {
    		if (this.logger.isDebugEnabled()) {
    			this.logger.debug("Getting user info from: " + path);
    		}
    		try {
    			OAuth2RestOperations restTemplate = this.restTemplate;
    			if (restTemplate == null) {
    				BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails();
    				resource.setClientId(this.clientId);
    				restTemplate = new OAuth2RestTemplate(resource);
    			}
    			// 在返回线程中这里将报错
    			OAuth2AccessToken existingToken = restTemplate.getOAuth2ClientContext()
    					.getAccessToken();
    			if (existingToken == null || !accessToken.equals(existingToken.getValue())) {
    				DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(
    						accessToken);
    				token.setTokenType(this.tokenType);
    				restTemplate.getOAuth2ClientContext().setAccessToken(token);
    			}
    			return restTemplate.getForEntity(path, Map.class).getBody();
    		}
    		catch (Exception ex) {
    			this.logger.warn("Could not fetch user details: " + ex.getClass() + ", "
    					+ ex.getMessage());
    			return Collections.<String, Object>singletonMap("error",
    					"Could not fetch user details");
    		}
    	}
    }
    
    • org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2RestOperationsConfiguration
    public class OAuth2RestOperationsConfiguration {
    	@Configuration
    	@ConditionalOnMissingBean(OAuth2ClientConfiguration.class)
    	@Conditional({ OAuth2ClientIdCondition.class, NoClientCredentialsCondition.class })
    	@Import(OAuth2ProtectedResourceDetailsConfiguration.class)
    	protected static class RequestScopedConfiguration {
    
    		@Bean
    		@Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
    		public DefaultOAuth2ClientContext oauth2ClientContext() {
    			DefaultOAuth2ClientContext context = new DefaultOAuth2ClientContext(
    					new DefaultAccessTokenRequest());
    			Authentication principal = SecurityContextHolder.getContext()
    					.getAuthentication();
    			if (principal instanceof OAuth2Authentication) {
    				OAuth2Authentication authentication = (OAuth2Authentication) principal;
    				Object details = authentication.getDetails();
    				if (details instanceof OAuth2AuthenticationDetails) {
    					OAuth2AuthenticationDetails oauthsDetails = (OAuth2AuthenticationDetails) details;
    					String token = oauthsDetails.getTokenValue();
    					context.setAccessToken(new DefaultOAuth2AccessToken(token));
    				}
    			}
    			return context;
    		}
    
    	}
    }
    

    OAuth2ClientContextBean生命周期为request,因此在非request线程中无法获取OAuth2ClientContext

    解决方案

    1. 使用token-info-url,并实现userDetailsService获取用户信息,而非user-info-url
    2. 不使用WebAsyncTask/DeferredResult
  • 相关阅读:
    20201304杨淑钰+快速浏览教材
    20201304 自我介绍
    熟悉编程语言
    俄罗斯方块游戏代码的改写
    2020 -2021-1 20201319 《信息安全专业导论》第八周学习总结
    如何学好编程
    2020-2021-1 20201319 《信息安全专业导论》第七周学习总结
    2020-2021-1 20201319 《信息安全专业导论》第六周学习总结
    实现进制转换伪代码
    斐波那契数列递归实现
  • 原文地址:https://www.cnblogs.com/luguojun/p/14294696.html
Copyright © 2011-2022 走看看