您可以找到open source example of this on GitHub。其许可证非常宽松,并且有据可查。我已经在各种研讨会和培训中使用过它,因此大多数错误都已解决。我建议你深入研究一下。不过为了完整起见,我将在这里描述一般过程,并以此作为解释的基础。
任何实现 OpenID Connect 代码流的 Web 应用程序都包括两个部分:
- 流程的开始和
- 回调的处理
执行这两件事的应用程序称为“客户端”或“依赖方”。此客户端使用 OpenID Connect 协议与之通信的东西称为 OpenID Connect Provider (OP),通常也称为 Identity Provider (IdP)。
客户端实现的第一部分将显示一个包含按钮的视图。此按钮将是典型的“登录”或“登录”按钮。请注意,这是可选的,如果应用程序检测到用户没有会话,它可能会立即将用户重定向到 OP。但是,鉴于您上面的问题,您的情况并非如此,客户端将首先呈现一个显示此类按钮的视图。视图可能如下所示:
<div>
@if(Session.Count == 0) {
<p>
This is a demo application to demonstrate the use for OAuth2
and OpenID Connect.
</p>
<p>
Pressing Sign In will redirect you to @ViewData["server_name"]
and authorize the application to access your profile info. The
data will only be used to demonstrate the possibilities of the
OpenID Connect protocol and will not be stored. Be sure to
revoke access when you are satisfied.
</p>
<div>
<a href="/login">Sign In</a>
</div>
} else {
// ...
}
</div>
此视图将由一个非常基本的控制器呈现,该控制器连接在Global.asax.cs 中建立的路由配置中。单击登录按钮时,OpenID Connect 部件将启动。处理此请求的控制器将简单地重定向到 OP 的授权端点。在最基本的情况下,这可能看起来像这样:
public class LoginController : Controller
{
private static string start_oauth_endpoint = Helpers.Client.Instance.GetAuthnReqUrl();
public ActionResult Index()
{
return Redirect(start_oauth_endpoint);
}
}
有趣的部分是如何获得授权端点。这可以是硬编码的,在Web.config 中定义,或者从 OP 的元数据中获取。在我上面引用的示例中,它在应用启动时获取 OP 的元数据。这是在位于 Web 应用程序的 App_Start 目录中的 AppConfig 中完成的。这将使用/.well-known/openid-configuration 向颁发者ID(位于Web.config)执行HTTP GET 请求。在应用启动时获取这些元数据而不是将其全部放入配置中的原因是为了减少 OP 和客户端的耦合。
上面截图中执行的重定向将有一些重要的查询字符串参数。其中一些将在设计时已知,并且将被硬编码。其他将在Web.config 中配置。有些将在运行时动态计算。下面列出了这些:
client_id
- 此 MVC Web 应用的客户端 ID。
-
response_type-
- OP 应该使用的响应类型。在您的情况下,这将始终是
code。
scope
- 客户端请求的访问范围。这将至少包括
openid。
redirect_uri
- 在对客户端进行身份验证和授权后,OP 应将用户发送到的重定向 URI。
也可以发送其他请求参数。为了帮助您确定要发送哪些内容以及它们对流程的影响,请查看oauth.tools。这就像“OAuth 和 OpenID Connect 的邮递员”。这是梦幻般的;你会喜欢的。在那里,您可以使用各种参数形成各种 OAuth 和 OpenID Connect 流。
一旦重定向到 OP,用户将进行身份验证。用户可能还必须同意客户端访问其受保护的资源。无论如何,OP 将在此之后将用户重定向到回调。这是实现的第二部分。
在这里,我们将有一个CallbackController(或类似的东西)。它看起来像这样(最简单的形式):
public class CallbackController : Controller
{
public ActionResult Index()
{
try
{
string responseString = Helpers.Client.Instance
.GetToken(Request.QueryString["code"]);
SaveDataToSession(responseString);
}
catch (Exception e)
{
Session["error"] = e.Message;
}
return Redirect("/");
}
}
这个 sn-p 的重要部分是它从查询字符串中获取code,并向 OP 的令牌端点(也通过解析 OP 的元数据定位)发出 HTTP POST 请求。如果成功,它会将响应保存在会话中以供以后使用。 GetToken 方法看起来像这样:
public String GetToken(String code)
{
var values = new Dictionary<string, string>
{
{ "grant_type", "authorization_code" },
{ "client_id", client_id},
{ "client_secret", client_secret },
{ "code" , code },
{ "redirect_uri", redirect_uri}
};
HttpClient tokenClient = new HttpClient();
var content = new FormUrlEncodedContent(values);
var response = tokenClient.PostAsync(token_endpoint, content).Result;
if (response.IsSuccessStatusCode)
{
var responseContent = response.Content;
return responseContent.ReadAsStringAsync().Result;
}
throw new OAuthClientException("Token request failed with status code: " + response.StatusCode);
}
这会将代码发送到 OP 并获得一个访问令牌、ID 令牌,也许还有一个刷新令牌作为交换。这段代码的重要部分是:
- 内容是格式 URL 编码的不是 JSON。这是一个常见的错误。
- 再次包含之前发送的 same 重定向 URI。这是为了匹配 OP 的两个请求。
-
grant_Type总是authorization_code。
- 客户端以某种方式进行身份验证。在这种情况下,通过在请求中包含与先前发送的相同的
client_id 以及 client_secret 表单元素中的秘密。
- 使用的 HTTP 方法(如上所述)是 POST,而不是 GET。这也是一个常见的错误。
在上面的示例中,我重定向回默认值HomeController。现在,执行 if 语句的 else 条件。在这里,它可以找到令牌:
<div>
@if(Session.Count == 0) {
// ...
} else {
@if(Session["id_token"] != null) {
<div>
ID Token:<br>
<pre>@Session["id_token"]</pre>
</div>
}
@if(Session["access_token"] != null) {
<div>
Access Token:<br>
<pre>@Session["access_token"]</pre>
</div>
}
@if(Session["refresh_token"] != null) {
<div>
Refresh Token:<br>
<pre>@Session["refresh_token"]</pre>
</div>
}
}
</div>
这个例子比这个更详细,但它希望能给你一个想法。通读一遍,查看自述文件,并愉快地了解有关 OpenID Connect 的更多信息!