【问题标题】:SSO with AD FS and OWIN how to create an account and handle permissionsSSO 与 AD FS 和 OWIN 如何创建帐户和处理权限
【发布时间】:2016-04-08 14:00:10
【问题描述】:

我配置了一个使用 AD FS 的 Web 应用,为此我使用 OWIN。

对于登录,一切正常。如果我是域用户并访问该网站,他会自动连接。

但我想要的是登录后自己处理用户和角色。

所以我想检查我的数据库中是否存在使用此 AD 帐户的用户(此过程将在登录另一个应用程序之前进行)

我想使用 Microsoft 的 Identity 来处理声明(角色和权限)。但我不明白如何让我的代码处理来自 AD FS(使用 Ws-Federation)的成功连接并添加验证并填写正确的角色。

我在 ConfigureAuth 中的代码:

public partial class Startup
{
    private static string realm = ConfigurationManager.AppSettings["ida:Wtrealm"];
    private static string adfsMetadata = ConfigurationManager.AppSettings["ida:ADFSMetadata"];
    private NLogLoggingService _loggingService;

    public void ConfigureAuth(IAppBuilder app)
    {
        _loggingService = new NLogLoggingService("Startup");
        _loggingService.Debug("ConfigureAuth");

        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseCookieAuthentication(new CookieAuthenticationOptions());

        app.UseWsFederationAuthentication(
            new WsFederationAuthenticationOptions
            {
                Wtrealm = realm,
                MetadataAddress = adfsMetadata,

                //CallbackPath = PathString.FromUriComponent("/Account/TestCallback"),

                // https://msdn.microsoft.com/en-us/library/microsoft.owin.security.authenticationmode(v=vs.113).aspx
                AuthenticationMode = AuthenticationMode.Passive,

                //Notifications = new WsFederationAuthenticationNotifications
                //{

                //}
            });

    }

在 Web.config 中,realm 是指向我的 Web 应用 (https://ssoadfs.test) 的链接,adfsMetadata 是指向 AD FS 中 metadata.xml 的链接。

在 AD FS 连接后设置我的角色和登录逻辑的方法是什么?

我在想的架构:

编辑: 经过一些尝试,我无法处理任何成功回调。我不想处理 HomeController 中的角色...

我上次的身份验证配置:

            _loggingService = new NLogLoggingService("Startup");
        _loggingService.Debug("ConfigureAuth");

        // Configure the db context, user manager and signin manager to use a single instance per request
        app.CreatePerOwinContext(ApplicationUser.ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
        app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);

        app.SetDefaultSignInAsAuthenticationType(DefaultAuthenticationTypes.ApplicationCookie);

        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            Provider = new CookieAuthenticationProvider
            {
                OnResponseSignIn = ctx =>
                {
                    _loggingService.Debug("OnResponseSignIn");
                    ctx.Identity = TransformClaims(ctx, app);
                },
                // Enables the application to validate the security stamp when the user logs in.
                // This is a security feature which is used when you change a password or add an external login to your account.  
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                    validateInterval: TimeSpan.FromMinutes(30),
                    regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
            }
        });

        app.UseWsFederationAuthentication(
            new WsFederationAuthenticationOptions
            {
                Wtrealm = realm,
                MetadataAddress = adfsMetadata,
                Caption = "Active Directory",

                CallbackPath = PathString.FromUriComponent("/Account/TestCallback"),

                Notifications = new WsFederationAuthenticationNotifications
                {
                    SecurityTokenValidated = n =>
                    {
                        new NLogLoggingService("Startup").Debug("SecurityTokenValidated");

                        var incomingClaimsFromAdfs = n.AuthenticationTicket.Identity.Claims.ToList();
                        var incomingClaimsHasNameIdentifier =
                            incomingClaimsFromAdfs.Any(
                                c => c.Type == System.Security.Claims.ClaimTypes.NameIdentifier);

                        _loggingService.Debug("SecurityTokenValidated - incomingClaimsHasNameIdentifier: " +
                                              incomingClaimsHasNameIdentifier);
                        if (!incomingClaimsHasNameIdentifier)
                        {
                            var emailClaim =
                                incomingClaimsFromAdfs.First(c => c.Type == System.Security.Claims.ClaimTypes.Name);
                            _loggingService.Debug(emailClaim.Value);
                        }

                        //if (!incomingClaimsHasNameIdentifier)
                        //{
                        //    var emailClaim = incomingClaimsFromAdfs.First(c => c.Type == System.Security.Claims.ClaimTypes.Name);
                        //    incomingClaimsFromAdfs.Add();

                        //    IUser user = await this.UserStore.FindByNameOrEmailAsync(userNameOrEmailAddress);
                        //    if ((Entity<long>)user == (Entity<long>)null)
                        //        LoginResult = new ApplicationUserManager.LoginResult(LoginResultType.InvalidUserNameOrEmailAddress, default(IUser));
                        //    //else if (!loggedInFromExternalSource && new PasswordHasher().VerifyHashedPassword(user.Password, plainPassword) != PasswordVerificationResult.Success)
                        //    //    LoginResult = new UserManager<TTenant, TRole, TUser>.LoginResult(LoginResultType.InvalidPassword, user);
                        //    else
                        //        LoginResult = await this.CreateLoginResultAsync(user, tenant);
                        //}
                        //else
                        //{
                        //    throw new ApplicationException("Get ADFS to provide the NameIdentifier claim!");
                        //}

                        //var normalizedClaims = incomingClaimsFromAdfs.Distinct(new ClaimComparer());
                        //var claimsIdentity = new ClaimsIdentity(normalizedClaims, n.AuthenticationTicket.Identity.AuthenticationType);
                        //n.AuthenticationTicket = new AuthenticationTicket(claimsIdentity, n.AuthenticationTicket.Properties);
                        return Task.FromResult(0);
                    }
                }
            });

