【HZERO微服务平台3】源码分析之oauth服务token生成、校验、获取信息、传递 - InfoQ...
source link: https://xie.infoq.cn/article/ee2ea70ee8b536519f639c350
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
【HZERO 微服务平台 3】源码分析之 oauth 服务 token 生成、校验、获取信息、传递
- 2021 年 12 月 13 日
本文字数:3433 字
阅读完需:约 11 分钟
hzero-oauth
服务是基于 spring security
、spring security oauth
、JWT
实现的统一认证服务中心,支持 oauth2.0
的四种授权模式:授权码模式
、简化模式
、密码模式
、客户端模式
,授权流程跟标准的 oauth2 流程一致。web 端采用简化模式(implicit)
登录系统,移动端可使用密码模式(password)
登录系统 。
完整的功能介绍: 认证服务
深入了解 hzero-oauth 需要熟练使用 spring security oauth, 简单描述一下它的功能:
oauth2 是开放的标准协议, spring security oauth 提供了实现, 授权中心(authorization server)用@EnableAuthorizationServer
及相关配置实现, 资源服务(resource server)用@EnableResourceServer
及相关配置实现;授权中心提供授权(/oauth/authorize)、获取 token(/oauth/token)等接口, 资源服务实现对 token 的校验、信息提取; hzero 的 oauth 服务既是授权中心也是资源服务;
OAuth2.0 的 RFC 文档: RFC 6749 - The OAuth 2.0 Authorization Framework
spring 官方开发文档: OAuth 2 Developers Guide
服务间 token 的传递过程流程图
前端使用 oauth2 流程获取 token(uuid 格式), 之后的请求必须携带 token, token 在服务间传递的示意图:
前端获取的 uuid 格式的 token(相当于 sessionId), 传递给网关;
网关使用 uuid token 获取用户信息, 把用户信息转换 jwt token, 并添加到
jwt_token
header 里, 传递到后端服务; 如果获取用户信息失败, 直接返回 401(认证失败);后端服务从 jwt_token 里解析、获取用户信息;
从 oauth 服务获取 token 的过程
调用post /oauth/token
接口获取 token 的过程:
TokenEndpoint#postAccessToken
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest); //责任链模式, 每种授权模式对应一个granter
AbstractTokenGranter#grant
ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
AbstractTokenGranter#getAccessToken
return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
AbstractTokenGranter#getOAuth2Authentication //这个方法会被子类granter覆写
return new OAuth2Authentication(storedOAuth2Request, null);
DefaultTokenServices#createAccessToken(OAuth2Authentication) //hzero修改版
DefaultTokenServices#createAccessToken(OAuth2Authentication , OAuth2RefreshToken) //hzero修改版
return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
调试技巧: 在最内层的方法上打断点, 看调用堆栈;
"hzero 修改版"表示: hzero 直接把 spring 的某些代码保留包名、类名复制到了项目里, 相当于直接替换了源码, 一种不太好的 hack 方法;
AbstractTokenGranter
的子类AuthorizationCodeTokenGranter、ImplicitTokenGranter、ClientCredentialsTokenGranter、ResourceOwnerPasswordTokenGranter
分别对应四种授权模式, 可以增加新的 Granter, 优雅的实现新的认证方式.调用过程的阅读方式: 如何以纯文本方式快速记录java代码的调用过程
oauth 服务校验 token 的过程
携带 token 调用接口时, 对 token 的检验过程:
OAuth2AuthenticationProcessingFilter#doFilter
Authentication authResult = authenticationManager.authenticate(authentication);
OAuth2AuthenticationManager#authenticate
OAuth2Authentication auth = tokenServices.loadAuthentication(token);
DefaultTokenServices#loadAuthentication
OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);
CustomRedisTokenStore#readAccessToken //从redis里读取、反序列化
如果快过期, 自动延长有效时间;
DefaultTokenServices#loadAuthentication
//如果快过期, 自动增加有效时间;
if (accessToken.getExpiresIn() < 3600) {
Long deltaMs = 4 * 3600 * 1000L; //4小时, 单位是毫秒;
((DefaultOAuth2AccessToken) accessToken).setExpiration(new Date(System.currentTimeMillis() + deltaMs));
tokenStore.storeAccessToken(accessToken, result);
}
gateway 获取用户信息(principal)的过程
gateway 把 uuid 转换为 jwt 是在
AddJwtFilter
用户信息最终是 oauth 服务从
CustomRedisTokenStore
里读取的;
gateway 服务里:从 gateway 调用非 public 的任意接口时:
GetUserDetailsFilter#run
CustomUserDetailsWithResult result = this.getUserDetailsService.getUserDetails(accessToken);
GetUserDetailsServiceImpl#getUserDetails //调用oauth服务的/oauth/api/user
注意: oauth/api/user
接口是 within 接口, 直接从网关调用会报错: error.permission.withinForbidden
<oauth><status>PERMISSION_WITH_IN</status><code>error.permission.withinForbidden</code><message>No access to within interface</message></oauth>
oauth 服务里:
// oauth/api/user
OauthController#user
return principal;
principal 来自 SecurityContext, SecurityContext 来自 OAuth2AuthenticationProcessingFilter:
OAuth2AuthenticationProcessingFilter#doFilter
Authentication authResult = authenticationManager.authenticate(authentication);
OAuth2AuthenticationManager#authenticate
OAuth2Authentication auth = tokenServices.loadAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authResult);
关于additionInfo
字段:
DefaultTokenServices#loadAuthentication 的返回结果包含 additionInfo, 但序列化的之后不包含, 因为 spring 添加了 ignore 注解;
principal 序列化把 additionInfo 字段里信息, 放到了和 client_id 同级的位置;
oauth 服务创建用户信息(principal)的过程
principal 来自Object SecurityContext.getAuthentication().getPrincipal()
, Object 具体是什么类型需要看AuthenticationToken
设置了什么值;
client_credentials 模式
principal 是CustomClientDetails
类型:
...
ClientCredentialsTokenGranter#grant
AbstractTokenGranter#grant
ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
CustomClientDetailsService#loadClientByClientId
clientDetailsWrapper.warp(clientDetails, client.getId(), client.getOrganizationId()); //角色、租户等信息来自这里
return getAccessToken(client, tokenRequest);
AbstractTokenGranter#getAccessToken
return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
ClientCredentialsTokenGranter#getOAuth2Authentication //hzero修改版
return new ClientOAuth2Authentication(storedOAuth2Request, new ClientAuthenticationToken(client)); //new ClientAuthenticationToken(client)的入参client是principal, 是CustomClientDetails
password 模式
principal 是CustomUserDetails
类型:
...
ResourceOwnerPasswordTokenGranter#getOAuth2Authentication
userAuth = authenticationManager.authenticate(userAuth);
ProviderManager#authenticate
AbstractUserDetailsAuthenticationProvider#authenticate
user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);
CustomAuthenticationProvider#retrieveUser
return getUserDetailsService().loadUserByUsername(username);
CustomUserDetailsService#loadUserByUsername
return createSuccessAuthentication(principalToReturn, authentication, user);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
业务服务从 jwt_token 获取用户信息的过程
调试思路: 给JwtTokenExtractor
打断点, 看调用堆栈;
业务服务里 hzero 没有用 spring oauth 的@EnableResourceServer
, 自定义了JwtTokenFilter
, 相当于OAuth2AuthenticationProcessingFilter
的功能:
JwtTokenFilter#doFilter
Authentication authentication = this.tokenExtractor.extract(httpRequest);
Authentication authResult = this.authenticate(authentication);
JwtTokenFilter#authenticate
this.tokenServices.loadAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authResult);
使用方法: 封装好的方法:DetailsHelper.getUserDetails()
qiaoxingxing
还未添加个人签名 2021.12.07 加入
还未添加个人简介
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK