Вопрос

Я модифицирую существующую систему, которая использует SharePoint 2013 с проверкой подлинности на основе форм (FBA), чтобы позволить пользователям самостоятельно создавать учетные записи и управлять учетными записями.Одна из вещей, которыми они могут управлять, - это членство в ролях;фактически, им присваивается код доступа к роли, который они предоставляют системе, и это добавляет их к роли, позволяя им получать доступ к сайтам, к которым этой роли был предоставлен доступ.Частью решения является пользовательский экран входа в систему, который помогает поддерживать все это и делает его более удобным для пользователя.Все это работает относительно хорошо.

Задача, над которой я работаю, заключается в добавлении поддержки внешних поставщиков аутентификации, а не аутентификации на основе форм - это бизнес-требование.Существует множество информации в Интернете (например,g. http://blogs.msdn.com/b/mvpawardprogram/archive/2011/06/17/mvps-for-sharepoint-2010-using-azure-acs-v2-to-authenticate-external-systems-users.aspx) о том, как добавить Azure ACS в качестве поставщика удостоверений, и это работает.Теперь проблема заключается в том, как добавить элемент авторизации роли самообслуживания.

Я получил простое дополнение к заявлениям, которое работает нормально, следуя руководству по адресу http://www.microsoft.com/en-us/download/details.aspx?id=27569.Проблема, с которой я сталкиваюсь, - это "обычная" проблема, связанная с тем, что встроенные STS выполняют кэширование токена, поэтому при изменении роли пользователя они по-прежнему получают старый кэшированный токен.Мой код увеличения утверждений не вызывается (конечно, нет - зачем бы это было, если STS использует кэшированный билет), поэтому изменение членства не просматривается.

Это в значительной степени тот же самый вопрос, который обсуждался на проверьте разрешения, показывающие неверную информацию и http://www.shillier.com/archive/2010/10/25/authorization-failures-with-claims-based-authentication-in-sharepoint-2010.aspx, с приведенным общим ответом об изменении срока службы токена.У меня есть несколько проблем и вопросов по этому поводу.

  1. Какой срок службы применяется в сценарии с внешними претензиями?Это не токен Windows, и это не токен FBA, так что ни WindowsTokenLifetime ни FormsTokenLifetime кажутся уместными, хотя именно эти два варианта все советуют изменить.

  2. Я не хочу, чтобы срок действия токена истекал для конечного пользователя за неоправданно короткое время, я просто хочу, чтобы у STS истек срок действия кэшированной версии или в идеале никогда не кэшировать ее вообще (я знаю, что там проблема с производительностью - мне все равно, для нашего масштаба это не будет иметь значения).Однако кажется, что эти две вещи взаимосвязаны.Есть ли способ разделить их?

  3. Если я не могу отделить, есть ли способ, чтобы экран входа в систему указывал системе, что я хочу, чтобы заявка была повторно дополнена?Могу ли я просто сделать это сам, изменив текущий идентификатор пользователя?Конечно, это происходит за спиной системы, поэтому я не хочу этого делать, если в моих силах.

  4. Все это, похоже, в конечном счете сводится к кэшированию в OOB STS, поскольку все остальные движущиеся части кажутся правильными.Является ли "правильным" способом избежать этого создание пользовательского STS, у которого нет поведения кэширования, и регистрация его в качестве поставщика удостоверений?(Если бы у меня был пользовательский STS, похоже, что на этом этапе я бы не стал беспокоиться об увеличении претензий, я бы просто выдал все надлежащие претензии в первую очередь.) Однако это кажется значительным объемом работы (хотя я знаю, что есть образцы, включающие http://archive.msdn.microsoft.com/SelfSTS), тем более что, похоже, мне придется подключить подключение проверяющей стороны к Azure ACS как часть процесса, где OOB STS делает это "волшебным образом" с помощью нескольких строк PowerShell и некоторых усилий.

  5. Или мне нужно отказаться от претензий и просто сменить сторону FBA?Это кажется ужасным способом, поскольку теперь мне нужно подключить все данные OpenID в моем коде аутентификации для FBA, и я отступаю от утверждений, когда знаю, что должен перейти к утверждениям.

Задержка, скажем, в 60 минут просто неприемлема, когда в модели FBA я могу внести изменения немедленно, заставив пользователя снова войти в систему, что приведет к получению членства в новой роли.

Это было полезно?

Решение

Ладно, у меня все получилось (ура!), но, черт возьми, это было тяжело.

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

По сути, я отошел от кода, упомянутого в https://stackoverflow.com/questions/8070456/re-calculate-claims-for-sharepoint-2010-user-while-the-user-is-still-logged-in/8185646#8185646 .Одна вещь, которую я должен был сделать, это удалить e.ReissueCookie поскольку это полностью нарушило аутентификацию для меня (пользователь вышел из системы в результате этого действия).В моем случае я не хотел делать это через специальную страницу, поэтому я использовал пользовательское значение в коллекции приложений, которое я немедленно удаляю после выполнения действий с ним.Соответствующий код в моем приемнике событий, который добавляет и удаляет код в global.asax следует:

        private readonly string AsaxText = @"
<%@ Assembly Name=""" + System.Reflection.Assembly.GetAssembly(typeof(ClaimSupport)).FullName + @"""%>
<script runat='server'> 
    void SessionAuthentication_SessionSecurityTokenReceived(object sender, Microsoft.IdentityModel.Web.SessionSecurityTokenReceivedEventArgs e) { 
        var application = HttpContext.Current.Application;
        if (application == null) {
            return;
        }
        var key = Company.SharePoint.Authentication.Data.ClaimSupport.ApplicationEventKey(e.SessionToken.ClaimsPrincipal.Identity.Name);
        var applicationValue = application[key];
        if (applicationValue == null) { 
            return;
        }
        var applicationString = applicationValue.ToString();
        if (string.IsNullOrWhiteSpace(applicationString)) {
            return;
        }
        application[key] = null;
        var sam = sender as Microsoft.IdentityModel.Web.SessionAuthenticationModule; 
        var logonWindow = Microsoft.SharePoint.Administration.Claims.SPSecurityTokenServiceManager.Local.LogonTokenCacheExpirationWindow; 
        var newValidTo = System.DateTime.UtcNow.Add(logonWindow);
        var currentPrincipal = e.SessionToken.ClaimsPrincipal;
        var claimsIdentity = (Microsoft.IdentityModel.Claims.IClaimsIdentity)currentPrincipal.Identity;

        var heartbeatClaim = GetHeartbeatClaim(claimsIdentity);
        var issuer = heartbeatClaim.Issuer;
        var originalIssuer = heartbeatClaim.OriginalIssuer;
        RemoveExistingEventClaims(claimsIdentity);
        AddEventClaim(claimsIdentity, applicationString, issuer, originalIssuer);

        e.SessionToken = sam.CreateSessionSecurityToken( 
            currentPrincipal, 
            e.SessionToken.Context, 
            e.SessionToken.ValidFrom, 
            newValidTo, 
            e.SessionToken.IsPersistent); 
        //e.ReissueCookie = true; - commented out because it broke things for me, but kept for reference
    } 

    private Microsoft.IdentityModel.Claims.Claim GetHeartbeatClaim(Microsoft.IdentityModel.Claims.IClaimsIdentity claimsIdentity) {
        var heartbeatClaim = (from c in claimsIdentity.Claims
                              where
                                  (c.ClaimType == Company.SharePoint.Authentication.Data.ClaimSupport.EventClaimType)
                              &&
                                  (c.Value == Company.SharePoint.Authentication.Data.ClaimSupport.HeartbeatClaimValue)
                              select c).FirstOrDefault();
        return heartbeatClaim;
    }

    private void AddEventClaim(Microsoft.IdentityModel.Claims.IClaimsIdentity claimsIdentity, string additionalEvent, string issuer, string originalIssuer) {
        var eventClaim = new Microsoft.IdentityModel.Claims.Claim(Company.SharePoint.Authentication.Data.ClaimSupport.EventClaimType, additionalEvent, HynesITe.SharePoint.Authentication.Data.ClaimSupport.EventClaimValueType, issuer, originalIssuer);
        claimsIdentity.Claims.Add(eventClaim);
    }

    private static void RemoveExistingEventClaims(Microsoft.IdentityModel.Claims.IClaimsIdentity claimsIdentity) {
        var currentClaims = (from c in claimsIdentity.Claims
                             where
                                 (c.ClaimType == HynesITe.SharePoint.Authentication.Data.ClaimSupport.EventClaimType)
                             &&
                                 (c.Value != HynesITe.SharePoint.Authentication.Data.ClaimSupport.HeartbeatClaimValue)
                             select c).ToList();
        foreach (var claim in currentClaims) {
            claimsIdentity.Claims.Remove(claim);
        }
    }
</script> 
";

        private void AddGlobalAsax(SPFeatureReceiverProperties properties) {
            var webApp = properties.Feature.Parent as SPWebApplication;
            if (webApp == null) {
                throw new SPException("Cannot add global.asax entries.");
            }
            var zones = Enum.GetValues(typeof(SPUrlZone)).Cast<SPUrlZone>().ToArray();
            var paths =
                zones.Select(z => Path.Combine(webApp.GetIisSettingsWithFallback(z).Path.ToString(), "global.asax"))
                    .Distinct().Where(File.Exists).ToArray();
            var globalAsaxFiles = new List<string>();
            globalAsaxFiles.AddRange(paths);
            foreach (var asax in from asax in globalAsaxFiles
                                 let contents = File.ReadAllText(asax)
                                 where !contents.Contains(AsaxText)
                                 select asax) {
                File.AppendAllText(asax, AsaxText);
            }
        }

        private void RemoveGlobalAsax(SPFeatureReceiverProperties properties) {
            var webApp = properties.Feature.Parent as SPWebApplication;
            if (webApp == null) {
                throw new SPException("Cannot add global.asax entries.");
            }
            var zones = Enum.GetValues(typeof(SPUrlZone)).Cast<SPUrlZone>().ToArray();
            var paths =
                zones.Select(z => Path.Combine(webApp.GetIisSettingsWithFallback(z).Path.ToString(), "global.asax"))
                    .Distinct().Where(File.Exists).ToArray();
            var globalAsaxFiles = new List<string>();
            globalAsaxFiles.AddRange(paths);
            foreach (var asax in globalAsaxFiles) {
                var contents = File.ReadAllText(asax);
                if (contents.Contains(AsaxText)) {
                    var replaced = contents.Replace(AsaxText, string.Empty);
                    File.WriteAllText(asax, replaced);
                }
            }
        }

        public override void FeatureActivated(SPFeatureReceiverProperties properties) {
            // other stuff
            AddGlobalAsax(properties);
            // other stuff
        }

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties) {
            // other stuff
            RemoveGlobalAsax(properties);
            // other stuff
        }

Тот Самый ClaimSupport сборка содержит некоторый различный общий код:

namespace HynesITe.SharePoint.Authentication.Data {
    public static class ClaimSupport {
        public static string EventClaimType {
            get {
                return "http://schema.Company.com/events";
            }
        }

        public static string EventClaimValueType {
            get {
                return Microsoft.IdentityModel.Claims.ClaimValueTypes.String;
            }
        }

        public static string ApplicationEventKey(string username) {
            return username + ":CurrentEvent";
        }

        public static string ApplicationIssuerKey(string username) {
            return username + ":Issuer";
        }

        public static string ApplicationOriginalIssuerKey(string username) {
            return username + ":OriginalIssuer";
        }

        public static string HeartbeatClaimValue {
            get {
                return "[heartbeat]";
            }
        }
    }
}

Одна вещь, которая здесь действительно важна, заключается в том, что если вы манипулируете утверждениями, как я здесь, и ожидаете, что эти утверждения будут распознаны SharePoint, вам необходимо сопоставить информацию об эмитенте, которую будет использовать SharePoint, с SPClaim, вот почему утверждение "сердцебиение".В большинстве случаев вас это не волнует - программа увеличения утверждений сделает все правильно, - но здесь мне нужно иметь только одно из возможных утверждений, поэтому мне пришлось манипулировать непосредственно в этом коде, а не в коде увеличения утверждений.

Также я знаю, что вы можете импортировать пространства имен в global.asax но меня беспокоит другой код или пользовательские манипуляции с этим файлом, поэтому я сделал изменения как можно более независимыми.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с sharepoint.stackexchange
scroll top