SSO with AD FS and OWIN how to create an account a

2019-03-29 08:45发布

I configure a Web App that use AD FS, for this I use OWIN.

For the login, all is ok. If i'm an user of a domain and go to the website, he is automatically connected.

But what I want to have is to handle users and roles by myself after login.

So I want to check that an user exists in my database with this AD account (this process will be make before the login in another application)

I want to use Identity from Microsoft to handle claims (roles and permissions). But I don't understand how to put my code to handle the successfull connection from AD FS (with Ws-Federation) and add verification and fill in the right roles.

My code in 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
                //{

                //}
            });

    }

In Web.config, realm is the link to my Web App (https://ssoadfs.test) and adfsMetadata is the link to metadata.xml from AD FS.

What is the way to go to set my role and login logic after AD FS connection ?

Schema that what I was thinking:

enter image description here

EDIT: After some tries, I cannot handle any success callback. I don't want to have to handle roles in HomeController ...

My last Auth config:

            _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);
                    }
                }
            });

In this code, I tried CallbackPath (nothing appeared in my log), WsFederationAuthenticationNotifications.SecurityTokenValidated (nothing appeared in my log), CookieAuthenticationProvider.OnResponseSignIn (same nothing happened)

In HomeController i'm able to have Identity.Name:

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

        return View();
    }

Did I miss something to get Notifications working or Provider in CookieAuthenticationOptions ???

1条回答
Luminary・发光体
2楼-- · 2019-03-29 09:17

If you use ASP.NET Identity 2.0 or later version, you can use an approach similar to it shown below. Please note that this approach assign GroupRoles to the user instead of assigning each of roles one by one. You can change necessary parts according to your needs.

[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;
}


Hope this helps...

查看更多
登录 后发表回答