与Okta的Asp.net MVC集成-在SigninManager.SignIn之后替换Okta索赔问题

问题描述 投票:0回答:1

我正在尝试与现有应用程序实现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”); }

c# asp.net-mvc asp.net-identity okta
1个回答
0
投票

在您的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]属性修饰控制器/动作,以防止未经授权的访问。

© www.soinside.com 2019 - 2024. All rights reserved.