Asp.net MVC 与 Okta 集成 - 在 SigninManager.SignIn 之后替换了 Okta 声明



我正在尝试实现与现有应用程序的Okta集成。 当前应用程序 - 它具有登录屏幕,用户使用 SignInManager.PasswordSignInAsync 方法进行身份验证和登录。

新 - 我正在使用 Okta 登录页面。我的启动文件有以下代码:

public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Ssl3;
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext(() => DependencyResolver.Current.GetService<ApplicationUserManager>());
app.CreatePerOwinContext(() => DependencyResolver.Current.GetService<ApplicationRoleManager>());
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
// app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseOktaMvc(new OktaMvcOptions()
{
OktaDomain = ConfigurationManager.AppSettings["okta:OktaDomain"],
ClientId = ConfigurationManager.AppSettings["okta:ClientId"],
ClientSecret = ConfigurationManager.AppSettings["okta:ClientSecret"],
RedirectUri = ConfigurationManager.AppSettings["okta:RedirectUri"],
PostLogoutRedirectUri = ConfigurationManager.AppSettings["okta:PostLogoutRedirectUri"],
GetClaimsFromUserInfoEndpoint = true,
Scope = new List<string> { "openid", "profile", "email", "offline_access" },
AuthorizationServerId = string.Empty
});
}
}

我的重定向 Uri 是:https://localhost:port/Account/Login

帐户控制器代码:

