前文.Net 5.0 通过IdentityServer4实现单点登录之oidc认证部分源码解析介绍了oidc组件整合了相关的配置信息和从id4服务配置节点拉去了相关的配置信息和一些默认的信息,生成了OpenIdConnectMessage实例,内容如下:
,通过该实例生成了跳转url,内容如下:
http://localhost:5001/connect/authorize?client_id=mvc&redirect_uri=http%3A%2F%2Flocalhost%3A5002%2Fsignin-oidc&response_type=code&scope=openid%20profile&code_challenge=Ur1nNYQMb92VuIDvgeN9mJCvQRWyspeUvEjWDToyHqg&code_challenge_method=S256&response_mode=form_post&nonce=637914152486923476.OGM4MTZlNjktODgyYi00MDk3LThmYjMtMThhZjA2Y2I1NTRmZDI1NzIxYzYtZjkzNS00YzhjLTgzODctNGQyMmJhNmRhNGM4&state=CfDJ8HpC1EPIyftOtkkyJFkl1v9AcTjtWAadkF-ERJUSWQun-BBX0VMyqB5FFwNfPPTDI8B_17mXRXOCH_G55jpkiMMjer5IV1T5Skt2nDxn8WGS_inRbRntd04agnYBGCxXyIT6cuspg0sXcOvorCManimIgsxsg5tHNSYrh8dWtdJ1FvOknWcfYhbqR5QzZ44WZKEEdxUNn-9CB6FJnulndq_5CwkqjPMux2TsnE3Wok1MsSC8kKAoHTuvBwrxd1Su_xmooEg64NJCI4_ZbB9h9lBuv9YUSraDDUzAOzPA8zqwRlYA2SCevtIcmXxaT23bQ63Zv0dJ3kCoyTsoxf5OYoaOs8JkDzXl7cqglBb21cJ7CHQMW1IXdku6bHo1-BSHuw&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=1.0.0.0
最后调用Response.Redirect跳转到上面的url,id4的认证终结点,这里因为配置节点的相关源码比较简单,本文不做介绍.
所以这里会进入到id4的认证终结点,这里关于id4如果跳转终结点的因为源码比较简单,这里也不做介绍.大致逻辑事通过配置访问url,跳转到对应的处理终结点.url和终结点通过id4默认配置产生.接着看下id4demo服务的StartUp文件的调用代码如下:
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using IdentityServer4;
using IdentityServerHost.Quickstart.UI;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
namespace IdentityServer
{
public class Startup
{
void CheckSameSite(HttpContext httpContext, CookieOptions options)
{
if (options.SameSite == SameSiteMode.None)
{
var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
if (true)
{
options.SameSite = SameSiteMode.Unspecified;
}
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
var builder = services.AddIdentityServer()
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients)
.AddTestUsers(TestUsers.Users);
builder.AddDeveloperSigningCredential();
services.AddAuthentication()
.AddGoogle("Google", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = "<insert here>";
options.ClientSecret = "<insert here>";
})
.AddOpenIdConnect("oidc", "Demo IdentityServer", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.SaveTokens = true;
options.Authority = "https://demo.identityserver.io/";
options.ClientId = "interactive.confidential";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
});
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
options.OnAppendCookie = cookieContext =>
CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
options.OnDeleteCookie = cookieContext =>
CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseCookiePolicy();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
}
}
接着分析认证终结点执行逻辑,源码如下:
public override async Task<IEndpointResult> ProcessAsync(HttpContext context)
{
Logger.LogDebug("Start authorize request");
NameValueCollection values;
if (HttpMethods.IsGet(context.Request.Method))
{
values = context.Request.Query.AsNameValueCollection();
}
else if (HttpMethods.IsPost(context.Request.Method))
{
if (!context.Request.HasApplicationFormContentType())
{
return new StatusCodeResult(HttpStatusCode.UnsupportedMediaType);
}
values = context.Request.Form.AsNameValueCollection();
}
else
{
return new StatusCodeResult(HttpStatusCode.MethodNotAllowed);
}
var user = await UserSession.GetUserAsync();
var result = await ProcessAuthorizeRequestAsync(values, user, null);
Logger.LogTrace("End authorize request. result type: {0}", result?.GetType().ToString() ?? "-none-");
return result;
}
首先通过跳转时通过get方式,所以看下内部方法(将querystring转换成键值对集合),如下:
public static NameValueCollection AsNameValueCollection(this IEnumerable<KeyValuePair<string, StringValues>> collection)
{
var nv = new NameValueCollection();
foreach (var field in collection)
{
nv.Add(field.Key, field.Value.First());
}
return nv;
}
转换后的内容如下:
看过上文应该知道这就是OpenIdConnectMessage实例的值.
接着看认证终结点的源码:
var user = await UserSession.GetUserAsync();
这里尝试从用户绘画中获取httpcontext上下文的用户信息,接着解析:
protected virtual async Task AuthenticateAsync()
{
if (Principal == null || Properties == null)
{
var scheme = await HttpContext.GetCookieAuthenticationSchemeAsync();
var handler = await Handlers.GetHandlerAsync(HttpContext, scheme);
if (handler == null)
{
throw new InvalidOperationException($"No authentication handler is configured to authenticate for the scheme: {scheme}");
}
var result = await handler.AuthenticateAsync();
if (result != null && result.Succeeded)
{
Principal = result.Principal;
Properties = result.Properties;
}
}
}
这里进入认证解析流程,获取默认配置的cookie认证方案.源码如下:
internal static async Task<string> GetCookieAuthenticationSchemeAsync(this HttpContext context)
{
var options = context.RequestServices.GetRequiredService<IdentityServerOptions>();
//获取配置中的认证方案,如果配置了,则采用自定义的认证方案
if (options.Authentication.CookieAuthenticationScheme != null)
{
return options.Authentication.CookieAuthenticationScheme;
}
//这里默认时名称为idsrv的Cookie认证方案
var schemes = context.RequestServices.GetRequiredService<IAuthenticationSchemeProvider>();
var scheme = await schemes.GetDefaultAuthenticateSchemeAsync();
if (scheme == null)
{
throw new InvalidOperationException("No DefaultAuthenticateScheme found or no CookieAuthenticationScheme configured on IdentityServerOptions.");
}
return scheme.Name;
}
这里获取IdentityServerOptions配置的认证方案,说明这里认证方案是可以自定义的,但是demo中并没有配置,且在StratUp类中ConfigureServices方法中配置IdentityServer4时,默认采用的就是Cookie认证方案,其认证方案名称为idsrv,源码如下:
public static IIdentityServerBuilder AddIdentityServer(this IServiceCollection services)
{
var builder = services.AddIdentityServerBuilder();
builder
.AddRequiredPlatformServices()
.AddCookieAuthentication()
.AddCoreServices()
.AddDefaultEndpoints()
.AddPluggableServices()
.AddValidators()
.AddResponseGenerators()
.AddDefaultSecretParsers()
.AddDefaultSecretValidators();
// provide default in-memory implementation, not suitable for most production scenarios
builder.AddInMemoryPersistedGrants();
return builder;
}
public static IIdentityServerBuilder AddCookieAuthentication(this IIdentityServerBuilder builder)
{
builder.Services.AddAuthentication(IdentityServerConstants.DefaultCookieAuthenticationScheme)
.AddCookie(IdentityServerConstants.DefaultCookieAuthenticationScheme)
.AddCookie(IdentityServerConstants.ExternalCookieAuthenticationScheme);
builder.Services.AddSingleton<IConfigureOptions<CookieAuthenticationOptions>, ConfigureInternalCookieOptions>();
builder.Services.AddSingleton<IPostConfigureOptions<CookieAuthenticationOptions>, PostConfigureInternalCookieOptions>();
builder.Services.AddTransientDecorator<IAuthenticationService, IdentityServerAuthenticationService>();
builder.Services.AddTransientDecorator<IAuthenticationHandlerProvider, FederatedSignoutAuthenticationHandlerProvider>();
return builder;
}
ok,这里返回了默认的cookie认证方案后,回到认证流程如下代码:
var handler = await Handlers.GetHandlerAsync(HttpContext, scheme);
if (handler == null)
{
throw new InvalidOperationException($"No authentication handler is configured to authenticate for the scheme: {scheme}");
}
var result = await handler.AuthenticateAsync();
if (result != null && result.Succeeded)
{
Principal = result.Principal;
Properties = result.Properties;
}
根据认证方案名称获取认证处理器,逻辑如下:
public async Task<IAuthenticationHandler> GetHandlerAsync(HttpContext context, string authenticationScheme)
{
var handler = await _provider.GetHandlerAsync(context, authenticationScheme);
if (handler is IAuthenticationRequestHandler requestHandler)
{
if (requestHandler is IAuthenticationSignInHandler signinHandler)
{
return new AuthenticationRequestSignInHandlerWrapper(signinHandler, _httpContextAccessor);
}
if (requestHandler is IAuthenticationSignOutHandler signoutHandler)
{
return new AuthenticationRequestSignOutHandlerWrapper(signoutHandler, _httpContextAccessor);
}
return new AuthenticationRequestHandlerWrapper(requestHandler, _httpContextAccessor);
}
return handler;
}
这里因为.CookieAuthenticationHandler处理器不是认证请求处理器,所以直接返回该处理器实例.接处理器实例的AuthenticateAsync从客户端加密的cookie中解析出用户信息写入到上下文中,应为这里是第一次调用,所以必然用户信息为空.关于cookie认证方案如果不清楚请参考https://cloud.tencent.com/developer/article/1561893
接着回到认证终结点源码如下:
var user = await UserSession.GetUserAsync();
var result = await ProcessAuthorizeRequestAsync(values, user, null);
Logger.LogTrace("End authorize request. result type: {0}", result?.GetType().ToString() ?? "-none-");
return result;
这里user为空,原因说了,接着分析ProcessAuthorizeRequestAsync方法:
internal async Task<IEndpointResult> ProcessAuthorizeRequestAsync(NameValueCollection parameters, ClaimsPrincipal user, ConsentResponse consent)
{
if (user != null)
{
Logger.LogDebug("User in authorize request: {subjectId}", user.GetSubjectId());
}
else
{
Logger.LogDebug("No user present in authorize request");
}
// validate request
var result = await _validator.ValidateAsync(parameters, user);
if (result.IsError)
{
return await CreateErrorResultAsync(
"Request validation failed",
result.ValidatedRequest,
result.Error,
result.ErrorDescription);
}
var request = result.ValidatedRequest;
LogRequest(request);
// determine user interaction
var interactionResult = await _interactionGenerator.ProcessInteractionAsync(request, consent);
if (interactionResult.IsError)
{
return await CreateErrorResultAsync("Interaction generator error", request, interactionResult.Error, interactionResult.ErrorDescription, false);
}
if (interactionResult.IsLogin)
{
return new LoginPageResult(request);
}
if (interactionResult.IsConsent)
{
return new ConsentPageResult(request);
}
if (interactionResult.IsRedirect)
{
return new CustomRedirectResult(request, interactionResult.RedirectUrl);
}
var response = await _authorizeResponseGenerator.CreateResponseAsync(request);
await RaiseResponseEventAsync(response);
LogResponse(response);
return new AuthorizeResult(response);
}
这里根据id4服务的配置和客户端传入的OpenIdConnectMessage实例值
(1)、检验客户端是否有效
private async Task<AuthorizeRequestValidationResult> LoadClientAsync(ValidatedAuthorizeRequest request)
{
//从请求的QuerString获取客户端id
var clientId = request.Raw.Get(OidcConstants.AuthorizeRequest.ClientId);
//clientId值长度和非空检验 默认clientId不能超过100
if (clientId.IsMissingOrTooLong(_options.InputLengthRestrictions.ClientId))
{
LogError("client_id is missing or too long", request);
return Invalid(request, description: "Invalid client_id");
}
request.ClientId = clientId;
//判断客户端是否在仓储中是否存在 demo中采用内存仓储
var client = await _clients.FindEnabledClientByIdAsync(request.ClientId);
if (client == null)
{
LogError("Unknown client or not enabled", request.ClientId, request);
return Invalid(request, OidcConstants.AuthorizeErrors.UnauthorizedClient, "Unknown client or client not enabled");
}
//设置请求的客户端信息
request.SetClient(client);
return Valid(request);
}
关于id4客户端的配置代码如下:
var builder = services.AddIdentityServer()
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients)
.AddTestUsers(TestUsers.Users);
通过.AddInMemoryClients(Config.Clients)配置,id4官方提供了ef core实现,当然这里可以选择重写,如Dapper.
public static IEnumerable<Client> Clients =>
new List<Client>
{
// machine to machine client
new Client
{
ClientId = "client",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.ClientCredentials,
// scopes that client has access to
AllowedScopes = { "api1" }
},
// interactive ASP.NET Core MVC client
new Client
{
ClientId = "mvc",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Code,
// where to redirect to after login
RedirectUris = { "http://localhost:5002/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
}
}
};
可以看到这里确实配置了ClientId为mvc的客户端信息.
(2)、检验jwt相关
var roLoadResult = await LoadRequestObjectAsync(request);
if (roLoadResult.IsError)
{
return roLoadResult;
}
// validate request object
var roValidationResult = await ValidateRequestObjectAsync(request);
if (roValidationResult.IsError)
{
return roValidationResult;
}
(3)、检验传入的redirectUri和客户端配置的是否一致,如下代码:
private async Task<AuthorizeRequestValidationResult> ValidateClientAsync(ValidatedAuthorizeRequest request)
{
//////////////////////////////////////////////////////////
// check request object requirement
//////////////////////////////////////////////////////////
if (request.Client.RequireRequestObject)
{
if (!request.RequestObjectValues.Any())
{
return Invalid(request, description: "Client must use request object, but no request or request_uri parameter present");
}
}
//////////////////////////////////////////////////////////
// redirect_uri must be present, and a valid uri
//////////////////////////////////////////////////////////
var redirectUri = request.Raw.Get(OidcConstants.AuthorizeRequest.RedirectUri);
if (redirectUri.IsMissingOrTooLong(_options.InputLengthRestrictions.RedirectUri))
{
LogError("redirect_uri is missing or too long", request);
return Invalid(request, description: "Invalid redirect_uri");
}
if (!Uri.TryCreate(redirectUri, UriKind.Absolute, out _))
{
LogError("malformed redirect_uri", redirectUri, request);
return Invalid(request, description: "Invalid redirect_uri");
}
//////////////////////////////////////////////////////////
// check if client protocol type is oidc
//////////////////////////////////////////////////////////
if (request.Client.ProtocolType != IdentityServerConstants.ProtocolTypes.OpenIdConnect)
{
LogError("Invalid protocol type for OIDC authorize endpoint", request.Client.ProtocolType, request);
return Invalid(request, OidcConstants.AuthorizeErrors.UnauthorizedClient, description: "Invalid protocol");
}
//////////////////////////////////////////////////////////
// check if redirect_uri is valid
//////////////////////////////////////////////////////////
if (await _uriValidator.IsRedirectUriValidAsync(redirectUri, request.Client) == false)
{
LogError("Invalid redirect_uri", redirectUri, request);
return Invalid(request, OidcConstants.AuthorizeErrors.InvalidRequest, "Invalid redirect_uri");
}
request.RedirectUri = redirectUri;
return Valid(request);
}
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有