赞
踩
授权认证服务通过认证后会返回Access Token,该token可用于请求资源服务(业务系统)的接口。我们需要把特定的信息放到请求头中,例如在请求头中写入Authorization: Bearer !xBYUEBY0N3o234N,Authorization为key,Bearer !xBYUEBY0N3o234N为value,!xBYUEBY0N3o234N是Access Token。请求经过OAuth2AuthenticationProcessingFilter后,过滤器中的认证管理器会调用配置的授权认证服务的check token接扣校验token是否有效,token有效则可以继续访问了。需要注意的是,对于资源认证服务Spring Security会把这个过滤器加入到过滤链里,但授权认证认证服务却不能。
笔者的Spring Cloud项目分ums[用户管理]服务以及auth[授权认证]服务,ums是一个业务系统,ums借助授权认证认证服务需要配置client-id,client-secret,token-info-uri等,token-info-uri即auth[授权认证]服务校验token的url,配置如下:
security:
oauth2:
client:
client-id: carp
client-secret: carp
resource:
id: carp-ums
token-info-uri: http://carp-auth:8002/oauth2/token/check
loadBalanced: true
我们doFilter()方法中的代码进行分析,tokenExtractor.extract(request)从请求中取Bearer Token并包装成Authentication,然后调用authenticationManager.authenticate()向授权认证服务发起请求,对token进行认证。
认证成功后,发送认证成功事件,并把身份认证信息写入上下文,代码如下:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { final boolean debug = logger.isDebugEnabled(); final HttpServletRequest request = (HttpServletRequest) req; final HttpServletResponse response = (HttpServletResponse) res; try { //此处代码逻辑很简单 //Header中取出Authorization中的access token 并包装成Authentication,Authentication的principal就是token Authentication authentication = tokenExtractor.extract(request); //authentication不存在,清空上下文 if (authentication == null) { if (stateless && isAuthenticated()) { SecurityContextHolder.clearContext(); } } else { //在request属性中写入OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE:xxxxx xxxx是token request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal()); //对于Bearer Token而言,authentication是PreAuthenticatedAuthenticationToken的实例,所以会执行到if分支里 if (authentication instanceof AbstractAuthenticationToken) { AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication; //buildDetails其实就是创建一个OAuth2AuthenticationDetails并放入remoteAddress、sessionId、tokenType、tokenValue信息 needsDetails.setDetails(authenticationDetailsSource.buildDetails(request)); } //调用RemoteTokenServices向授权认证服务发起请求,进行token校验。 Authentication authResult = authenticationManager.authenticate(authentication); //发布一个身份认证成功的事件 eventPublisher.publishAuthenticationSuccess(authResult); //上下文中写入身份认证信息 SecurityContextHolder.getContext().setAuthentication(authResult); } } catch (OAuth2Exception failed) { //清空上下文 SecurityContextHolder.clearContext(); //发布一个身份认证失败的事件 eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed), new PreAuthenticatedAuthenticationToken("access-token", "N/A")); //进行错误处理 authenticationEntryPoint.commence(request, response, new InsufficientAuthenticationException(failed.getMessage(), failed)); return; } chain.doFilter(request, response); }
authenticationDetailsSource**.**buildDetails()最终会执行到下面OAuth2AuthenticationDetails的构造方法,代码逻辑也十分简单,其实就是取出request中的相关信息,进行了一次包装,如下:
public OAuth2AuthenticationDetails(HttpServletRequest request) { this.tokenValue = (String) request.getAttribute(ACCESS_TOKEN_VALUE); this.tokenType = (String) request.getAttribute(ACCESS_TOKEN_TYPE); this.remoteAddress = request.getRemoteAddr(); HttpSession session = request.getSession(false); this.sessionId = (session != null) ? session.getId() : null; StringBuilder builder = new StringBuilder(); if (remoteAddress!=null) { builder.append("remoteAddress=").append(remoteAddress); } if (builder.length()>1) { builder.append(", "); } if (sessionId!=null) { builder.append("sessionId=<SESSION>"); if (builder.length()>1) { builder.append(", "); } } if (tokenType!=null) { builder.append("tokenType=").append(this.tokenType); } if (tokenValue!=null) { builder.append("tokenValue=<TOKEN>"); } this.display = builder.toString(); }
authenticationManager是OAuth2AuthenticationManager的实例,其中tokenServices是RemoteTokenServices的实例,如果授权认证服务checkToken返回的数据结构体被自定过,与Spring Security原来返回的不一致,则需要对RemoteTokenServices进行改造,并替换默认实例。因为RemoteTokenServices就是发送一个http请求,解析返回结果,进行数据包装。当然,Spring Security只是一个授权认证框架,如果我们使用的Dubbo Rpc,也可以进行改造,注入Dubbo Rpc接口实现ResourceServerTokenServices接口即可。有些变量命名比较抽象,截图以及的OAuth2AuthenticationManager代码如下:
public Authentication authenticate(Authentication authentication) throws AuthenticationException { if (authentication == null) { throw new InvalidTokenException("Invalid token (token not found)"); } String token = (String) authentication.getPrincipal(); //默认是调用RemoteTokenService向授权认证服务的checkToken接口发起http Token验证请求 //验证通过后包装成OAuth2Authentication OAuth2Authentication auth = tokenServices.loadAuthentication(token); //token无效 if (auth == null) { throw new InvalidTokenException("Invalid token: " + token); } //判断从接口获取的授权信息中的资源id,是否包含业务系统配置的资源id Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds(); if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) { throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")"); } //校验客户端id checkClientDetails(auth); //进行details拷贝 if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) { OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails(); // Guard against a cached copy of the same details if (!details.equals(auth.getDetails())) { // Preserve the authentication details from the one loaded by token services details.setDecodedDetails(auth.getDetails()); } } //将details写入到身份认证信息中 auth.setDetails(authentication.getDetails()); //认账状态设置为已认证 auth.setAuthenticated(true); return auth; } private void checkClientDetails(OAuth2Authentication auth) { if (clientDetailsService != null) { ClientDetails client; try { //调用clientDetailsService获取客户端详情 client = clientDetailsService.loadClientByClientId(auth.getOAuth2Request().getClientId()); } catch (ClientRegistrationException e) { throw new OAuth2AccessDeniedException("Invalid token contains invalid client id"); } //判断客户端配置的scope 是否包含http request的中scope参数值 Set<String> allowed = client.getScope(); for (String scope : auth.getOAuth2Request().getScope()) { if (!allowed.contains(scope)) { throw new OAuth2AccessDeniedException( "Invalid token contains disallowed scope (" + scope + ") for this client"); } } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。