ASP.NET Core 有一個靈活的方式來處理外部認證,有如下幾個步驟:
如果你使用了 ASP.NET Identity,ASP.NET Identity 對於許多底層代碼都做了封裝, 建議閱讀Microsoft文檔並查看ASP.NET Identity快速入門源碼,以此來充分了解 ASP.NET Identity。
一.添加外部認證處理程序
與外部認證提供者交互所需的協議實現被封裝在一個認證處理程序中。 一些提供者使用專有協議(例如Facebook等社交提供者),一些使用標准協議, OpenID Connect,WS-Federation或SAML2p。
請參閱此快速入門以了解添加外部認證並對其進行配置的分步說明。
二.cookies的作用
外部認證處理程序上的一個設置 SignInScheme
,例如:
services.AddAuthentication()
.AddGoogle("Google", options =>
{
options.SignInScheme = "scheme of cookie handler to use";
options.ClientId = "...";
options.ClientSecret = "...";
})
登錄 scheme
指定將暫時存儲外部認證的結果的cookie處理程序的名稱,例如 由外部提供商發送的身份信息單元(Claim)。 這是必要的,因為在完成外部認證過程之前,通常會有幾個重定向(比如QQ登錄的跳轉)。
鑒於這是一種常見的做法,IdentityServer 專門為此外部提供程序工作流程注冊一個Cookie處理程序。 該方案通過IdentityServerConstants.ExternalCookieAuthenticationScheme
常量表示。 如果你要使用 IdentityServer 提供外部cookie處理程序,那么對於上面的SignInScheme
,默認值為IdentityServerConstants.ExternalCookieAuthenticationScheme
常量:
services.AddAuthentication()
.AddGoogle("Google", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = "...";
options.ClientSecret = "...";
})
您也可以注冊您自己的自定義Cookie處理程序,如下所示:
services.AddAuthentication()
.AddCookie("YourCustomScheme")
.AddGoogle("Google", options =>
{
options.SignInScheme = "YourCustomScheme";
options.ClientId = "...";
options.ClientSecret = "...";
})
對於特定的場景,你可以不使用外部Cookie機制,將外部用戶直接轉發到主要Cookie處理程序。 這通常涉及在外部處理程序上處理事件,以確保從外部身份源執行正確的身份信息(Claim)轉換。
三. 觸發認證
你可以通過HttpContext
上的ChallengeAsync
擴展方法(或使用MVC ChallengeResult)調用外部認證處理程序。
如果傳遞某些設置,比如: returnUrl和scheme可以這樣設置:
var callbackUrl = Url.Action("ExternalLoginCallback");
var props = new AuthenticationProperties
{
RedirectUri = callbackUrl,
Items =
{
{ "scheme", provider },
{ "returnUrl", returnUrl }
}
};
return Challenge(provider, props);
四. 回調處理程序和用戶簽名
在回調頁面上,通常任務是:
- 檢查由外部提供商返回的身份。
- 如果處理用戶,對於已存在的用戶和新用戶的處理是不同的
- 新用戶在登入可能需要額外的步驟和UI界面。
- 可能會創建一個內部賬戶來綁定外部身份
- 存儲你要保留的外部身份信息單元(Claim)。
- 刪除臨時cookie
- 登錄用戶
** 檢查外部身份:**
// read external identity from the temporary cookie
var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (result?.Succeeded != true)
{
throw new Exception("External authentication error");
}
// retrieve claims of the external user
var externalUser = result.Principal;
if (externalUser == null)
{
throw new Exception("External authentication error");
}
// retrieve claims of the external user
var claims = externalUser.Claims.ToList();
// try to determine the unique id of the external user - the most common claim type for that are the sub claim and the NameIdentifier
// depending on the external provider, some other claim type might be used
var userIdClaim = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Subject);
if (userIdClaim == null)
{
userIdClaim = claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
}
if (userIdClaim == null)
{
throw new Exception("Unknown userid");
}
var externalUserId = userIdClaim.Value;
var externalProvider = userIdClaim.Issuer;
// use externalProvider and externalUserId to find your user, or provision a new user
** 清理和登錄:**
// issue authentication cookie for user
await HttpContext.SignInAsync(user.SubjectId, user.Username, provider, props, additionalClaims.ToArray());
// delete temporary cookie used during external authentication
await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
// validate return URL and redirect back to authorization endpoint or a local page
if (_interaction.IsValidReturnUrl(returnUrl) || Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return Redirect("~/");
五.State,URL Length和ISecureDataFormat
當重定向到外部登錄時,來自客戶端應用程序的狀態必須頻繁進行往返。 這意味着狀態在離開客戶端之前被捕獲並保存直到用戶返回到客戶端應用程序。 許多協議(包括OpenID Connect)都允許將某種狀態作為參數傳遞,身份提供者將在響應中返回該狀態。 ASP.NET Core提供的OpenID Connect身份驗證處理程序利用了該協議的這一功能,這就是它如何實現上述的returnUrl功能。
在請求參數中存儲狀態的問題是請求URL可能會變得太大(超過2000個字符的公共限制)。 OpenID Connect身份驗證處理程序的確提供了一個可擴展點,用於將狀態存儲在服務器中,而不是在請求URL中。 您可以通過實現ISecureDataFormat
幸運的是,IdentityServer為您提供了一個實現,由在DI容器中注冊的IDistributedCache
實現(例如,獨立的MemoryDistributedCache
)支持。 要使用IdentityServer提供的安全數據格式實現,只需在配置DI時在IServiceCollection上調用AddOidcStateDataFormatterCache
擴展方法即可。 如果沒有參數傳遞,則所有配置的OpenID Connect處理程序將使用IdentityServer提供的安全數據格式實現:
public void ConfigureServices(IServiceCollection services)
{
// configures the OpenIdConnect handlers to persist the state parameter into the server-side IDistributedCache.
services.AddOidcStateDataFormatterCache();
services.AddAuthentication()
.AddOpenIdConnect("demoidsrv", "IdentityServer", options =>
{
// ...
})
.AddOpenIdConnect("aad", "Azure AD", options =>
{
// ...
})
.AddOpenIdConnect("adfs", "ADFS", options =>
{
// ...
});
}
如果只配置特定方案,則將這些方案作為參數傳遞:
public void ConfigureServices(IServiceCollection services)
{
// configures the OpenIdConnect handlers to persist the state parameter into the server-side IDistributedCache.
services.AddOidcStateDataFormatterCache("aad", "demoidsrv");
services.AddAuthentication()
.AddOpenIdConnect("demoidsrv", "IdentityServer", options =>
{
// ...
})
.AddOpenIdConnect("aad", "Azure AD", options =>
{
// ...
})
.AddOpenIdConnect("adfs", "ADFS", options =>
{
// ...
});
}