IdentityServer4源码解析_5_查询用户信息接口
2020-03-29 18:52
381 查看
目录
- IdentityServer4源码解析_1_项目结构
- IdentityServer4源码解析_2_元数据接口
- IdentityServer4源码解析_3_认证接口
- IdentityServer4源码解析_4_令牌发放接口
- IdentityServer4源码解析_5_查询用户信息接口
- [IdentityServer4源码解析_6_结束会话接口]
- [IdentityServer4源码解析_7_查询令牌信息接口]
- [IdentityServer4源码解析_8_撤销令牌接口]
协议简析
UserInfo接口是OAuth2.0中规定的需要认证访问的接口,可以返回认证用户的声明信息。请求UserInfo接口需要使用通行令牌。响应报文通常是json数据格式,包含了一组claim键值对集合。与UserInfo接口通讯必须使用https。
根据RFC2616协议,UserInfo必须支持GET和POST方法。
UserInfo接口必须接受Bearer令牌。
UserInfo接口应该支持javascript客户端跨域访问,可以使用CORS协议或者其他方案。
UserInfo请求
推荐使用GET方法,使用Authorization头承载Bearer令牌来请求UserInfo接口。
GET /userinfo HTTP/1.1 Host: server.example.com Authorization: Bearer SlAV32hkKG
成功响应
如果某个claim为空或者null,不返回该键。
必须返回sub(subject)声明。
必须校验UserInfo返回的sub与id_token中的sub是否一致
content-type必须是application/json,必须使用utf-8编码
如果加密位jwt返回,content-type必须位application/jwt
HTTP/1.1 200 OK Content-Type: application/json { "sub": "248289761001", "name": "Jane Doe", "given_name": "Jane", "family_name": "Doe", "preferred_username": "j.doe", "email": "janedoe@example.com", "picture": "http://example.com/janedoe/me.jpg" }
失败响应
HTTP/1.1 401 Unauthorized WWW-Authenticate: error="invalid_token", error_description="The Access Token expired"
响应校验
客户端必须校验如下内容
- 校验认证服务身份(https)
- 如果客户端注册时设置了userinfo_encrypted_response_alg ,收到响应时用对应算法解密
- 如果响应有签名,客户端需要验签
源码解析
校验通行令牌
- 首先会尝试从
Authorizaton
头中获取Bearer Token
的值,找到的话则返回 - 如果content-type为表单类型,尝试从表单中获取
access_token
参数值 - 两处都没有获取到
Beaer Token
的话则返回校验失败结果
public async Task<BearerTokenUsageValidationResult> ValidateAsync(HttpContext context) { var result = ValidateAuthorizationHeader(context); if (result.TokenFound) { _logger.LogDebug("Bearer token found in header"); return result; } if (context.Request.HasFormContentType) { result = await ValidatePostBodyAsync(context); if (result.TokenFound) { _logger.LogDebug("Bearer token found in body"); return result; } } _logger.LogDebug("Bearer token not found"); return new BearerTokenUsageValidationResult(); }
校验请求参数
由
IUserInfoRequestValidator的默认实现
UserInfoRequestValidator对入参进行校验。
accessToken
,必须包括openid
声明的权限- 必须有
sub
声明,sub
是subject
的缩写,代表用户唯一标识 - 收集
accessToken
所有claim
,移除以下与用户信息无关的claim
。
at_hash,aud,azp,c_hash,client_id,exp,iat,iss,jti,nonce,nbf,reference_token_id,sid,scope
用筛选后的claim
创建名称为UserInfo
的Principal
- 调用
IProfileService
的IsAcriveAsync
方法判断用户是否启用,不是启动状态的话返回invalid_token
错误 - 返回校验成功结果对象,包括步骤3构建的
Principal
public async Task<UserInfoRequestValidationResult> ValidateRequestAsync(string accessToken) { // the access token needs to be valid and have at least the openid scope var tokenResult = await _tokenValidator.ValidateAccessTokenAsync( accessToken, IdentityServerConstants.StandardScopes.OpenId); if (tokenResult.IsError) { return new UserInfoRequestValidationResult { IsError = true, Error = tokenResult.Error }; } // the token must have a one sub claim var subClaim = tokenResult.Claims.SingleOrDefault(c => c.Type == JwtClaimTypes.Subject); if (subClaim == null) { _logger.LogError("Token contains no sub claim"); return new UserInfoRequestValidationResult { IsError = true, Error = OidcConstants.ProtectedResourceErrors.InvalidToken }; } // create subject from incoming access token var claims = tokenResult.Claims.Where(x => !Constants.Filters.ProtocolClaimsFilter.Contains(x.Type)); var subject = Principal.Create("UserInfo", claims.ToArray()); // make sure user is still active var isActiveContext = new IsActiveContext(subject, tokenResult.Client, IdentityServerConstants.ProfileIsActiveCallers.UserInfoRequestValidation); await _profile.IsActiveAsync(isActiveContext); if (isActiveContext.IsActive == false) { _logger.LogError("User is not active: {sub}", subject.GetSubjectId()); return new UserInfoRequestValidationResult { IsError = true, Error = OidcConstants.ProtectedResourceErrors.InvalidToken }; } return new UserInfoRequestValidationResult { IsError = false, TokenValidationResult = tokenResult, Subject = subject }; }
生成响应报文
调用
IUserInfoResponseGenerator接口的默认实现
UserInfoResponseGenerator的
ProcessAsync方法生成响应报文。
- 从校验结果中获取
scope
声明值,查询scope
值关联的IdentityResource
(身份资源)及其关联的所有claim
。得到的结果就是用户请求的所有claim
- 调用
DefaultProfileService
的GetProfileDataAsync
方法,返回校验结果claim
与用户请求claim
的交集。 - 如果
claim
集合中没有sub
,取校验结果中的sub
值。如果IProfileService
返回的sub
声明值与校验结果的sub
值不一致抛出异常。 - 返回
claim
集合。 - 响应头写入
Cache-Control:no-store, no-cache, max-age=0
,Pragma:no-cache
claim
集合用json格式写入响应内容
public virtual async Task<Dictionary<string, object>> ProcessAsync(UserInfoRequestValidationResult validationResult) { Logger.LogDebug("Creating userinfo response"); // extract scopes and turn into requested claim types var scopes = validationResult.TokenValidationResult.Claims.Where(c => c.Type == JwtClaimTypes.Scope).Select(c => c.Value); var requestedClaimTypes = await GetRequestedClaimTypesAsync(scopes); Logger.LogDebug("Requested claim types: {claimTypes}", requestedClaimTypes.ToSpaceSeparatedString()); // call profile service var context = new ProfileDataRequestContext( validationResult.Subject, validationResult.TokenValidationResult.Client, IdentityServerConstants.ProfileDataCallers.UserInfoEndpoint, requestedClaimTypes); context.RequestedResources = await GetRequestedResourcesAsync(scopes); await Profile.GetProfileDataAsync(context); var profileClaims = context.IssuedClaims; // construct outgoing claims var outgoingClaims = new List<Claim>(); if (profileClaims == null) { Logger.LogInformation("Profile service returned no claims (null)"); } else { outgoingClaims.AddRange(profileClaims); Logger.LogInformation("Profile service returned the following claim types: {types}", profileClaims.Select(c => c.Type).ToSpaceSeparatedString()); } var subClaim = outgoingClaims.SingleOrDefault(x => x.Type == JwtClaimTypes.Subject); if (subClaim == null) { outgoingClaims.Add(new Claim(JwtClaimTypes.Subject, validationResult.Subject.GetSubjectId())); } else if (subClaim.Value != validationResult.Subject.GetSubjectId()) { Logger.LogError("Profile service returned incorrect subject value: {sub}", subClaim); throw new InvalidOperationException("Profile service returned incorrect subject value"); } return outgoingClaims.ToClaimsDictionary(); }
相关文章推荐
- PHP用户管理中常用接口调用实例及解析(含源码)
- 利用百度车联网提供的天气查询接口用python查询天气信息以及安卓(Java)利用gson解析数据
- .Net Core 认证系统之基于Identity Server4 Token的JwtToken认证源码解析
- python--字典--实战分析--用户信息查询接口设计
- Asp.NetCoreWebApi图片上传接口(二)集成IdentityServer4授权访问(附源码)
- 解析支付宝单笔买卖查询接口回来的XML信息
- 域名解析信息查询小工具 - 完整源码(微信小程序+php)免费下载
- FQL——facebook用户信息查询接口
- 简单理解 OAuth 2.0 及资料收集,IdentityServer4 部分源码解析
- v9用于静态页查询登陆状态以及用户信息的ajax接口
- C# AD(Active Directory)域信息同步,组织单位、用户等信息查询
- Java集合(5)——List接口与AbstractList抽象类源码解析
- 8、用户更新个人信息接口开发
- NET 实现自定义ContextUser的Identity和Principal实现自定义用户信息,权限验证
- Linux如何查询在线用户信息
- iphone 反向地理解析 从坐标获得用户的具体位置信息
- 查询用户注册状态 在线用户信息
- 【升级版最新双端直播盒子源码】带分销分润影视app安卓苹果ios/VIP视频解析免采集接口
- 更改Identity用户信息
- 注册中心 Eureka 源码解析 —— Eureka-Server 启动(二)之 EurekaBootStrap