目录

协议简析

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对入参进行校验。

  1. accessToken,必须包括openid声明的权限
  2. 必须有sub声明,subsubject的缩写,代表用户唯一标识
  3. 收集accessToken所有claim,移除以下与用户信息无关的claim

    at_hash,aud,azp,c_hash,client_id,exp,iat,iss,jti,nonce,nbf,reference_token_id,sid,scope

    用筛选后的claim创建名称为UserInfoPrincipal
  4. 调用IProfileServiceIsAcriveAsync方法判断用户是否启用,不是启动状态的话返回invalid_token错误
  5. 返回校验成功结果对象,包括步骤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接口的默认实现UserInfoResponseGeneratorProcessAsync方法生成响应报文。

  1. 从校验结果中获取scope声明值,查询scope值关联的IdentityResource(身份资源)及其关联的所有claim。得到的结果就是用户请求的所有claim
  2. 调用DefaultProfileServiceGetProfileDataAsync方法,返回校验结果claim与用户请求claim的交集。
  3. 如果claim集合中没有sub,取校验结果中的sub值。如果IProfileService返回的sub声明值与校验结果的sub值不一致抛出异常。
  4. 返回claim集合。
  5. 响应头写入Cache-Control:no-store, no-cache, max-age=0,Pragma:no-cache
  6. 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_查询用户信息接口的更多相关文章

  1. IdentityServer4源码解析_1_项目结构

    目录 IdentityServer4源码解析_1_项目结构 IdentityServer4源码解析_2_元数据接口 IdentityServer4源码解析_3_认证接口 IdentityServer4 ...

  2. identityserver4源码解析_2_元数据接口

    目录 identityserver4源码解析_1_项目结构 identityserver4源码解析_2_元数据接口 identityserver4源码解析_3_认证接口 identityserver4 ...

  3. identityserver4源码解析_3_认证接口

    目录 identityserver4源码解析_1_项目结构 identityserver4源码解析_2_元数据接口 identityserver4源码解析_3_认证接口 identityserver4 ...

  4. IdentityServer4源码解析_4_令牌发放接口

    目录 identityserver4源码解析_1_项目结构 identityserver4源码解析_2_元数据接口 identityserver4源码解析_3_认证接口 identityserver4 ...

  5. Mybaits 源码解析 (三)----- Mapper接口底层原理(为什么Mapper不用写实现类就能访问到数据库?)

    上一篇我们讲解到mapperElement方法用来解析mapper,我们这篇文章具体来看看mapper.xml的解析过程 mappers配置方式 mappers 标签下有许多 mapper 标签,每一 ...

  6. Mybatis源码解析-MapperRegistry代理注册mapper接口

    知识储备 SqlsessionFactory-mybatis持久层操作数据的前提,具体的解析是通过SqlSessionFactoryBean生成的,可见>>>Spring mybat ...

  7. 【JUC源码解析】ForkJoinPool

    简介 ForkJoin 框架,另一种风格的线程池(相比于ThreadPoolExecutor),采用分治算法,工作密取策略,极大地提高了并行性.对于那种大任务分割小任务的场景(分治)尤其有用. 框架图 ...

  8. Spring BeanDefinitionHolder源码解析

    BeanDefinitionHolder源码解析 继承关系 实现的接口 和BeanDefinition一样实现了BeanMetadataElement接口,获得了获取数据源(配置类的class对象)的 ...

  9. 利用数据库视图实现WEB查询敏感信息接口动态脱敏

    前言: 利用数据库视图,实现web接口查询敏感信息时动态脱敏. 具体目标:某接口为用户信息查询接口,返回敏感用户信息(id,姓名.手机号[敏感].身份证号[敏感]),如果web用户为管理员角色,则查询 ...

随机推荐

  1. [PyTorch入门]之迁移学习

    迁移学习教程 来自这里. 在本教程中,你将学习如何使用迁移学习来训练你的网络.在cs231n notes你可以了解更多关于迁移学习的知识. 在实践中,很少有人从头开始训练整个卷积网络(使用随机初始化) ...

  2. 【自己的下载平台】搭建aria2网站

    前言 本文章将带你搭建一个自己的服务器下载平台:aria2,它的用途是什么? 下载用途 百度网盘 普通文件 迅雷种子 等等 准备工具 服务器连接软件xshell或者putty 一台服务器 安装宝塔面板 ...

  3. python爬虫-提取网页数据的三种武器

    常用的提取网页数据的工具有三种xpath.css选择器.正则表达式 1.xpath 1.1在python中使用xpath必须要下载lxml模块: lxml官方文档 :https://lxml.de/i ...

  4. Nginx使用和配置

    概要: Nginx 简介 Nginx 架构说明 Nginx 基础配置与使用 Nginx 反向代理与负载均衡 Nginx 实现高速缓存 Nginx 性能参数调优 一.Nginx 简介与安装 Nginx ...

  5. Golang/Python/PHP带你彻底学会gRPC

    目录 一.gRPC是什么? 二.Protocol Buffers是什么? 三.需求:开发健身房服务 四.最佳实践 Golang 1. 安装protoc工具 2. 安装protoc-gen-go 3. ...

  6. 万字硬核干货!6大技巧,极速提升kubectl的生产力!

    明晚8:30,k3s实战课程开启!将由Rancher研发总监带你畅游k3s与边缘AI的奇妙世界.课程内容完全由实际使用场景中总结而来,别错过啦~!访问以下链接即可传送到课程现场: http://z-m ...

  7. Flutter01-学习准备

    1. 简介: Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面. Flutter可以与现有的代码一起工作.在全世界,Flutter正在被越来越多的开发者和 ...

  8. nodejs通过响应回写的方式渲染页面资源

    我们一般通过node框架提供的api操作页面渲染,如何利用原始回写的方式来实现同样的功能呢下面是通过node 提供的异步地读取一个文件的全部内容api readFile进行操作,代码如下: html ...

  9. java多线程基础API

    本次内容主要讲认识Java中的多线程.线程的启动与中止.yield()和join.线程优先级和守护线程. 1.Java程序天生就是多线程的 一个Java程序从main()方法开始执行,然后按照既定的代 ...

  10. docker学习之路

    环境 : ubuntu 16.4 下载docker 首先使用命令行下载 docker wget -qO- https://get.docker.com/ | sh 启动 下载完成之后进行一个启动,但是 ...