获的了access_token怎么使用?我们历经千辛万苦,才获取到access_token,那么获取到了access_token如何使用access_token?
1、单独的资源服务器如何使用access_token
在《30、oauth2.0认证服务器与资源服务器分离成不同服务(进程)或既是认证服务又是资源服务器》一文中,我们后面一段代码解释了核心过滤器 OAuth2AuthenticationProcessingFilter处理流程,BearerTokenExtractor获取token的解析流程:
(1)从Http的头Header中查找Authentication头且为Bearer验证
(2)如果(1)中找不到,则从RequestParam中查找,也就是url请求参数中查找
(3)如果(2)中找找不到,则从form表单中查找;
所以使用access_token有三种方式:
(1) 在Header中携带
Header:
Authentication:Bearer f732723d-af7f-41bb-bd06-2636ab2be135
(2) 拼接在url中作为requestParam
http://localhost:8080/order/1?access_token=f732723d-af7f-41bb-bd06-2636ab2be135
(3)在form表单中携带
form param:
access_token=f732723d-af7f-41bb-bd06-2636ab2be135
2、认证服务器同时也是资源服务器情况如何使用access_token
(1)如果客户端访问资源服务器(间接访问授权服务器,资源服务器与授权服务器分离《同时授权服务也是资源服务-提供用户权限等详细信息查询》)
经过确认,如果是资源服务器访问异地的认证服务器,使用的方式为(资源服务器配置方式为使用token)check_token,具体示例如下:
security:
oauth2:
resource:
filter-order: 3
id: test_resource_id
tokenInfoUri:http://localhost:7006/oauth/check_token
preferTokenInfo: true
client:
accessTokenUri:http://localhost:7006/oauth/token
userAuthorizationUri:http://localhost:7006/oauth/authorize
clientId: wx_takeout_client_id
clientSecret: wx_takeout_client_secret
也就是说,如果访问资源服务器的资源并携带上access_token(用以上3中方式) 那么资源服务器则会将传递给资源服务器的access_token到认证服务器去验证,通过则可以访问,否则无权访问;
具体流程是资源服务器带上access_token后访问check_token地址,将token转为http的头(至于如何转换我没有具体跟踪,感兴趣的自己可以跟踪一下),头中包含了Authentication授权字段。该字段值为client_id和client_secret的Basic值(与直接访问授权服务器的字段值不一样)进行校验最终使用ClientCredentialsTokenEndpointFilter认证。该过滤器会使用提取client_id然后从数据库中查找client_id的记录权限信息。验证无效则无法访问。
(2)如果客户端访问授权服务器(也是资源服务器)
经过确认,授权服务器同时也是资源服务器的时候,是不需要像资源服务器那样在配置文件中配置,这样的话客户端访问的URL是不会通过check_token进行校验的,会通过BasicAuthenticationFilter进行校验,也就是必须是Http的Header中包含Authentication且对应值为Basic的用户名和密码(此时不是client_id和client_secret)加密,才能通过。否则会报401错误。
原因:
(1)Spring初始化时,会初始化10个过滤器链,对应过滤器链如下
ApplicationFilterConfig[name=characterEncodingFilter, filterClass=org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter]
ApplicationFilterConfig[name=webMvcMetricsFilter, filterClass=org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter]
ApplicationFilterConfig[name=hiddenHttpMethodFilter, filterClass=org.springframework.boot.web.servlet.filter.OrderedHiddenHttpMethodFilter]
ApplicationFilterConfig[name=httpPutFormContentFilter, filterClass=org.springframework.boot.web.servlet.filter.OrderedHttpPutFormContentFilter]
ApplicationFilterConfig[name=requestContextFilter, filterClass=org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter]
ApplicationFilterConfig[name=springSecurityFilterChain, filterClass=org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean$1]
ApplicationFilterConfig[name=httpTraceFilter, filterClass=org.springframework.boot.actuate.web.trace.servlet.HttpTraceFilter]
ApplicationFilterConfig[name=webStatFilter, filterClass=com.alibaba.druid.support.http.WebStatFilter]
ApplicationFilterConfig[name=resourceUrlEncodingFilter, filterClass=org.springframework.web.servlet.resource.ResourceUrlEncodingFilter]
ApplicationFilterConfig[name=Tomcat WebSocket (JSR356) Filter, filterClass=org.apache.tomcat.websocket.server.WsFilter]
其中第6个过滤器链就是oauth2.0的权限过滤过滤器链。我们只要关注它即可。所有过滤器的处理流程ApplicationFilterChain中进行循环调用所有处理器链:
循环跳过前5个过滤器链,直接跳转到第6个过滤器链
接下来就是调用改过滤器链的过程:
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// Lazily initialize the delegate if necessary.
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized (this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: " +
"no ContextLoaderListener or DispatcherServlet registered?");
}
delegateToUse = initDelegate(wac);
}
this.delegate = delegateToUse;
}
}
// Let the delegate perform the actual doFilter operation.
invokeDelegate(delegateToUse, request, response, filterChain);
}
这里又使用了过滤器链代理,使用代理获取所有过滤器链的所有过滤器,然后逐个执行所有的过滤器。
private void doFilterInternal(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = firewall
.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse fwResponse = firewall
.getFirewalledResponse((HttpServletResponse) response);
List<Filter> filters = getFilters(fwRequest);
if (filters == null || filters.size() == 0) {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(fwRequest)
+ (filters == null ? " has no matching filters"
: " has an empty filter list"));
}
fwRequest.reset();
chain.doFilter(fwRequest, fwResponse);
return;
}
VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
vfc.doFilter(fwRequest, fwResponse);
}
重点就是过滤器链中的代理获取过滤器列表的过程(可以理解为过滤器链中就包含了多个过滤器--就是列表):
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
调试到这里发现,过滤器是根据请求参数(这里是请求路径url)动态添加到列表中的,不同的url处理的过滤器连表都不一样.
a. 如果是与终端请求相关(如/oauth/authorize或check_token等)则会使用过滤器列表:
[
OrRequestMatcher
[
requestMatchers=
[
Ant [pattern='/oauth/token'],
Ant [pattern='/oauth/token_key'],
Ant [pattern='/oauth/check_token'
]
],
[
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@51131f0,
org.springframework.security.web.context.SecurityContextPersistenceFilter@7c93a0b4,
org.springframework.security.web.header.HeaderWriterFilter@4a2c416a,
org.springframework.security.web.authentication.logout.LogoutFilter@cdea588,
org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter@5219e424,
org.springframework.security.web.authentication.www.BasicAuthenticationFilter@2ebf71de,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@1a0a059c,
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@e944bb2,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@1756f30,
org.springframework.security.web.session.SessionManagementFilter@fcbdef7,
org.springframework.security.web.access.ExceptionTranslationFilter@6310bdff,
org.springframework.security.web.access.intercept.FilterSecurityInterceptor@40cbb015
]
]
也即是资源服务器使用check_token访问授权服务器的时候,会使用上面的过滤器列表,其中包含了一个ClientCredentialsTokenEndpointFilter,也就是关键,这个filter会根据http头部的client_id和client_secret(具体是client_id)从数据库中查询出对应的记录。验证是否有对应权限。
b. 如果是不与终端相关的(也即是客户端直接访问对应资源的url,不经过单独的资源服务器中转验证)
这时候的访问的url不是check_token,而直接就是客户端访问的资源的url,如(/user/principal).如果是这样:
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
匹配到的过滤器列表对应值就是:
[
org.springframework.security.web.util.matcher.AnyRequestMatcher@1,
[
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@200a66e2, org.springframework.security.web.context.SecurityContextPersistenceFilter@39ab3107, org.springframework.security.web.header.HeaderWriterFilter@95f952, org.springframework.security.web.authentication.logout.LogoutFilter@7aa398ea, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@2f45d0a7, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@1ae0b1b8, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@6cf0996f, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@6e3b967b, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@41cbbd87, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@1fab4d5d, org.springframework.security.web.session.SessionManagementFilter@4453e472, org.springframework.security.web.access.ExceptionTranslationFilter@3fa97ae, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@2d52498f
]
]
这个时候就不会有ClientCredentialsTokenEndpointFilter而是多了一个BasicAuthenticationFilter,这个过滤器则是使用自定义的UserDetailImpl接口实现通过用户名获取用户信息。不论是资源服务器访问还是客户端直接访问授权服务器,http的header中必须包含Authentication字段,对应值都为basic加密后的值,如果是资源服务器访问则为client_id和client_secret的加密值,如果是客户端直接访问授权服务,则是用户名和密码的加密值(如admin,123456)。具体验证头过程在BasicAuthenticationFilter中:
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
final boolean debug = this.logger.isDebugEnabled();
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Basic ")) {
chain.doFilter(request, response);
return;
}
try {
String[] tokens = extractAndDecodeHeader(header, request);
assert tokens.length == 2;
String username = tokens[0];
if (debug) {
this.logger
.debug("Basic Authentication Authorization header found for user '"
+ username + "'");
}
if (authenticationIsRequired(username)) {
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, tokens[1]);
authRequest.setDetails(
this.authenticationDetailsSource.buildDetails(request));
Authentication authResult = this.authenticationManager
.authenticate(authRequest);
if (debug) {
this.logger.debug("Authentication success: " + authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
this.rememberMeServices.loginSuccess(request, response, authResult);
onSuccessfulAuthentication(request, response, authResult);
}
}
catch (AuthenticationException failed) {
SecurityContextHolder.clearContext();
if (debug) {
this.logger.debug("Authentication request for failed: " + failed);
}
this.rememberMeServices.loginFail(request, response);
onUnsuccessfulAuthentication(request, response, failed);
if (this.ignoreFailure) {
chain.doFilter(request, response);
}
else {
this.authenticationEntryPoint.commence(request, response, failed);
}
return;
}
chain.doFilter(request, response);
}
以上就是提取Authentication的过程,如果没有或不是basic就无法通过验证(所以资源服务或直接访问授权服务都必须最后转为header且包含Authentication值为basic),接下来就是authenticate验证过程,如果是ClientCredentialsTokenEndpointFilter则UserDetail为ClientCredentialsService从数据库中通过client_id获取授权信息,否则通过UserDetailImpl(自己实现的接口)获取授权信息。
总结:
(1) 认证服务器配置了资源服务器,无法使用access_token访问对应资源,这可能是一个bug或可能本人在本文中使用方法不对(如果谁知道,麻烦通知我或给我留言,感激不尽)。
(2)资源服务器(分离模式)通过access_token访问有三种模式,bearer授权头、请求参数,form表单;
(3)访问认证服务器资源使用用户名密码(非client_id或client_secret)的basic授权头模式或通过资源服务器间接访问,访问资源服务器可以通过access_token访问。
快来成为我的朋友或合作伙伴,一起交流,一起进步!
QQ群:961179337
微信:lixiang6153
邮箱:lixx2048@163.com
公众号:IT技术快餐
更多资料等你来拿!