新文章 网摘 文章 随笔 日记

Identity Server 3独立实施第2部分

不再支持IdentityServer3。我强烈建议您考虑使用IdentityServer4

上一篇文章扩展Identity Server的实现,现在我们将创建一些基本的MVC客户端并开始对客户端应用程序进行身份验证。

本指南的这一部分将着眼于将ASP.NET MVC应用程序与Identity Server手动集成,以便我们可以实际了解OpenID Connect 1.0和Identity Server 3的某些功能和过程。 本指南的第3部分将介绍如何使用OpenID Connect katana中间件自动配置应用程序以使用Identity Server。

表单发布客户端

通过使用表单发布响应模式授权,授权响应参数被编码为HTML表单值,并通过HTTP POST传输到发出请求的客户端,结果参数使用该application/x-www-form-urlencoded格式随正文一起返回您可以在OAuth 2.0表单发布响应模式规范中找到表单发布响应模式的更多示例和安全注意事项表单后响应模式减轻了对查询字符串和片段值中的响应值进行编码的一些安全隐患。

安装

我们所需要的只是一个使用MVC模板的基本ASP.NET项目。确保在创建项目时不添加任何身份验证模板。

必需的软件包(Microsoft.Owin.Host.SystemWeb允许我们在IIS中运行OWIN管道):

install-package Microsoft.Owin.Host.SystemWeb
install-package Microsoft.Owin.Security.Cookies
install-package System.IdentityModel.Tokens.Jwt

授权书

首先,我们将添加一个新的MVC控制器以及一个登录方法。这将显式使用OpenID Connect 1.0 Authorize端点(请参阅我先前描述此内容的文章之一),该端点将呈现Identity Server实现的登录页面。OpenID Connect 1.0将此称为身份验证请求,它们定义为:

“ [一个] OAuth 2.0授权请求,使用OpenID Connect定义的扩展参数和范围来请求最终用户由作为OpenID Connect提供程序的授权服务器向作为OpenID Connect依赖方的客户端进行身份验证。 ” ID连接1.0核心
private const string ClientUri = @"https://localhost:44304";
private const string CallbackEndpoint = ClientUri + @"/account/signInCallback";
private const string IdServBaseUri = @"https://localhost:44300/core";
private const string AuthorizeUri = IdServBaseUri + @"/connect/authorize";

public ActionResult SignIn() {
	var state = Guid.NewGuid().ToString("N");
    var nonce = Guid.NewGuid().ToString("N");

    var url = AuthorizeUri +
		"?client_id=implicitclient" +
		"&response_type=id_token" +
		"&scope=openid email profile" +
		"&redirect_uri=" + CallbackEndpoint +
		"&response_mode=form_post" +
		"&state=" + state +
		"&nonce=" + nonce;

    this.SetTempCookie(state, nonce);
    return this.Redirect(url);
}

private void SetTempCookie(string state, string nonce) {
    var tempId = new ClaimsIdentity("TempCookie");
    tempId.AddClaim(new Claim("state", state));
    tempId.AddClaim(new Claim("nonce", nonce));

    this.Request.GetOwinContext().Authentication.SignIn(tempId);
}

这些Authorizatin请求参数中的一些已从OAuth 2.0传递过来,而其他一些则是OpenID Connect 1.0特有的。

CLIENT_ID是指当我们建立了暗示客户端,我们使用的OAuth 2.0客户端标识符。

response_type确定要使用的授权流。这里的选项codetokenid_token token

作用域必须始终包含openid作用域,其他所有内容都是可选的。建议您始终保持最小范围。

redirect_uri是将响应发送到的URI。这必须与您的OpenID提供程序中的客户端的重定向URI完全匹配。这应该是https,但可以是http。

response_mode通知授权服务器使用什么机制从授权服务器返回参数。请注意,此参数是可选的,当前仅在要使用表单发布响应模式时使用。

状态将在令牌响应中回显,从而保持请求和响应之间的状态(对跨站请求伪造很有用)。

随机数将被回荡在身份令牌背面,因此维持令牌和请求之间的状态。

您可以在此处找到认证端点的完整规范

我们在此处使用的临时Cookie将使我们能够在登录回调中检查这些状态和随机数,如下所示:

[HttpPost]
public async Task<ActionResult> SignInCallback() {
	var token = this.Request.Form["id_token"];
    var state = this.Request.Form["state"];

    var claims = await ValidateIdentityTokenAsync(token, state);
    var id = new ClaimsIdentity(claims, "Cookies");
    this.Request.GetOwinContext().Authentication.SignIn(id);

    return this.Redirect("/");
}

在这里,我们接收到身份验证请求的结果,然后在登录用户之前发送以进行验证。

 

