Срок действия токена STS для внешних заявок (e.g.Azure ACS)
-
10-12-2019 - |
Вопрос
Я модифицирую существующую систему, которая использует 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, с приведенным общим ответом об изменении срока службы токена.У меня есть несколько проблем и вопросов по этому поводу.
Какой срок службы применяется в сценарии с внешними претензиями?Это не токен Windows, и это не токен FBA, так что ни
WindowsTokenLifetime
ниFormsTokenLifetime
кажутся уместными, хотя именно эти два варианта все советуют изменить.Я не хочу, чтобы срок действия токена истекал для конечного пользователя за неоправданно короткое время, я просто хочу, чтобы у STS истек срок действия кэшированной версии или в идеале никогда не кэшировать ее вообще (я знаю, что там проблема с производительностью - мне все равно, для нашего масштаба это не будет иметь значения).Однако кажется, что эти две вещи взаимосвязаны.Есть ли способ разделить их?
Если я не могу отделить, есть ли способ, чтобы экран входа в систему указывал системе, что я хочу, чтобы заявка была повторно дополнена?Могу ли я просто сделать это сам, изменив текущий идентификатор пользователя?Конечно, это происходит за спиной системы, поэтому я не хочу этого делать, если в моих силах.
Все это, похоже, в конечном счете сводится к кэшированию в OOB STS, поскольку все остальные движущиеся части кажутся правильными.Является ли "правильным" способом избежать этого создание пользовательского STS, у которого нет поведения кэширования, и регистрация его в качестве поставщика удостоверений?(Если бы у меня был пользовательский STS, похоже, что на этом этапе я бы не стал беспокоиться об увеличении претензий, я бы просто выдал все надлежащие претензии в первую очередь.) Однако это кажется значительным объемом работы (хотя я знаю, что есть образцы, включающие http://archive.msdn.microsoft.com/SelfSTS), тем более что, похоже, мне придется подключить подключение проверяющей стороны к Azure ACS как часть процесса, где OOB STS делает это "волшебным образом" с помощью нескольких строк PowerShell и некоторых усилий.
Или мне нужно отказаться от претензий и просто сменить сторону 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
но меня беспокоит другой код или пользовательские манипуляции с этим файлом, поэтому я сделал изменения как можно более независимыми.