在这段代码中,我尝试了 CallbackPath(我的日志中没有出现任何内容)、WsFederationAuthenticationNotifications.SecurityTokenValidated(我的日志中没有出现任何内容)、CookieAuthenticationProvider.OnResponseSignIn(同样没有发生任何事情)

在 HomeController 中,我可以拥有 Identity.Name:

public ActionResult Index()
    {
        if (HttpContext.GetOwinContext().Authentication.User.Identity.IsAuthenticated)
        {
            new NLogLoggingService("Home").Debug("User is authenticated");
        }

        return View();
    }

我是否错过了让通知工作或 CookieAuthenticationOptions 中的提供程序的东西???

【问题讨论】:

  • 使用 AD 和身份不是开箱即用的选项。您被困在创建该功能中。您将需要 2 个 UserManager 实例配置不同...
  • 你能开发“2个UserManager实例”吗?现在,我想创建一个与 AD WS-Federation 一起使用的 Wep API(仍然是 todo),然后使用 cookie 调用另一个方法来检查用户是否作为用户存在于数据库中。
  • @user18620 你好,这不是一个真正的解决方案......但我通过在 App_Start/RouteConfig 中设置默认路由在 AccountController.AuthenticationCallbackAD() 中检查了这一点。在此方法中,我创建用户或检查他是否使用 ASP.NET Identity UserManager 进行了验证。并添加自定义声明。
  • 你找到这个问题的最终解决方案了吗?
  • @PreguntonCojoneroCabrón 不,我现在让它在 HomeController 中工作,这不是一个漂亮的实现,但我没有时间寻找更好的解决方案

标签: c# asp.net-mvc owin adfs ws-federation


【解决方案1】:

