OWIN Cookie Authentication - олицетворение SQL Server с делегированием Kerberos

После нескольких недель исследований Identity 2.0, олицетворения, делегирования и Kerberos, я все еще не могу найти решение, которое позволит мне олицетворять пользователя ClaimsIdentity, который я создал с помощью OWIN в моем приложении MVC. Специфика моего сценария такова.

Аутентификация Windows отключена + Аноним включен.
Я использую класс запуска OWIN для ручной аутентификации пользователя с помощью нашей Active Directory. Затем я собираю некоторые свойства в файл cookie, который доступен во всей остальной части приложения. Это ссылка, на которую я ссылался при настройке этих классов.

Startup.Auth.cs

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
     AuthenticationType = MyAuthentication.ApplicationCookie,
     LoginPath = new PathString("/Login"),
     Provider = new CookieAuthenticationProvider(),
     CookieName = "SessionName",
     CookieHttpOnly = true,
     ExpireTimeSpan = TimeSpan.FromHours(double.Parse(ConfigurationManager.AppSettings["CookieLength"]))
});

AuthenticationService.cs

    using System;
    using System.DirectoryServices.AccountManagement;
    using System.DirectoryServices;
    using System.Security.Claims;
    using Microsoft.Owin.Security;
    using System.Configuration;
    using System.Collections.Generic;

    using System.Linq;

    namespace mine.Security
    {
        public class AuthenticationService
        {
            private readonly IAuthenticationManager _authenticationManager;
            private PrincipalContext _context;
            private UserPrincipal _userPrincipal;
            private ClaimsIdentity _identity;

        public AuthenticationService(IAuthenticationManager authenticationManager)
        {
            _authenticationManager = authenticationManager;
        }

        /// <summary>
        /// Check if username and password matches existing account in AD. 
        /// </summary>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        public AuthenticationResult SignIn(String username, String password)
        {

            // connect to active directory
            _context = new PrincipalContext(ContextType.Domain,
                                            ConfigurationManager.ConnectionStrings["LdapServer"].ConnectionString,
                                            ConfigurationManager.ConnectionStrings["LdapContainer"].ConnectionString,
                                            ContextOptions.SimpleBind,
                                            ConfigurationManager.ConnectionStrings["LDAPUser"].ConnectionString,
                                            ConfigurationManager.ConnectionStrings["LDAPPass"].ConnectionString);

            // try to find if the user exists
            _userPrincipal = UserPrincipal.FindByIdentity(_context, IdentityType.SamAccountName, username);

            if (_userPrincipal == null)
            {
                return new AuthenticationResult("There was an issue authenticating you.");
            }

            // try to validate credentials
            if (!_context.ValidateCredentials(username, password))
            {
                return new AuthenticationResult("Incorrect username/password combination.");
            }

            // ensure account is not locked out
            if (_userPrincipal.IsAccountLockedOut())
            {
                return new AuthenticationResult("There was an issue authenticating you.");
            }

            // ensure account is enabled
            if (_userPrincipal.Enabled.HasValue && _userPrincipal.Enabled.Value == false)
            {
                return new AuthenticationResult("There was an issue authenticating you.");
            }

            MyContext dbcontext = new MyContext();
            var appUser = dbcontext.AppUsers.Where(a => a.ActiveDirectoryLogin.ToLower() == "domain\" +_userPrincipal.SamAccountName.ToLower()).FirstOrDefault();
            if (appUser == null)
            {
                return new AuthenticationResult("Sorry, you have not been granted user access to the MED application.");
            }

            // pass both adprincipal and appuser model to build claims identity
            _identity = CreateIdentity(_userPrincipal, appUser);
            _authenticationManager.SignOut(MyAuthentication.ApplicationCookie);
            _authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = false }, _identity);


            return new AuthenticationResult();
        }

        /// <summary>
        /// Creates identity and packages into cookie
        /// </summary>
        /// <param name="userPrincipal"></param>
        /// <returns></returns>
        private ClaimsIdentity CreateIdentity(UserPrincipal userPrincipal, AppUser appUser)
        {

            var identity = new ClaimsIdentity(MyAuthentication.ApplicationCookie, ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
            identity.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "Active Directory"));
            identity.AddClaim(new Claim(ClaimTypes.GivenName, userPrincipal.GivenName));
            identity.AddClaim(new Claim(ClaimTypes.Surname, userPrincipal.Surname));
            identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userPrincipal.SamAccountName));
            identity.AddClaim(new Claim(ClaimTypes.Name, userPrincipal.SamAccountName));
            identity.AddClaim(new Claim(ClaimTypes.Upn, userPrincipal.UserPrincipalName));


            if (!String.IsNullOrEmpty(userPrincipal.EmailAddress))
            {
                identity.AddClaim(new Claim(ClaimTypes.Email, userPrincipal.EmailAddress));
            }

            // db claims
            if (appUser.DefaultAppOfficeId != null)
            {
                identity.AddClaim(new Claim("DefaultOffice", appUser.AppOffice.OfficeName));
            }

            if (appUser.CurrentAppOfficeId != null)
            {
                identity.AddClaim(new Claim("Office", appUser.AppOffice1.OfficeName));
            }

            var claims = new List<Claim>();
            DirectoryEntry dirEntry = (DirectoryEntry)userPrincipal.GetUnderlyingObject();

            foreach (string groupDn in dirEntry.Properties["memberOf"])
            {
                string[] parts = groupDn.Replace("CN=", "").Split(',');
                claims.Add(new Claim(ClaimTypes.Role, parts[0]));
            }

            if (claims.Count > 0)
            {
                identity.AddClaims(claims);
            }


            return identity;
        }

        /// <summary>
        /// Authentication result class
        /// </summary>
        public class AuthenticationResult
        {
            public AuthenticationResult(string errorMessage = null)
            {
                ErrorMessage = errorMessage;
            }

            public String ErrorMessage { get; private set; }
            public Boolean IsSuccess => String.IsNullOrEmpty(ErrorMessage);
        }
    }
}