private async Task<IEnumerable<Claim>> ValidateIdentityTokenAsync(string token, string state) {
	const string certString = "MIIDBTCCAfGgAwIBAgIQNQb+T2ncIrNA6cKvUA1GWTAJBgUrDgMCHQUAMBIxEDAOBgNVBAMTB0RldlJvb3QwHhcNMTAwMTIwMjIwMDAwWhcNMjAwMTIwMjIwMDAwWjAVMRMwEQYDVQQDEwppZHNydjN0ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqnTksBdxOiOlsmRNd+mMS2M3o1IDpK4uAr0T4/YqO3zYHAGAWTwsq4ms+NWynqY5HaB4EThNxuq2GWC5JKpO1YirOrwS97B5x9LJyHXPsdJcSikEI9BxOkl6WLQ0UzPxHdYTLpR4/O+0ILAlXw8NU4+jB4AP8Sn9YGYJ5w0fLw5YmWioXeWvocz1wHrZdJPxS8XnqHXwMUozVzQj+x6daOv5FmrHU1r9/bbp0a1GLv4BbTtSh4kMyz1hXylho0EvPg5p9YIKStbNAW9eNWvv5R8HN7PPei21AsUqxekK0oW9jnEdHewckToX7x5zULWKwwZIksll0XnVczVgy7fCFwIDAQABo1wwWjATBgNVHSUEDDAKBggrBgEFBQcDATBDBgNVHQEEPDA6gBDSFgDaV+Q2d2191r6A38tBoRQwEjEQMA4GA1UEAxMHRGV2Um9vdIIQLFk7exPNg41NRNaeNu0I9jAJBgUrDgMCHQUAA4IBAQBUnMSZxY5xosMEW6Mz4WEAjNoNv2QvqNmk23RMZGMgr516ROeWS5D3RlTNyU8FkstNCC4maDM3E0Bi4bbzW3AwrpbluqtcyMN3Pivqdxx+zKWKiORJqqLIvN8CT1fVPxxXb/e9GOdaR8eXSmB0PgNUhM4IjgNkwBbvWC9F/lzvwjlQgciR7d4GfXPYsE1vf8tmdQaY8/PtdAkExmbrb9MihdggSoGXlELrPA91Yce+fiRcKY3rQlNWVd4DOoJ/cPXsXwry8pWjNCo5JD8Q+RQ5yZEy7YPoifwemLhTdsBz3hlZr28oCGJ3kbnpW0xGvQb3VHSTVVbeei0CfXoW6iz1";

	var cert = new X509Certificate2(Convert.FromBase64String(certString));

    var result = await this.Request
		.GetOwinContext()
        .Authentication
        .AuthenticateAsync("TempCookie");

    if (result == null) {
		throw new InvalidOperationException("No temp cookie");
    }

    if (state != result.Identity.FindFirst("state").Value) {
		throw new InvalidOperationException("invalid state");
	}

    var parameters = new TokenValidationParameters {
		ValidAudience = "implicitclient",
        ValidIssuer = IdServBaseUri,
        IssuerSigningToken = new X509SecurityToken(cert)
    };

    var handler = new JwtSecurityTokenHandler();
    SecurityToken jwt;
    var id = handler.ValidateToken(token, parameters, out jwt);

    if (id.FindFirst("nonce").Value != result.Identity.FindFirst("nonce").Value) {
		throw new InvalidOperationException("Invalid nonce");
	}

    this.Request.GetOwinContext().Authentication.SignOut("TempCookie");

	return id.Claims;
}

在这里,我们将返回的状态和随机数与临时cookie中存储的返回状态和随机数进行比较,并验证JWT

登出

private const string LogoutUri = IdServBaseUri + @"/connect/endsession";
public ActionResult SignOut() {
	this.Request.GetOwinContext().Authentication.SignOut();
    return this.Redirect(LogoutUri);
}

我在这里仅包括基本的注销功能。注销重定向将在以后的文章中介绍。此处找到会话管理的完整规范

介绍

将以下内容添加到_Layout.cshtml中的导航栏中

<li>@Html.ActionLink("SignIn", "SignIn", "Account")</li>
<li>@Html.ActionLink("SignOut", "SignOut", "Account")</li>

将以下内容添加到您的Index.cshtml视图中,以便我们登录后即可看到我们的声明。

@if (User.Identity.IsAuthenticated) {
	<p>
		<dl>
			@foreach (var claim in System.Security.Claims.ClaimsPrincipal.Current.Claims) {
				<dt>@claim.Type</dt>
				<dd>@claim.Value</dd>
            }
		</dl>
	</p>
}
else {
	<p>anonymous.</p>
}

启动

为了将所有这些结合在一起,我们将使用OWIN管道发布我们的cookie(应用程序和临时文件),并确保JWT中间件使用Identity Server使用的声明类型,而不是冗长的XML名称空间。

public class Startup {
	public void Configuration(IAppBuilder app) {
		JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();

		app.UseCookieAuthentication(new CookieAuthenticationOptions {
			AuthenticationType = "Cookies"
		});

		app.UseCookieAuthentication(new CookieAuthenticationOptions {
			AuthenticationType = "TempCookie",
			AuthenticationMode = AuthenticationMode.Passive
		});
	}
}

现在我们可以运行该项目,并看到实际的效果。

表单发布客户端主页

预登录首页。

身份服务器登录页面

身份服务器登录页面。

Identity Server同意屏幕

Identity Server同意屏幕。您可能还记得,我们启用了同意页面,并能够记住该客户的内容。请注意,这是默认行为。

表格后客户索赔

在这里,我们可以查看已登录用户的声明。应我们的要求openidprofile而且email,我们可以看到一些很容易识别的标准要求,如family_nameemail您可以在此处找到OpenID Connect 1.0标准声明的列表

Identity Server注销确认 Identity Server注销屏幕

注销屏幕很容易说明。但是,我们没有设置重定向,因此在注销后,我们将保留在Identity Server站点内。

关于使用Form Post Client和Identity Server 3的评论

通过此客户端,我们可以明确使用OpenID Connect规范的各种功能。但是,在下一个客户端中,我们将开始通过使用一些将自动处理本文中所有内容的标准Microsoft Katana库,来研究与Identity Server集成的简单程度。

但是,对于该显式客户端,您可以扩展一些领域,例如,可以使用发现端点来获取JWK公钥,而不是对其进行硬编码,或者可以利用Identity Server的注销重定向功能。

下一步,我们将设置一个混合客户端,并使用基于Owin的MVC客户端来展示Identity Server 3的易用性。

我没想到会详细介绍此客户端,也没想到OpenID Connect规范,因此,如果您发现任何咆哮者都可以Twitter上向我大喊大叫,我将尽快对其进行排序。

资料来源

posted @ 2020-07-22 09:03  岭南春  阅读(43)  评论(0)    收藏  举报