我正在尝试与现有应用程序实现Okta集成。当前应用程序-它具有登录屏幕,并且使用SignInManager.PasswordSignInAsync方法对用户进行身份验证和登录。
NEW-我正在使用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声明,它只有AspNet.Identity声明具有UserId,Email和SecurityStamp值。当我获得SigninStatus为成功时,我在AspNetUserClaims表中手动添加accesstoken,idtoken等声明,然后再次登录用户以获取更新的Identity。并且当它给出失败状态时,我创建外部用户并使用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获取Active AccessToken//创建自定义过滤器以在所有控制器上用作“授权”过滤器
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);
}
}
}
注销:单击注销按钮时,将调用注销,并将PostLogoutRedirectUri设置为Account / LogOff。但是在将RedirectUri设置为授权码/回调后,注销将成为注销,授权端点,回调的无限循环。我在这里做错了吗?
public void Logout()
{
if (HttpContext.User.Identity.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.SignOut(
DefaultAuthenticationTypes.ExternalCookie,
CookieAuthenticationDefaults.AuthenticationType,
OktaDefaults.MvcAuthenticationType);
}
}
public ActionResult LogOff()
{
//登录操作在授权代码/回调之后被调用,因为它被设置为默认路由 返回RedirectToAction(“ Login”,“ Account”); }
在您的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]
属性修饰控制器/动作,以防止未经授权的访问。