public class AccountController : Controller
{
private readonly ApplicationSignInManager _signInManager;
private readonly ApplicationUserManager _userManager;
public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
[AllowAnonymous]
public async Task<ActionResult> Login()
{
if (!HttpContext.User.Identity.IsAuthenticated)
{
var properties = new AuthenticationProperties();
properties.RedirectUri = "/Account/Login";
HttpContext.GetOwinContext().Authentication.Challenge(properties, OktaDefaults.MvcAuthenticationType);
return new HttpUnauthorizedResult();
}
var userClaims = HttpContext.GetOwinContext().Authentication.User.Claims;
//Fetching values before Sign IN as they are getting lost, adding as custom claim when receive success status
var accesstoken = userClaims.FirstOrDefault(x => x.Type == "access_token");
var idtoken = userClaims.FirstOrDefault(c => c.Type == "id_token");
var refreshtoken = userClaims.FirstOrDefault(c => c.Type == "refresh_token");
var expiresat = userClaims.FirstOrDefault(c => c.Type == "exp");
var issuedat = userClaims.FirstOrDefault(c => c.Type == "iat");
//SignInManager
//Not getting below information after using ExternalCookie so commenting
//ExternalLoginInfo loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
//Get Custom ExternalloginInfo
ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
ExternalLoginInfo externalInfo = new ExternalLoginInfo();
externalInfo.DefaultUserName = externalLogin.DefaultUserName;
externalInfo.Email = externalLogin.Email;
externalInfo.Login = externalLogin.LoginInfo;
externalInfo.ExternalIdentity = externalLogin.ExternalIdentity;
var result = await _signInManager.ExternalSignInAsync(externalInfo, isPersistent: false);
switch (result)
{

当我将调试点放在 Switch 上时,User.Identity 没有 Okta Claims,它只有带有 UserId、Email、SecurityStamp 值的 AspNet.Identity Claims。 当我将登录状态设置为成功时,我在 AspNetUserClaims 表中手动添加访问令牌、标识令牌等声明,然后再次登录用户以获取更新的标识。 当它给出失败状态时,我创建外部用户并使用AddLoginAsync方法将Sub与AspNetUsers表UserId值映射。

这是正确的方法吗?此外,即使在添加ExternalCookie之后,我也没有获得ExternalLoginInfo,因此手动创建ExternalLoginInfo对象。

更改后

[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
if (!HttpContext.User.Identity.IsAuthenticated)
{
return new ChallengeResult("OpenIdConnect", Url.Action("ExtLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}
return RedirectToAction(returnUrl?? "{Controller}/{Action}");  //verify this       
}
[AllowAnonymous]
public async Task<ActionResult> ExtLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
if (loginInfo.Email == null)//Email is coming null from loginInfo
{
loginInfo.Email = loginInfo.ExternalIdentity.Claims.FirstOrDefault(c => c.Type == "email").Value;
}
var user = await this._userManager.FindAsync(loginInfo.Login);
if (user == null)
{
//Create User 
}
//Add Okta provided Claims here
var idTokenClaim = user.Claims.FirstOrDefault(c => c.ClaimType == "id_token");
if (idTokenClaim != null)
{
_userManager.RemoveClaim(user.Id, new Claim(idTokenClaim.ClaimType, idTokenClaim.ClaimValue));
_userManager.AddClaim(user.Id, new Claim("id_token", loginInfo.ExternalIdentity.Claims.FirstOrDefault(c => c.Type == "id_token").Value));
}
else
{
_userManager.AddClaim(user.Id, new Claim("id_token", loginInfo.ExternalIdentity.Claims.FirstOrDefault(c => c.Type == "id_token").Value));
}
var accessTokenClaim = user.Claims.FirstOrDefault(c => c.ClaimType == "access_token");
if (accessTokenClaim != null)
{
_userManager.RemoveClaim(user.Id, new Claim(accessTokenClaim.ClaimType, accessTokenClaim.ClaimValue));
_userManager.AddClaim(user.Id, new Claim("access_token", loginInfo.ExternalIdentity.Claims.FirstOrDefault(c => c.Type == "access_token").Value));
}
else
{
_userManager.AddClaim(user.Id, new Claim("access_token", loginInfo.ExternalIdentity.Claims.FirstOrDefault(c => c.Type == "access_token").Value));
}
var refreshTokenClaim = user.Claims.FirstOrDefault(c => c.ClaimType == "refresh_token");
if (refreshTokenClaim != null)
{
_userManager.RemoveClaim(user.Id, new Claim(refreshTokenClaim.ClaimType, refreshTokenClaim.ClaimValue));
_userManager.AddClaim(user.Id, new Claim("refresh_token", loginInfo.ExternalIdentity.Claims.FirstOrDefault(c => c.Type == "refresh_token").Value));
}
else
{
_userManager.AddClaim(user.Id, new Claim("refresh_token", loginInfo.ExternalIdentity.Claims.FirstOrDefault(c => c.Type == "refresh_token").Value));
}
var expClaim = user.Claims.FirstOrDefault(c => c.ClaimType == "exp");
if (expClaim != null)
{
_userManager.RemoveClaim(user.Id, new Claim(expClaim.ClaimType, expClaim.ClaimValue));
_userManager.AddClaim(user.Id, new Claim("exp", GetClaimsDateTime(Convert.ToInt64(loginInfo.ExternalIdentity.Claims.FirstOrDefault(c => c.Type == "exp").Value))));
}
else
{
_userManager.AddClaim(user.Id, new Claim("exp", GetClaimsDateTime(Convert.ToInt64(loginInfo.ExternalIdentity.Claims.FirstOrDefault(c => c.Type == "exp").Value))));
}
var iatClaim = user.Claims.FirstOrDefault(c => c.ClaimType == "iat");
if (iatClaim != null)
{
_userManager.RemoveClaim(user.Id, new Claim(iatClaim.ClaimType, iatClaim.ClaimValue));
_userManager.AddClaim(user.Id, new Claim("iat", GetClaimsDateTime(Convert.ToInt64(loginInfo.ExternalIdentity.Claims.FirstOrDefault(c => c.Type == "iat").Value))));
}
else
{
_userManager.AddClaim(user.Id, new Claim("iat", GetClaimsDateTime(Convert.ToInt64(loginInfo.ExternalIdentity.Claims.FirstOrDefault(c => c.Type == "iat").Value))));
}
//Sign the User with additional claims
result = await _signInManager.ExternalSignInAsync(loginInfo, isPersistent: true);
if(result == SignInStatus.Success){
return RedirectToAction(returnUrl ?? "{Controller}/{Action}");
}

用于从 Okta 获取活动访问令牌的授权过滤器 创建了自定义筛选器以用作所有控制器上的授权筛选器

public class CustomAuthAttribute : AuthorizeAttribute
{
public override void OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
//Get New Accesstoken before it expires
if (filterContext.HttpContext.User.Identity.IsAuthenticated)
{
var _oktaDomain = ConfigurationManager.AppSettings["okta:OktaDomain"];
var _redirectUri = ConfigurationManager.AppSettings["okta:RedirectUri"];
var _clientId = ConfigurationManager.AppSettings["okta:ClientId"];
var _clientSecret = ConfigurationManager.AppSettings["okta:ClientSecret"];
var _refreshToken = HttpContext.Current.User.Identity.GetUserRefreshToken();
var _issuedat = HttpContext.Current.User.Identity.GetUserIat();
var _expiresat = HttpContext.Current.User.Identity.GetUserExp();
var expire = 3600;
if (DateTime.Now.Subtract(Convert.ToDateTime(_issuedat)).TotalSeconds >= (expire - 3540))//Testing for 1 min
{
var client = new RestClient(_oktaDomain + "/oauth2/v1/token");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddParameter("client_id", _clientId);
request.AddParameter("client_secret", _clientSecret);
request.AddParameter("grant_type", "refresh_token");
request.AddParameter("redirect_uri", _redirectUri);
request.AddParameter("scope", "openid profile email offline_access");
request.AddParameter("refresh_token", _refreshToken);
IRestResponse response = client.Execute(request);
//Console.WriteLine(response.Content);
dynamic jsonResponse = null;
if (response.StatusCode == HttpStatusCode.OK) { 
jsonResponse = JObject.Parse(response.Content);
}
if (jsonResponse.error == null)
{
//find common place to keep below
ClaimsPrincipal cp = HttpContext.Current.GetOwinContext().Authentication.User;
foreach (var uidentity in cp.Identities)
{
var idTokenClaim = uidentity.FindFirst("id_token");
if (idTokenClaim != null)
{
uidentity.RemoveClaim(idTokenClaim);
uidentity.AddClaim(new Claim("id_token", jsonResponse.id_token.Value));
}
else
{
uidentity.AddClaim(new Claim("id_token", jsonResponse.id_token.Value));
}
var accessTokenClaim = uidentity.FindFirst("access_token");
if (accessTokenClaim != null)
{
uidentity.RemoveClaim(accessTokenClaim);
uidentity.AddClaim(new Claim("access_token", jsonResponse.access_token.Value));
}
else
{
uidentity.AddClaim(new Claim("access_token", jsonResponse.access_token.Value));
}
var refreshTokenClaim = uidentity.FindFirst("refresh_token");
if (refreshTokenClaim != null)
{
uidentity.RemoveClaim(refreshTokenClaim);
uidentity.AddClaim(new Claim("refresh_token", jsonResponse.refresh_token.Value));
}
else
{
uidentity.AddClaim(new Claim("refresh_token", jsonResponse.refresh_token.Value));
}
var expClaim = uidentity.FindFirst("exp");
if (expClaim != null)
{
uidentity.RemoveClaim(expClaim);
uidentity.AddClaim(new Claim("exp", DateTime.UtcNow.AddSeconds(Convert.ToDouble(jsonResponse.expires_in)).ToLocalTime().ToString(CultureInfo.InvariantCulture)));
}
else
{
uidentity.AddClaim(new Claim("exp", DateTime.UtcNow.AddSeconds(Convert.ToDouble(jsonResponse.expires_in)).ToLocalTime().ToString(CultureInfo.InvariantCulture)));
}
var iatClaim = uidentity.FindFirst("iat");
if (iatClaim != null)
{
uidentity.RemoveClaim(iatClaim);
uidentity.AddClaim(new Claim("iat", DateTime.UtcNow.ToLocalTime().ToString()));
}
else
{
uidentity.AddClaim(new Claim("iat", DateTime.UtcNow.ToString()));
}
//This will only add claims to Identity, Find a way to save in DB as well
HttpContext.Current.GetOwinContext().Authentication.SignIn(uidentity);
}                        
}           
}
注销:单击"注销">

按钮时,将调用"注销",并将"注销后重定向URI"设置为"帐户/注销"。但是在将重定向 Uri 设置为授权代码/回调后,注销变成了注销、授权端点、回调的无限循环。我在这里做错了什么?

public void Logout()
{
if (HttpContext.User.Identity.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.SignOut(
DefaultAuthenticationTypes.ExternalCookie,
CookieAuthenticationDefaults.AuthenticationType,                                     
OktaDefaults.MvcAuthenticationType);  
}
}
public ActionResult LogOff()
{

登录操作在授权代码/回调后调用,因为它被设置为默认路由 返回重定向到操作("登录","帐户"(; }

在你的startup.cs

app.UseOktaMvc(new OktaMvcOptions()
{
OktaDomain = ConfigurationManager.AppSettings["okta:OktaDomain"],
ClientId = ConfigurationManager.AppSettings["okta:ClientId"],
ClientSecret = ConfigurationManager.AppSettings["okta:ClientSecret"],
RedirectUri = ConfigurationManager.AppSettings["okta:RedirectUri"],
PostLogoutRedirectUri = ConfigurationManager.AppSettings["okta:PostLogoutRedirectUri"],
Scope = new List<string> { "openid", "profile", "email", "offline_access" },
});

在您的web.config请确保您具有重定向 uri,如下所示

<add key="okta:RedirectUri" value="https://localhost:{port}/authorization-code/callback" />

不应为此路由编写控制器。这已经由 Okta.AspNet 包提供。登录过程由此路由本身处理,除非你想要执行开箱即用的操作。

默认路由的操作应如下所示:

if (!HttpContext.User.Identity.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(OpenIdConnectAuthenticationDefaults.AuthenticationType);
return new HttpUnauthorizedResult();
}
return RedirectToAction("Index", "Home");

现在,您应该能够读取应用程序中的所有用户声明。

var claims = HttpContext.GetOwinContext().Authentication.User.Claims.ToList();

您还可以通过[Authorize]属性装饰控制器/操作,以保护它们免受未经身份验证的访问。

相关内容

最新更新