IdentityServer4源码解析_5_查询用户信息接口

目录

协议简析

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": "[email protected]",
"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();
}

原文地址:https://www.cnblogs.com/holdengong/p/12594007.html

时间: 2024-10-29 10:46:23

IdentityServer4源码解析_5_查询用户信息接口的相关文章

IdentityServer4源码解析_1_项目结构

目录 identityserver4源码解析_1_项目结构 identityserver4源码解析_2_元数据接口 identityserver4源码解析_3_认证接口 identityserver4源码解析_4_令牌发放接口 identityserver4源码解析_5_查询用户信息接口 identityserver4源码解析_6_结束会话接口 identityserver4源码解析_7_查询令牌信息接口 identityserver4源码解析_8_撤销令牌接口 简介 Security源码解析系

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

目录 identityserver4源码解析_1_项目结构 identityserver4源码解析_2_元数据接口 identityserver4源码解析_3_认证接口 identityserver4源码解析_4_令牌发放接口 identityserver4源码解析_5_查询用户信息接口 identityserver4源码解析_6_结束会话接口 identityserver4源码解析_7_查询令牌信息接口 identityserver4源码解析_8_撤销令牌接口 协议 Token接口 oidc服

rest_framework-00-规范-APIview源码解析-认证

rest_framework-00-规范-APIview源码解析-认证 规范 支付宝: 接口开发 订单api----order 方式1:缺点:如果有10张表,则需要40个url. urls.py views.py 缺点:如果有10张表,则需要40个url.    接下来就出现了resrful 规范,比较简洁 方式2:resrful 规范(建议)  url简洁了,只有一条. 1. 根据method不同做不同的操作,示例:基于FBV: urls.py views.py 2. 根据method不同做不

Spring-web源码解析之ContentNegotiationStrategy

创建ContentNegotiationStrategy组件源码,参考:org.springframework.web.accept.ContentNegotiationManagerFactoryBean#afterPropertiesSet Spring-web源码解析之ContentNegotiationStrategy 基于4.1.7.RELEASE request和mediatypes解析的策略类,其唯一的一个接口是 List<MediaType> resolveMediaTypes

SpringSecurity 依据用户请求的过程进行源码解析

SpringSecurity实现安全管理主要通过滤器(filter).验证器(AuthenticationManager).用户数据提供器(ProviderManager).授权器(accessDecisionManager).投票器(AccessDecisionVoter)这几个基本模块协作完成的.大概分为两个部分 用户验证 和授权 这个两个部分.这个部分主要在AuthenticationProcessingFilter和AbstractSecurityInterceptor中完成. 使用过S

Android应用源码列车车次与航班信息查询

项目简介:本 项目是一个查询火车车次信息和飞机航班信息的应用源码,可以查询一个城市到另外一个城市的车次和班次的信息,并且可以加入收藏以供日后使用,火车车次信息 和飞机航班信息数据都是由webservice.webxml.com.cn提供,查询出以后可以把车次信息或者航班信息添加到收藏列表,本项目默认编译 版本4.4.2编码GBK有大量中文注释,可以很好的学习和借鉴ListView.SQLite.XmlPullParser的使用方法. 运行截图:

YTKNetwork源码解析

对于iOS开发者来说,就算是没有用过YTKNetwork框架,应该也见过,听过了.它是猿题库技术团队开源的一个网络请求框架,内部封装了AFNetworking.它把每个请求实例化,管理它的生命周期,也可以管理多个请求. 在正式讲解源码之前,我会先讲一下该框架所用的架构和设计模式.我总觉得对架构和设计有一定的了解的话,会有助于对源码的理解. 1. 架构 先上图: YTKRequest架构图 在这里简单说明一下: YTKNetwork框架将每一个请求实例化,YTKBaseRequest是所有请求类的

Retrofit源码解析

square公司开源了一系列的优秀库,比如Retrofit,OkHttp,Picasso等, 前面简单分析了Picasso的源码,这里来分析下Retrofit的使用: 一.gradle添加依赖 compile 'com.squareup.okhttp:okhttp:2.4.0' compile 'com.squareup.okhttp:okhttp-urlconnection:2.4.0' compile 'com.squareup.okio:okio:1.5.0' compile 'com.g

SDWebImage源码解析(二)

源码来源: https://github.com/rs/SDWebImage 版本: 3.7 SDWebImage是一个开源的第三方库,它提供了UIImageView的一个分类,以支持从远程服务器下载并缓存图片的功能.它具有以下功能: 提供UIImageView的一个分类,以支持网络图片的加载与缓存管理 一个异步的图片加载器 一个异步的内存+磁盘图片缓存 支持GIF图片 支持WebP图片 后台图片解压缩处理 确保同一个URL的图片不被下载多次 确保虚假的URL不会被反复加载 确保下载及缓存时,主