ディレクトリ
- _1_プロジェクト構造を解析するIdentityServer4ソース
- IdentityServer4ソースメタデータの解析_2_インタフェース
- IdentityServer4ソース解析_3_認証インターフェース
- _4_トークン支払いインターフェースを解析IdentityServer4ソース
- IdentityServer4元ユーザインタフェース情報のクエリの解析_5_
- [IdentityServer4ソース解決は、セッションインターフェースを終了_6_]
- [インタフェーストークン問い合わせを解析IdentityServer4 _7_ソース]
- 【IdentityServer4ソースリボークトークンインターフェース解析_8_]
契約の分析
インタフェースにアクセスするには認証が必要なのUserInfoインターフェースはOAuth2.0を指定し、文はユーザーを認証するための情報を返すことがあります。UserInfoインターフェースは、利用要求トークンパスを必要とします。JSON応答パケットデータのフォーマットは、典型的には、セットを主張キーと値のペアのセットを含みます。必見の使用httpsで通信するためのUserInfoインターフェース。
RFC2616プロトコルによると、のUserInfoは、GETとPOSTメソッドをサポートしている必要があります。
UserInfoインターフェイスは、ベアラトークンを受け入れる必要があります。
UserInfoインターフェースは、JavaScriptクライアントのクロスドメインアクセスをサポートしなければならない、あなたはCORSプロトコルまたは他のプログラムを使用することができます。
UserInfo要求
GETメソッドは、のUserInfoインターフェーストークンベアラ要求ベアラAuthorizationヘッダを使用して推奨されます。
GET /userinfo HTTP/1.1
Host: server.example.com
Authorization: Bearer SlAV32hkKG
正常な応答
請求が空またはnullの場合、キーが返されません。
我々は、サブ(被写体)の文を返さなければなりません。
UserInfo一致id_tokenサブサブを返すことを確認する必要があり
、コンテンツタイプアプリケーション/ JSON、UTF-8でエンコードされている必要があり使用されなければならない
暗号化JWTリターンビット場合、コンテンツ型ビット必須アプリケーション/ 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
値を、言葉のリターンを見つけます - コンテンツタイプならば、の種類にフォームからフォームを取得しようとする
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、ナンス、NBF、reference_token_id、SID、スコープ
スクリーニング後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();
}