IdentityServer4源码解析_5_查询用户信息接口
目录
- 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();
}
IdentityServer4源码解析_5_查询用户信息接口的更多相关文章
- IdentityServer4源码解析_1_项目结构
目录 IdentityServer4源码解析_1_项目结构 IdentityServer4源码解析_2_元数据接口 IdentityServer4源码解析_3_认证接口 IdentityServer4 ...
- identityserver4源码解析_2_元数据接口
目录 identityserver4源码解析_1_项目结构 identityserver4源码解析_2_元数据接口 identityserver4源码解析_3_认证接口 identityserver4 ...
- identityserver4源码解析_3_认证接口
目录 identityserver4源码解析_1_项目结构 identityserver4源码解析_2_元数据接口 identityserver4源码解析_3_认证接口 identityserver4 ...
- IdentityServer4源码解析_4_令牌发放接口
目录 identityserver4源码解析_1_项目结构 identityserver4源码解析_2_元数据接口 identityserver4源码解析_3_认证接口 identityserver4 ...
- Mybaits 源码解析 (三)----- Mapper接口底层原理(为什么Mapper不用写实现类就能访问到数据库?)
上一篇我们讲解到mapperElement方法用来解析mapper,我们这篇文章具体来看看mapper.xml的解析过程 mappers配置方式 mappers 标签下有许多 mapper 标签,每一 ...
- Mybatis源码解析-MapperRegistry代理注册mapper接口
知识储备 SqlsessionFactory-mybatis持久层操作数据的前提,具体的解析是通过SqlSessionFactoryBean生成的,可见>>>Spring mybat ...
- 【JUC源码解析】ForkJoinPool
简介 ForkJoin 框架,另一种风格的线程池(相比于ThreadPoolExecutor),采用分治算法,工作密取策略,极大地提高了并行性.对于那种大任务分割小任务的场景(分治)尤其有用. 框架图 ...
- Spring BeanDefinitionHolder源码解析
BeanDefinitionHolder源码解析 继承关系 实现的接口 和BeanDefinition一样实现了BeanMetadataElement接口,获得了获取数据源(配置类的class对象)的 ...
- 利用数据库视图实现WEB查询敏感信息接口动态脱敏
前言: 利用数据库视图,实现web接口查询敏感信息时动态脱敏. 具体目标:某接口为用户信息查询接口,返回敏感用户信息(id,姓名.手机号[敏感].身份证号[敏感]),如果web用户为管理员角色,则查询 ...
随机推荐
- Python 破解极验滑动验证码
Python 破解极验滑动验证码 测试开发社区 1周前 阅读目录 极验滑动验证码 实现 位移移动需要的基础知识 对比两张图片,找出缺口 获得图片 按照位移移动 详细代码 回到顶部 极验滑动验证码 以 ...
- 淘宝网-接口测试白皮书V0.1
<软件自动化测试开发> 出版了 淘宝(中国)软件有限公司 接口测试白皮书 V0.1 淘宝网平台测试组(qa.taobao.com) 淘宝网-接口测试白皮书 2 目录 1 接口测试的背 ...
- CSS 双飞翼布局
10 Jul 2016 » CSS 双飞翼布局:总共分三栏,左侧栏Left,中间主栏Main,右侧栏Right 第一步,建立三个div,不过注意,中间Main需要加一个wrap div. 整个结构看起 ...
- HTML5全屏背景视频与 CSS 和 JS(插件或库)
译文原链接:http://codetheory.in/html5-fullscreen-background-video/ 前言: 当网页载入时,自动播放的全屏背景视频已经成为当前颇受欢迎的趋势. 就 ...
- nginx设置目录浏览及中文乱码问题解决
在Nginx下默认是不允许列出整个目录的.如需此功能, 先打开nginx.conf文件,在location server 或 http段中加入 autoindex on;另外两个参数最好也加上去: a ...
- 攻防世界Mobile5 EasyJNI 安卓逆向CTF
EasyJNI 最近正好在出写JNI,正好看到了一道JNI相关的较为简单明了的CTF,就一时兴起的写了,不得不说逆向工程和正向开发确实是可以互补互相加深的 JNI JNI(Java Native In ...
- 第一篇博客 C+++知识点总结一
1.成员 1.比较特殊的成员类型:protected. 保护成员在本类中和private类型的成员作用一模一样.区别在于保护成员可以由本类的派生类的成员函数访问,但是私有成员在其派生类中无法访问. 2 ...
- 实验三——NFS服务器配置
实验三——NFS服务器配置 实 验 基 本 信 息 实验名称:NFS服务器配置(3学时) 实验时间: 年 月 日 实验地点: 信工606实验室 同组同学: 实验目的: 了解NFS服务的基本原 ...
- 《JavaScript 模式》读书笔记(2)— 基本技巧1
这篇文章的主要内容,介绍了一些js编程中的基本技巧,其实这些技巧,大家在开发的过程中,或多或少都在使用,或者已经可以熟练的应用于自己的代码或项目中了.那么,这篇文章,就一起来回顾下这些“基本技巧”. ...
- SpringBoot内置的各种Starter是怎样构建的?--SpringBoot源码(六)
注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 温故而知新 本篇接 外部配置属性值是如何被绑定到XxxProperties类属性上的?--SpringBoot源码(五) 温 ...