如果您使用ASP.NET Identity 2.0 或更高版本,您可以使用类似于如下所示的方法。请注意,这种方法将GroupRoles 分配给用户,而不是一一分配每个角色。您可以根据需要更换必要的零件。

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

    ApplicationGroupManager groupManager = new ApplicationGroupManager();

    if (Membership.ValidateUser(model.UserName, model.Password))
    {
        FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);

        //Assign Roles to the current User
        ApplicationUser user = UserManager.FindByName(model.UserName);

        //If the user is registered in the system (ASP.NET Identity) add record to AspNetUsers table 
        if (user != null)
        {
            //Returns Group Id and Role Id by using User Id parameter
            var userGroupRoles = groupManager.GetUserGroupRoles("bfd9730e-2093-4fa0-89a2-226e301d831b"); 
            foreach (var role in userGroupRoles)
            {
                string roleName = RoleManager.FindById(role.ApplicationRoleId).Name;
                UserManager.AddToRole(user.Id, roleName);
            }
        }
        else
        {
            //crate new user
            //first retrieve user info from LDAP:
            // Create an array of properties that we would like and add them to the search object  
            string[] requiredProperties = new string[] { "samaccountname", "givenname", "sn", "mail", "physicalDeliveryOfficeName", "title" };
            var userInfo = CreateDirectoryEntry(model.UserName, requiredProperties);

            var newUser = new ApplicationUser();
            newUser.UserName = userInfo.GetDirectoryEntry().Properties["samaccountname"].Value.ToString();
            newUser.Name = userInfo.GetDirectoryEntry().Properties["givenname"].Value.ToString();
            newUser.Surname = userInfo.GetDirectoryEntry().Properties["sn"].Value.ToString();
            newUser.Email = userInfo.GetDirectoryEntry().Properties["mail"].Value.ToString();
            newUser.EmailConfirmed = true;
            newUser.PasswordHash = null;

            var result = await UserManager.CreateAsync(newUser);
            if (result.Succeeded)
            {
                //If the user is created ...
            }

            //Assign user group (and roles)
            var defaultGroup = "751b30d7-80be-4b3e-bfdb-3ff8c13be05e";
            groupManager.SetUserGroups(newUser.Id, new string[] { defaultGroup });
        }
        return this.RedirectToAction("Index", "Issue");
    }

    this.ModelState.AddModelError(string.Empty, "Wrong username or password!");
    return this.View(model);
}


static SearchResult CreateDirectoryEntry(string sAMAccountName, string[] requiredProperties)
{
    DirectoryEntry ldapConnection = null;

    try
    {
        ldapConnection = new DirectoryEntry("LDAP://OU=******, DC=******, DC=******", "acb@xyz.com", "YourPassword");
        ldapConnection.AuthenticationType = AuthenticationTypes.Secure;

        DirectorySearcher search = new DirectorySearcher(ldapConnection);
        search.Filter = String.Format("(sAMAccountName={0})", sAMAccountName);

        foreach (String property in requiredProperties)
            search.PropertiesToLoad.Add(property);

        SearchResult result = search.FindOne();
        //SearchResultCollection searchResultCollection = search.FindAll(); //You can also retrieve all information

        if (result != null)
        {                
            return result;
        }
        else {
            return null;
            //Console.WriteLine("User not found!");
        }
    }
    catch (Exception e)
    {
        Console.WriteLine("Exception caught:\n\n" + e.ToString());
    }

    return null;
}


希望这会有所帮助...

【讨论】:

  • 您好 Murat,感谢您帮助我。但我的问题更多是我无法以 OWIN 的正确方式处理回调。我永远不会传入登录页面,因为它是一个应用程序 SSO(因此用户在进入我的应用程序之前已经过 Owin 的身份验证)。现在,我在 HomeController 中添加逻辑以检查用户是否已通过身份验证,然后在 UserManager 中添加用户 ... :(
  • 不客气,杰罗姆。我不明白你为什么使用 HomeController 而不是 AccountController?另一方面,如果你有足够的时间并且没有使用 ASP.NET Identity 2.0 或更高版本,你可以看看ASP.NET MVC and Identity 2.0: Understanding the Basics 之类的文章。祝你好运:)
  • 我使用 AspNet.Identity.Core、.EntityFramework、.Owin。但我想使用 AD FS 和 Owin 来简化配置(只需要 AD 中的 metadata.xml)。我使用与来自 Identity 的 UserManager 类似的代码,在 HomeController 中添加此逻辑不是一个选择。我可以将它放在 AccountController 中并定义默认路由。但我确信 Owin 可以帮助我在 Startup Auth Config 中处理这个问题
猜你喜欢
  • 2018-08-03
  • 1970-01-01
  • 1970-01-01
  • 2014-10-06
  • 1970-01-01
  • 1970-01-01
  • 2022-01-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多