请求地址:localhost:40150/oauth/token/?grant_type=mobile_password

请求头, Basic 是 client_id 和 client_secret

在这里插入图片描述

请求参数

在这里插入图片描述

TokenEndpoint#postAccessToken 报错There is no client authentication. Try adding an appropriate authentication filter.

    @RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
	public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal,
	@RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
		if (!(principal instanceof Authentication)) {
			throw new InsufficientAuthenticationException(
					"There is no client authentication. Try adding an appropriate authentication filter.");
		}
        // 省略代码
        ...
    }

报错原因:/oauth/token/ 改成 /oauth/token, 因为结尾多了斜杆 / 导致

DefaultSecurityFilterChain#matches 匹配不上

    /**
    DefaultSecurityFilterChain#matches
    */
    public boolean matches(HttpServletRequest request) {
		return requestMatcher.matches(request);
	}

1. 抽象过滤器:OncePerRequestFilter#doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)

// 找到这个方法 this = WebMvcMetricsFilter
this.doFilterInternal(httpRequest, httpResponse, filterChain);

2. ApplicationFilterChain#doFilter 调用 internalDoFilter

    @Override
    public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {
        if( Globals.IS_SECURITY_ENABLED ) {
            ...
        }else {
            internalDoFilter(request,response);
        }
   }

internalDoFilter 取出过滤器 调用 FilterChainProxy#doFilter

在这里插入图片描述

在这里插入图片描述

2. FilterChainProxy#doFilter 方法里面调用 doFilterInternal 关键方法 getFilters 这里返回具体的过滤器

    @Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
       doFilterInternal(request, response, chain);
    }
    
    private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) {
       // 获取过滤器
       List<Filter> filters = getFilters(fwRequest);
    }
    
    private List<Filter> getFilters(HttpServletRequest request) {
		for (SecurityFilterChain chain : filterChains) {
			if (chain.matches(request)) {
				return chain.getFilters();
			}
		}
		return null;
	}

WebSecurity#performBuild 把 FilterChainProxy.filterChains 初始化, 如下图,最关键是 filters

在这里插入图片描述

看到有两个实现类, 里面的filters,那到底选择哪个filters, 又是如何选择的

FilterChainProxy#getFilters, 遍历 filterChains,根据request 匹配正则来确定选 filters

   private List<Filter> getFilters(HttpServletRequest request) {
		for (SecurityFilterChain chain : filterChains) {
			if (chain.matches(request)) {
				return chain.getFilters();
			}
		}

		return null;
	}

重点来了,报错根本原因就是这个 chain.matches(request) 我的请求 request = /oauth/token/, OrRequestMatcher [requestMatchers=[Ant [pattern=‘/oauth/token’], Ant [pattern=‘/oauth/token_key’], Ant [pattern=‘/oauth/check_token’]]] OrRequestMatcher.requestMatchers [Ant [pattern=‘/oauth/token’]],导致request = /oauth/token/ 和 pattern='/oauth/token’匹配不上 选错了filters, 具体看图

在这里插入图片描述

OrRequestMatcher#matches, 返回 false, 进入下一个循环

   public boolean matches(HttpServletRequest request) {
		for (RequestMatcher matcher : requestMatchers) {
			if (logger.isDebugEnabled()) {
				logger.debug("Trying to match using " + matcher);
			}
			if (matcher.matches(request)) {
				logger.debug("matched");
				return true;
			}
		}
		logger.debug("No matches found");
		return false;
	}

AnyRequestMatcher#matches 直接返回true

    public boolean matches(HttpServletRequest request) {
		return true;
	}

FilterChainProxy#doFilterInternal 关键的类 VirtualFilterChain,把刚filters 设置到里面,

private void doFilterInternal(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
			
    ...
    VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);

    // 调用doFilter, 然后挨个匹配 filters 列表的filter, 找到对应的
	vfc.doFilter(fwRequest, fwResponse);
}

VirtualFilterChain#doFilter, 把 filters 的过滤器匹配

   @Override
   public void doFilter(ServletRequest request, ServletResponse response) {
      if (currentPosition == size) {
			  ...
	  } else {
		  currentPosition++;

		  Filter nextFilter = additionalFilters.get(currentPosition - 1);

		  nextFilter.doFilter(request, response, this);
	  }
   }

AnyRequestMatcher对应的filters

[
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter,
org.springframework.security.web.context.SecurityContextPersistenceFilter, 
org.springframework.security.web.header.HeaderWriterFilter, 
org.springframework.security.web.authentication.logout.LogoutFilter, 
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter, 
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter, 
org.springframework.security.web.authentication.AnonymousAuthenticationFilter,
org.springframework.security.web.session.SessionManagementFilter, 
org.springframework.security.web.access.ExceptionTranslationFilter,
org.springframework.security.web.access.intercept.FilterSecurityInterceptor
]

OrRequestMatcher对应的filters

[
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter, 
org.springframework.security.web.context.SecurityContextPersistenceFilter, 
org.springframework.security.web.header.HeaderWriterFilter, 
org.springframework.security.web.authentication.logout.LogoutFilter, 
org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter, 
org.springframework.security.web.authentication.www.BasicAuthenticationFilter, 
org.springframework.security.web.savedrequest.RequestCacheAwareFilter, 
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter, 
org.springframework.security.web.session.SessionManagementFilter, 
org.springframework.security.web.access.ExceptionTranslationFilter, 
org.springframework.security.web.access.intercept.FilterSecurityInterceptor
]

因为我请求 Basic 要选择 BasicAuthenticationFilter处理才是正确的,也因为 request = /oauth/token/ 和 pattern='/oauth/token’匹配不上选错了filters,导致找不到对应的filter 处理,最终导致 TokenEndpoint的请求参数principal 为空 抛出 throw new InsufficientAuthenticationException(“There is no client authentication. Try adding an appropriate authentication filter.”); 至此报错原因,源码级别分析到此结束

    @RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
	public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal,
	@RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
		if (!(principal instanceof Authentication)) {
			throw new InsufficientAuthenticationException(
					"There is no client authentication. Try adding an appropriate authentication filter.");
		}
        // 省略代码
        ...
    }

断点的类

在这里插入图片描述

end 谢谢

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