Эта часть, похоже, работает нормально. Тем не менее, я должен быть способен олицетворять ClaimsIdentity при вызове базы данных, потому что в базе данных установлена ??настройка безопасности на уровне ролей. Мне нужно, чтобы соединение выполнялось в контексте ClaimsIdentity для остальной части сеанса этого пользователя.

  • Я установил SPN для Kerberos, и я знаю, что он работает. Это приложение было ранее windows auth с делегацией Kerberos, и оно работало правильно.
  • Пул приложений работает под учетной записью службы, используемой в SPN, которая имеет разрешения делегирования.
  • Объект Identity, который я создал, в значительной степени используется только в контексте приложения. Я имею в виду, что я получаю все необходимые свойства из главным образом каталога Active, но будет два, которые будут созданы из базы данных. Этот идентификатор не отображается непосредственно в таблицу sql или любой другой источник данных.

Может ли кто-нибудь помочь мне указать на пример, где я могу олицетворять объект ClaimsIdentity при создании запросов к базе данных SQL Server?

c#,asp.net-mvc,owin,kerberos,wif,

3

Ответов: 3


4
+50

Возможно, я не понимаю вопрос, но:

Для подключения к серверу SQL с использованием проверки подлинности Windows строка подключения должна использовать «Integrated Security», что означает, что он будет использовать текущий контекст безопасности, который создает соединение. Обычно это будет ваш пользователь AppPool, который в вашем случае является учетной записью службы. Насколько я знаю, вы не можете автоматически передавать свое олицетворение в поток AppPool, используя Kerberos auth . Вот цитата, которую я нашел:

В IIS только базовая аутентификация регистрирует пользователей с маркером безопасности, который проходит по сети на удаленный SQL-сервер. По умолчанию другие режимы безопасности IIS, используемые в сочетании с параметрами элемента конфигурации идентификации, не приведут к появлению маркера, который может аутентифицироваться на удаленном SQL Server.

Поэтому, если вы хотите олицетворять других пользователей, вам нужно будет начать новую тему под руководством пользователя, который вы выдаете себя за себя. Таким образом, соединение Integrated Security будет использовать Windows Auth этого пользователя для подключения к SQL Server.

Я точно не знаю, как это сделать, но вот что-то, что может подтолкнуть вас в правильном направлении:

public void NewThreadToRunSQLQueries(object claimsIdentity) {
    if (claimsIdentity as ClaimsIdentity == null) {
        throw new ArgumentNullException("claimsIdentity");
    }

    ClaimsIdentity claimsIdentity = (ClaimsIdentity)claimsIdentity;
    var claimsIdentitylst = new ClaimsIdentityCollection(new List<IClaimsIdentity> { claimsIdentity });
    IClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimsIdentitylst);
    Thread.CurrentPrincipal = claimsPrincipal; //Set current thread principal

    using(SqlConnection connection = new SqlConnection("Server=myServerAddress;Database=myDataBase;Integrated Security=True;")) 
    {
        connection.Open(); //Open connection under impersonated user account
        //Run SQL Queries
    }
}

