如何使用Owin OpenId Connect库让ASP.NET创建经过身份验证的会话?
如何使用Owin OpenId Connect库让ASP.NET创建经过身份验证的会话?
在浏览了许多在线示例后,我发现了一个似乎很简单的需求,但我仍然无法理解。我正在尝试扩展一个现有的ASP.NET应用程序,该应用程序目前使用表单身份验证,以便它可以同时使用OpenID Connect进行身份验证和来自身份提供者的一些角色信息。特别是,我正在与一个现有的托管身份提供者集成,我无法控制它。
我正在使用ASP.NET MVC和用于OpenIdConnect的Owin组件。具体来说,我使用了以下Owin组件:
Microsoft.Owin.Security
Microsoft.Owin.Security.Cookies
Microsoft.Owin.Security.OpenIdConnect
我已经成功地实现了以下功能:
1. 在Web浏览器中,导航到使用[Authorize]属性进行保护的控制器方法。
2. Owin组件正确地将我重定向到身份提供者,我可以进行身份验证,然后重定向回我的应用程序(注意:我的身份提供者要求传递一个redirect_uri,所以我当前正在将其设置为OpenIdConnectAuthenticationOptions启动配置的一部分)。
3. 当重定向回我的应用程序时,我能够看到access_token和id_token作为查询字符串的一部分。此外,我已经能够使用access_token调用用户信息端点,并使用该令牌正确地获取有关用户的信息。
到目前为止一切顺利!但是,
我无法理解的是,大多数我见过的Owin示例似乎没有解释额外的配置是否需要,以便ASP.NET根据从身份提供者重定向回我的应用程序来实际创建一个经过身份验证的会话。
从文档中我得到的一般感觉是,我不应该在Owin库中进行额外的配置-一旦我已经配置了系统使用cookie身份验证和OpenId Connect库-它应该可以正常工作。然而,这并不像看起来那么容易。我猜我漏掉了什么。
一些具体的考虑/观察:
1. 我找到的许多示例不要求在OpenIdConnectAuthenticationOptions中设置RedirectUri,但是我的身份提供者要求每次都设置此参数。
2. 我找到的很少有示例解释了由于命中RedirectUri而触发的控制器方法是否应该受到[Authorize]的保护,或者是否应该保持匿名。在我的测试中,如果我将其标记为[Authorize],我会进入无限的重定向循环。如果我将其保持匿名,我可以在请求信息中看到令牌,但是ASP.NET会话永远不会创建。例如,Request.IsAuthenticated始终为false。
3. 作为一个测试,我在OpenIdConnectAuthenticationNotifications()事件中设置了断点,目前只有我的代码在RedirectToIdentityProvider事件中断点,其他事件似乎都没有命中-这使我相信我没有正确配置它。
4. 根据我找到的建议,我在web.config中将身份验证节点设置为如下方式,但是如果我排除此节点,似乎并没有什么区别。
总结一下:
1. 我需要编写特定的代码来处理从身份提供者返回的重定向,以手动设置给定用户的ASP.NET会话(cookie等)吗?
2. 如果是这样,这段代码应该放在由RedirectUri命中而调用的控制器方法中,还是应该放在OpenIdConnectAuthenticationNotifications()中的一个“Notifications”事件中?
最后,如果我不应该在从身份提供者重定向后手动设置已验证的会话(如果它应该自动工作),那么在此配置中常见的错误有哪些建议?
为了完整起见:
我的Owin管道启动配置方法:
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
//根据我所知,这些都没有问题
ClientId = "client_id_string",
ClientSecret = "client_secret_string",
Authority = "url_to_identity_provider",
Scope = "email name etc",
//我会被正确重定向到此URL,但是不确定
//是否应该手动创建会话
RedirectUri = "http://mymachine/mymvcapp/authorize",
//这会导致重定向带有access_token
//这是有效的
ResponseType = "token",
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = (context) =>
{
//我能够进入这个方法
return Task.FromResult(0);
},
MessageReceived = (context) =>
{
//似乎没有运行这一行
return Task.FromResult(0);
},
SecurityTokenReceived = (context) =>
{
//似乎没有运行这一行
return Task.FromResult(0);
},
SecurityTokenValidated = (context) =>
{
//似乎没有运行这一行
return Task.FromResult(0);
},
AuthorizationCodeReceived = (context) =>
{
//似乎没有运行这一行
return Task.FromResult(0);
},
AuthenticationFailed = (context) =>
{
//似乎没有运行这一行
return Task.FromResult(0);
},
},
});
}
我的正确启动登录流程的安全方法:
[Authorize]
public class HomeController : Controller
{
//当第一次命中时,我会被发送到登录流程
public ActionResult Index()
{
return View();
}
}
在RedirectUri中调用的方法,但是指示ASP.NET经过身份验证的会话已创建:
public class AuthorizeController : Controller
{
//[Authorize] - 当前已将此Authorize特性关闭,因此该方法是匿名的。
//如果我再次打开它,我会进入无限的重定向循环
public ActionResult Index()
{
//从身份提供者到这个控制器方法的传入请求确实包含有效的access_token和id_token
//(可以用于用户信息终点),但不为我的Web应用程序创建有效的ASP.NET会话
//Request.IsAuthenticated始终为false
//在这个控制器方法中,是否应该手动创建ASP.NET会话/cookie信息?
//注意:对我来说,如果这个属性不是匿名的,那么这是没有意义的,
//因为Request很少会出现IsAuthenticated == true,但是如果您阅读完整的问题,就会明白我为什么要尝试使用匿名访问这个方法
return View();
}
}
问题的原因是在使用外部服务器通知用户已经被授权的方法时,无法在该方法上使用[Authorize]属性,因为会话尚未被授权。解决方法是手动创建身份验证cookie。
解决方法的具体步骤可以参考以下链接中的内容:
(我相信使用基本的Microsoft Owin工具时,你必须自己完成这个步骤,如果你愿意,你总是可以自己完成。)
下面是实现这个解决方法的示例代码:
// 创建一个ClaimsIdentity对象,用于保存用户的身份信息 var claimsIdentity = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "John Doe"), new Claim(ClaimTypes.Email, "johndoe@example.com") }, "ApplicationCookie"); // 创建一个ClaimsPrincipal对象,将ClaimsIdentity对象添加到其中 var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); // 创建一个AuthenticationManager对象,用于处理身份验证 var authenticationManager = HttpContext.Current.GetOwinContext().Authentication; // 调用AuthenticationManager的SignIn方法,将ClaimsPrincipal对象保存到cookie中 authenticationManager.SignIn(new AuthenticationProperties { IsPersistent = false // 设置cookie的持久性 }, claimsPrincipal);
通过上述代码,我们可以手动创建一个身份验证cookie,并将用户的身份信息保存其中。这样,就可以在外部服务器通知用户已经被授权的方法中使用[Authorize]属性,确保会话已经被授权。