表单身份验证超时被忽略
Forms Authentication Timeout Being Ignored
我正在使用 asp.net 表单身份验证来要求用户在访问特定页面时进行登录。我希望用户在 5 分钟不活动后必须再次登录,但无论我在 web.config 中的表单部分的超时值中输入什么,用户只会在会话状态过期后被踢出。
我尝试过的测试之一涉及此配置:
<system.web>
<sessionState timeout="1" />
<authentication mode="Forms">
<forms loginUrl="~/authentication" timeout="2" slidingExpiration="true"/>
</authentication>
</system.web>
如果我登录并保持空闲一分钟,如果我刷新页面,系统会要求我重新登录。但是,我的印象是我应该能够继续工作,直到表单身份验证超时到期。我知道在 1 分钟标记处,slidingExpiration 设置更新我的 cookie 为时已晚,但在 cookie 实际过期之前我应该还有一分钟。
如果我删除 Sessiontate 超时部分,我将不会在两分钟后被要求登录。在我被要求重新登录之前需要很长时间(大概 30 分钟)。这对我来说听起来像是只在 sessionState 过期时才要求我重新登录。
我是不是漏掉了什么?
下面是所涉及的控制器和方法的基本布局。首先,用户尝试转到 Recruiter 页面:
public class HireController : Controller
{
[Authorize]
public ActionResult Recruiter()
{
//do stuff after we've been authorized to access this page
}
}
因为用户需要被授权,所以他们被重定向到身份验证控制器中的登录页面:
public class AuthenticationController : BaseAuthenticationController
{
private readonly IAuthenticationService AuthenticationService;
public AuthenticationController(IAuthenticationService authService)
: base(authService)
{
AuthenticationService = authService;
}
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Index(string returnUrl)
{
var special= false;
return View("~/Views/Login/Login.cshtml", new LoginModel(special) { ReturnUrl = returnUrl });
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(LoginCredentials credentials, string returnUrl)
{
try
{
if (!ModelState.IsValid)
{
throw new ApplicationException(GeneralError);
}
base.DoLogin(credentials.Username, credentials.Password);
}
catch (Exception ex)
{
string message = (ex is ApplicationException) ? ex.Message : GeneralError;
ModelState.AddModelError("", message);
return View("~/Views/Login/Login.cshtml", new LoginModel { Username = credentials.Username, ReturnUrl = returnUrl });
}
return RedirectToLocal(returnUrl);
}
private ActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
if (User.Identity != null && User.Identity.IsAuthenticated)
{
return RedirectToAction("Recruiter", "Hire");
}
return RedirectToAction("Recruiter", "Hire");
}
}
这是 BaseAuthenticationController class:
public class BaseAuthenticationController : Controller
{
private readonly IAuthenticationService AuthenticationService;
protected const string GeneralError = "Login failure please try again";
public BaseAuthenticationController(IAuthenticationService authService)
{
AuthenticationService = authService;
}
public void DoLogin(string username, string password)
{
AuthenticationService.Login(username, password);
}
}
这是具体的 IAuthenticationService class:
public class WebAuthenticationService : IAuthenticationService
{
private const string InvalidError = "Invalid User Credentials Please try again";
private const string LockoutError = "You have been locked out of the Hiring Center. You will receive an email shortly once your password has been reset.";
readonly string uri = ConfigurationManager.AppSettings["HiringLoginApiBaseUrl"];
private readonly ISecurityContext SecurityContext;
public WebAuthenticationService(ISecurityContext securityContext)
{
SecurityContext = securityContext;
}
private LoginResult GetUserLogin(string username, string password)
{
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri(uri);
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("username", username),
new KeyValuePair<string, string>("password", password)
});
var postResult = httpClient.PostAsync("/api/Login/Post", content).Result;
var loginResult = postResult.Content.ReadAsAsync<LoginResult>().Result;
return loginResult;
}
}
public MembershipProvider AuthenticationProvider
{
get { return Membership.Provider; }
}
public void Login(string userName, string password)
{
var loginResult = this.GetUserLogin(userName, password);
if (!loginResult.IsValid)
{
throw new ApplicationException(InvalidError);
}
if (loginResult.IsLockedOut)
{
throw new ApplicationException(LockoutError);
}
// Maintain the location
IUser current = SecurityContext.Current;
SecurityContext.SetCurrent(User.CreateAuthorized(userName, current.Location, current.Preferences));
FormsAuthentication.SetAuthCookie(userName, false);
}
}
我不太清楚 WebAuthenticationService 中以下行的意义所在 class:
SecurityContext.SetCurrent(User.CreateAuthorized(userName, current.Location, current.Preferences));
SetCurrent()方法定义如下:
public class HttpSecurityContext : ISecurityContext
{
public static string SECURITY_CONTEXT_KEY = "SECURITY_CONTEXT";
public IUser Current
{
get
{
IUser user = HttpContext.Current.User as IUser;
if (user == null)
{
throw new ApplicationException("Context user is invalid;");
}
return user;
}
}
public void SetCurrent(IUser user)
{
HttpContext.Current.User = user;
}
}
Web.Config 会员提供商:
<membership>
<providers>
<clear />
<add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=asdfasf" connectionStringName="mydb" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" passwordFormat="Hashed" maxInvalidPasswordAttempts="3" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" passwordStrengthRegularExpression="" applicationName="/" />
</providers>
</membership>
我必须将以下内容添加到 web.config 文件以防止未经授权的用户访问该页面:
<authorization>
<deny users="?" />
</authorization>
显然这是非常标准的程序,令我惊讶的是我在搜索的早期没有运行进入它。 This msdn 文章实际上提到了添加该部分。
您只是为 FormsAuthentication.SetAuthCookie
方法使用了错误的参数值。根据文档 https://msdn.microsoft.com/pl-pl/library/twk5762b(v=vs.110).aspx 第二个参数设置持久性 cookie 如果设置为 true。否则 cookie 不会持久保存并在会话超时时丢失。因此,如果您想通过不同的会话(会话超时后)保留身份验证 cookie,请使用:
FormsAuthentication.SetAuthCookie(userName, true);
但是请记住,在会话超时后,用户将丢失他的所有会话变量,这可能会导致您的网络应用程序出错。
我找到了所有问题的原因。我在 global.asax 中发现了一堆代码,它们操纵用户的会话并基本上覆盖表单身份验证。此代码在对服务器的每个请求上运行,并保持用户登录,只要他们仍在会话中进行身份验证。这意味着即使表单身份验证 cookie 已过期(确实如此!),用户仍将保持登录状态。我猜以前的开发人员是从表单身份验证开始的,然后出于某种原因决定编写自己的东西。我们决定更改会话超时,以便用户在 5 分钟而不是默认的 20 分钟后注销。
下面是 global.asax 中的一些代码,这些代码让我差点秃顶:
protected void Application_PreRequestHandlerExecute()
{
HttpSessionState session = HttpContext.Current.Session;
if (session == null)
return;
IUser user = (session[HttpSecurityContext.SECURITY_CONTEXT_KEY] as IUser) ?? CreateUser();
securityContext.SetCurrent(user);
}
protected void Application_PostRequestHandlerExecute()
{
HttpSessionState session = HttpContext.Current.Session;
if (session == null) return;
session[HttpSecurityContext.SECURITY_CONTEXT_KEY] = securityContext.Current;
}
private IUser CreateUser()
{
IUserLocation location = LocateUser();
IUser user = Common.Security.User.CreateAnonymous(location);
SetupUserPreferences(user);
return user;
}
这就是我们在 web.config 中针对会话超时所做的更改:
<system.web>
<sessionState timeout="5"/>
</system.web>
我正在使用 asp.net 表单身份验证来要求用户在访问特定页面时进行登录。我希望用户在 5 分钟不活动后必须再次登录,但无论我在 web.config 中的表单部分的超时值中输入什么,用户只会在会话状态过期后被踢出。
我尝试过的测试之一涉及此配置:
<system.web>
<sessionState timeout="1" />
<authentication mode="Forms">
<forms loginUrl="~/authentication" timeout="2" slidingExpiration="true"/>
</authentication>
</system.web>
如果我登录并保持空闲一分钟,如果我刷新页面,系统会要求我重新登录。但是,我的印象是我应该能够继续工作,直到表单身份验证超时到期。我知道在 1 分钟标记处,slidingExpiration 设置更新我的 cookie 为时已晚,但在 cookie 实际过期之前我应该还有一分钟。
如果我删除 Sessiontate 超时部分,我将不会在两分钟后被要求登录。在我被要求重新登录之前需要很长时间(大概 30 分钟)。这对我来说听起来像是只在 sessionState 过期时才要求我重新登录。
我是不是漏掉了什么?
下面是所涉及的控制器和方法的基本布局。首先,用户尝试转到 Recruiter 页面:
public class HireController : Controller
{
[Authorize]
public ActionResult Recruiter()
{
//do stuff after we've been authorized to access this page
}
}
因为用户需要被授权,所以他们被重定向到身份验证控制器中的登录页面:
public class AuthenticationController : BaseAuthenticationController
{
private readonly IAuthenticationService AuthenticationService;
public AuthenticationController(IAuthenticationService authService)
: base(authService)
{
AuthenticationService = authService;
}
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Index(string returnUrl)
{
var special= false;
return View("~/Views/Login/Login.cshtml", new LoginModel(special) { ReturnUrl = returnUrl });
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(LoginCredentials credentials, string returnUrl)
{
try
{
if (!ModelState.IsValid)
{
throw new ApplicationException(GeneralError);
}
base.DoLogin(credentials.Username, credentials.Password);
}
catch (Exception ex)
{
string message = (ex is ApplicationException) ? ex.Message : GeneralError;
ModelState.AddModelError("", message);
return View("~/Views/Login/Login.cshtml", new LoginModel { Username = credentials.Username, ReturnUrl = returnUrl });
}
return RedirectToLocal(returnUrl);
}
private ActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
if (User.Identity != null && User.Identity.IsAuthenticated)
{
return RedirectToAction("Recruiter", "Hire");
}
return RedirectToAction("Recruiter", "Hire");
}
}
这是 BaseAuthenticationController class:
public class BaseAuthenticationController : Controller
{
private readonly IAuthenticationService AuthenticationService;
protected const string GeneralError = "Login failure please try again";
public BaseAuthenticationController(IAuthenticationService authService)
{
AuthenticationService = authService;
}
public void DoLogin(string username, string password)
{
AuthenticationService.Login(username, password);
}
}
这是具体的 IAuthenticationService class:
public class WebAuthenticationService : IAuthenticationService
{
private const string InvalidError = "Invalid User Credentials Please try again";
private const string LockoutError = "You have been locked out of the Hiring Center. You will receive an email shortly once your password has been reset.";
readonly string uri = ConfigurationManager.AppSettings["HiringLoginApiBaseUrl"];
private readonly ISecurityContext SecurityContext;
public WebAuthenticationService(ISecurityContext securityContext)
{
SecurityContext = securityContext;
}
private LoginResult GetUserLogin(string username, string password)
{
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri(uri);
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("username", username),
new KeyValuePair<string, string>("password", password)
});
var postResult = httpClient.PostAsync("/api/Login/Post", content).Result;
var loginResult = postResult.Content.ReadAsAsync<LoginResult>().Result;
return loginResult;
}
}
public MembershipProvider AuthenticationProvider
{
get { return Membership.Provider; }
}
public void Login(string userName, string password)
{
var loginResult = this.GetUserLogin(userName, password);
if (!loginResult.IsValid)
{
throw new ApplicationException(InvalidError);
}
if (loginResult.IsLockedOut)
{
throw new ApplicationException(LockoutError);
}
// Maintain the location
IUser current = SecurityContext.Current;
SecurityContext.SetCurrent(User.CreateAuthorized(userName, current.Location, current.Preferences));
FormsAuthentication.SetAuthCookie(userName, false);
}
}
我不太清楚 WebAuthenticationService 中以下行的意义所在 class:
SecurityContext.SetCurrent(User.CreateAuthorized(userName, current.Location, current.Preferences));
SetCurrent()方法定义如下:
public class HttpSecurityContext : ISecurityContext
{
public static string SECURITY_CONTEXT_KEY = "SECURITY_CONTEXT";
public IUser Current
{
get
{
IUser user = HttpContext.Current.User as IUser;
if (user == null)
{
throw new ApplicationException("Context user is invalid;");
}
return user;
}
}
public void SetCurrent(IUser user)
{
HttpContext.Current.User = user;
}
}
Web.Config 会员提供商:
<membership>
<providers>
<clear />
<add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=asdfasf" connectionStringName="mydb" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" passwordFormat="Hashed" maxInvalidPasswordAttempts="3" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" passwordStrengthRegularExpression="" applicationName="/" />
</providers>
</membership>
我必须将以下内容添加到 web.config 文件以防止未经授权的用户访问该页面:
<authorization>
<deny users="?" />
</authorization>
显然这是非常标准的程序,令我惊讶的是我在搜索的早期没有运行进入它。 This msdn 文章实际上提到了添加该部分。
您只是为 FormsAuthentication.SetAuthCookie
方法使用了错误的参数值。根据文档 https://msdn.microsoft.com/pl-pl/library/twk5762b(v=vs.110).aspx 第二个参数设置持久性 cookie 如果设置为 true。否则 cookie 不会持久保存并在会话超时时丢失。因此,如果您想通过不同的会话(会话超时后)保留身份验证 cookie,请使用:
FormsAuthentication.SetAuthCookie(userName, true);
但是请记住,在会话超时后,用户将丢失他的所有会话变量,这可能会导致您的网络应用程序出错。
我找到了所有问题的原因。我在 global.asax 中发现了一堆代码,它们操纵用户的会话并基本上覆盖表单身份验证。此代码在对服务器的每个请求上运行,并保持用户登录,只要他们仍在会话中进行身份验证。这意味着即使表单身份验证 cookie 已过期(确实如此!),用户仍将保持登录状态。我猜以前的开发人员是从表单身份验证开始的,然后出于某种原因决定编写自己的东西。我们决定更改会话超时,以便用户在 5 分钟而不是默认的 20 分钟后注销。
下面是 global.asax 中的一些代码,这些代码让我差点秃顶:
protected void Application_PreRequestHandlerExecute()
{
HttpSessionState session = HttpContext.Current.Session;
if (session == null)
return;
IUser user = (session[HttpSecurityContext.SECURITY_CONTEXT_KEY] as IUser) ?? CreateUser();
securityContext.SetCurrent(user);
}
protected void Application_PostRequestHandlerExecute()
{
HttpSessionState session = HttpContext.Current.Session;
if (session == null) return;
session[HttpSecurityContext.SECURITY_CONTEXT_KEY] = securityContext.Current;
}
private IUser CreateUser()
{
IUserLocation location = LocateUser();
IUser user = Common.Security.User.CreateAnonymous(location);
SetupUserPreferences(user);
return user;
}
这就是我们在 web.config 中针对会话超时所做的更改:
<system.web>
<sessionState timeout="5"/>
</system.web>