Thread thread = new Thread(NewThreadToRunSQLQueries);
thread.Start(_identity);

Редактировать:

Что касается вашего комментария о том, как сделать эту структуру «глобальной», предполагая, что у вас есть доступ к HttpContext в вашем обработчике проверки подлинности, вы можете сделать это:

var principal = new ClaimsPrincipal(_identity);

Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
     HttpContext.Current.User = principal;
}

Поэтому теоретически рабочий поток из IIS должен теперь запускаться под аутентифицированным пользователем (олицетворение). И надежные подключения к SQL Server должны быть возможны. Я говорю теоретически, потому что сам не пробовал. Но в худшем случае вы могли бы получить претензию от HttpContext, чтобы начать отдельный поток, как в моем примере выше. Но если это работает само по себе, вам даже не придется начинать новый поток, как я изначально упоминал.


1 принят

[Обновление] Я смог выполнить это, выполнив следующее. Я создал класс для повторного использования этих методов. В этом классе я использовал System.IdentityModel.Selectorsи System.IdentityModel.Tokensбиблиотеку для генерации a KeberosReceiverSecurityTokenи сохранил ее в памяти.

public class KerberosTokenCacher
{
    public KerberosTokenCacher()
    {

    }

    public KerberosReceiverSecurityToken WriteToCache(string contextUsername, string contextPassword)
    {
        KerberosSecurityTokenProvider provider =
                        new KerberosSecurityTokenProvider("YOURSPN",
                        TokenImpersonationLevel.Impersonation,
                        new NetworkCredential(contextUsername.ToLower(), contextPassword, "yourdomain"));

        KerberosRequestorSecurityToken requestorToken = provider.GetToken(TimeSpan.FromMinutes(double.Parse(ConfigurationManager.AppSettings["KerberosTokenExpiration"]))) as KerberosRequestorSecurityToken;
        KerberosReceiverSecurityToken receiverToken = new KerberosReceiverSecurityToken(requestorToken.GetRequest());

        IAppCache appCache = new CachingService();
        KerberosReceiverSecurityToken tokenFactory() => receiverToken;

        return appCache.GetOrAdd(contextUsername.ToLower(), tokenFactory); // this will either add the token or get the token if it exists

    }

    public KerberosReceiverSecurityToken ReadFromCache(string contextUsername)
    {
        IAppCache appCache = new CachingService();
        KerberosReceiverSecurityToken token = appCache.Get<KerberosReceiverSecurityToken>(contextUsername.ToLower());

        return token;
    }

    public void DeleteFromCache(string contextUsername)
    {
        IAppCache appCache = new CachingService();
        KerberosReceiverSecurityToken token = appCache.Get<KerberosReceiverSecurityToken>(contextUsername.ToLower());

        if(token != null)
        {
            appCache.Remove(contextUsername.ToLower());
        }
    }

}

Теперь, когда пользователи входили в систему с помощью моего AuthenticationService, я создаю билет и сохраняю его в памяти. Когда они выходят из системы, я делаю обратное и удаляю билет из кеша. Заключительная часть (которую я все еще ищу лучше для этого), я добавил код в конструктор моего класса dbcontext.

public MyContext(bool impersonate = true): base("name=MyContext")
{
    if (impersonate)
    {
        var currentUsername = HttpContext.Current.GetOwinContext().Authentication.User?.Identity?.Name;

        if (!string.IsNullOrEmpty(currentUsername)){

            KerberosTokenCacher kerberosTokenCacher = new KerberosTokenCacher();
            KerberosReceiverSecurityToken token = kerberosTokenCacher.ReadFromCache(currentUsername);

            if (token != null)
            {
                token.WindowsIdentity.Impersonate();
            }
            else
            {
                // token has expired or cache has expired so you must log in again
                HttpContext.Current.Response.Redirect("Login/Logoff");
            }

        }
    }
}

Очевидно, что он определенно не идеален, но он позволяет мне использовать аутентификацию Owin Cookie с активным каталогом и иметь установленный билет Kerberos, позволяющий подключиться к базе данных SQL под контекстом пользователя, прошедшего проверку подлинности.


-1

Я предполагаю, что вам не хватает точки конфигурации в IIS, вам нужно разрешить IIS передавать этот пользовательский контекст вам, это не значение по умолчанию.

Взгляните на этот документ, прежде чем пытаться «исправить» свой код. Если это не поможет, сообщите нам и сообщите нам о своей настройке, только код не может сделать трюк.

C #, asp.net-MVC, Owin, Керберос, WIF,
Похожие вопросы