MVC下的Web API限制同一账号同时登录多台设备
原理:设备A成功登录后,会以当前会话的SessionID作为键,用户id作为值,保存在application(保存在服务器的全局变量,多用户可以共享)变量中。设备B用同账号登录后会判断application里是否已存在用户id作为值的数据,存在则将该值设为“_offline_”。设备A再在请求交互操作后,后端每次会判断SessionID作为键的值是否为“_offline_”,是则弹出该账号在异地登录,跳回登录界面。
后端处理登录的代码(登录在Web API里写的):
private string loginin(string content)
{
string returnStr = "";
JLogin login = JsonConvert.DeserializeObject<JLogin>(content);
JHUserBL userBL = new JHUserBL();
JGetUser user = userBL.IsLogin(login.login_name, login.password);
if (user != null)
{
#region 存储登录的SessionID
HttpContext httpContext = System.Web.HttpContext.Current;
var userOnline = (Hashtable)httpContext.Application["Online"];
if (userOnline != null)
{
IDictionaryEnumerator enumerator = userOnline.GetEnumerator();
while (enumerator.MoveNext())
{
if (enumerator.Value != null && enumerator.Value.ToString().Equals(user.id.ToString()))
{
userOnline[enumerator.Key.ToString()] = "_offline_";
break;
}
}
}
else
{
userOnline = new Hashtable();
}
HttpContext.Current.Session.Timeout = 60 * 24;//1天
userOnline[HttpContext.Current.Session.SessionID] = user.id.ToString();
httpContext.Application.Lock();
httpContext.Application["Online"] = userOnline;
httpContext.Application.UnLock();
#endregion
returnStr = JsonHelper.RequestSuccess(JsonConvert.SerializeObject(user));
new JHLogBL().Insert(LogType.login, "用户登录", user.id);
}
else
{
returnStr = JsonHelper.RequstFail();
}
return returnStr;
}
后端处理异常登录的代码(LoginCheckFilterAttribute.cs下):
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace GDSMPlateForm
{
public class LoginCheckFilterAttribute:ActionFilterAttribute
{
//表示是否检查登录
public bool IsCheck { get; set; }
//Action方法执行之前执行此方法
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
if (IsCheck)
{
#region 同一账号登录验证
HttpContext httpContext = System.Web.HttpContext.Current;
Hashtable userOnline = (Hashtable)httpContext.Application["Online"];
if (userOnline != null)
{
if (userOnline.ContainsKey(httpContext.Session.SessionID))
{
var value = userOnline[httpContext.Session.SessionID];
//判断当前session保存的值是否为被注销值
if (value != null && "_offline_".Equals(value))
{
//验证被注销则清空session
userOnline.Remove(httpContext.Session.SessionID);
httpContext.Application.Lock();
httpContext.Application["online"] = userOnline;
httpContext.Application.UnLock();
HttpCookie cookie = new HttpCookie("failLogin")
{
Value = "failLogin",
Expires = DateTime.Now.AddDays(1)
};
HttpContext.Current.Response.Cookies.Add(cookie);
HttpContext.Current.Request.Cookies.Remove("user_id");
//string msg = "下线通知:当前账号另一地点登录, 您被迫下线。若非本人操作,您的登录密码很可能已经泄露,请及时改密。";
filterContext.HttpContext.Response.Redirect("/");
}
}
}
#endregion
string req_method = filterContext.HttpContext.Request.HttpMethod;
if (req_method.ToLower() != "get" && req_method.ToLower() != "post" && req_method.ToLower() != "head")
{
GDSMCommon.LogHelper.WriteLog("平台请求方式有错:" + req_method);
filterContext.HttpContext.Response.Redirect("/error.html");
}
if (HttpContext.Current.Request.UrlReferrer != null)
{
if (HttpContext.Current.Request.Url.Host != HttpContext.Current.Request.UrlReferrer.Host)
{
GDSMCommon.LogHelper.WriteLog("跨站请求伪造:当前Host:" + HttpContext.Current.Request.Url.Host + ",上一个Host:" + HttpContext.Current.Request.UrlReferrer.Host);
filterContext.HttpContext.Response.Redirect("~/error.html");
}
}
string full_url = HttpContext.Current.Request.Url.ToString().ToLower();
if (full_url.Contains("content-type") || full_url.Contains("content-transfer-encoding") || full_url.Contains(";") || full_url.Contains("=_"))
{
GDSMCommon.LogHelper.WriteLog("参数请求错误:" + HttpContext.Current.Request.Url.ToString());
filterContext.HttpContext.Response.Redirect("~/error.html");
}
//else
//{
// GD.Common.LogHelper.WriteLog("跨站请求伪造:当前Host:" + HttpContext.Current.Request.Url.Host + ",上一个Host:不存在" );
// filterContext.HttpContext.Response.Redirect("~/error.html");
//}
string url = HttpContext.Current.Request.Url.AbsolutePath.ToString().ToLower();
if (url != "/" && url != "/home/index" && url != "/home/" && url != "/home" && url.Contains("afileupload") == false && url.Contains("classiccase") == false && url.Contains("h5template") == false && url.Contains("registeragree") == false && url.Contains("bannerupload") == false)
{
校验用户是否已经登录
//if (filterContext.HttpContext.Session["UserInfo"] == null)
//{
// //跳转到登陆页
// filterContext.HttpContext.Response.Redirect("/?tourl=" + HttpContext.Current.Server.UrlEncode(HttpContext.Current.Request.Url.ToString()));
//}
if (HttpContext.Current.Request.Cookies["user_id"] == null)
{
filterContext.HttpContext.Response.Redirect("/?tourl=" + HttpContext.Current.Server.UrlEncode(HttpContext.Current.Request.Url.ToString()));
}
else
{
if (HttpContext.Current.Request.Cookies["user_id"].Value == "")
{
HttpCookie cookie = HttpContext.Current.Request.Cookies["user_id"];
cookie.Expires = DateTime.Now.AddDays(-1);
HttpContext.Current.Request.Cookies.Add(cookie);
filterContext.HttpContext.Response.Redirect("/?tourl=" + HttpContext.Current.Server.UrlEncode(HttpContext.Current.Request.Url.ToString()));
}
}
}
}
}
}
}
然后在首页处理重复登录的状态:
$(function () {
var failLogin = getCookie("failLogin");
var user_id = getCookie("user_id");
if (failLogin != "" && user_id != "") {
var msg = "下线通知:当前账号另一地点登录, 您被迫下线。若非本人操作,您的登录密码很可能已经泄露,请及时改密。";
layer.open({
content: msg,
end: function () {
document.cookie = "failLogin=";
delCookie("user_id");
}
});
}
});
在MVC的WebApi中默认是没有开启Session会话支持的。需要在Global中重写Init方法来指定会话需要支持的类型
public class WebApiApplication : System.Web.HttpApplication
{
public override void Init()
{
this.PostAuthenticateRequest += (sender, e) => HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
base.Init();
}
}
在WebApiConfig中建立建立HttpControllerHandler和HttpControllerRouteHandler 并覆写它,同时Routes.MapHttpRoute要修改为下面的格式
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API 配置和服务
// Web API 路由
//config.MapHttpAttributeRoutes();
//config.Routes.MapHttpRoute(
// name: "DefaultApi",
// routeTemplate: "api/{controller}/{id}",
// defaults: new { id = RouteParameter.Optional }
//);
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
).RouteHandler = new SessionControllerRouteHandler();
}
public class SessionRouteHandler : HttpControllerHandler, IRequiresSessionState
{
public SessionRouteHandler(RouteData routeData)
: base(routeData)
{
}
}
public class SessionControllerRouteHandler : HttpControllerRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new SessionRouteHandler(requestContext.RouteData);
}
}
}
SessionID每次请求一直会变的情况,Global页面下需要添加Session_Start方法
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using System.Web.SessionState;
namespace GDSMPlateForm
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
void Session_Start(object sender, EventArgs e)
{
// Code that runs when a new session is started
}
void Session_End(object sender, EventArgs e)
{
// Code that runs when a session ends.
// Note: The Session_End event is raised only when the sessionstate mode
// is set to InProc in the Web.config file. If session mode is set to StateServer
// or SQLServer, the event is not raised.
}
}
public class WebApiApplication : System.Web.HttpApplication
{
public override void Init()
{
this.PostAuthenticateRequest += (sender, e) => HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
base.Init();
}
void Session_Start(object sender, EventArgs e)
{
// Code that runs when a new session is started
}
void Session_End(object sender, EventArgs e)
{
// Code that runs when a session ends.
// Note: The Session_End event is raised only when the sessionstate mode
// is set to InProc in the Web.config file. If session mode is set to StateServer
// or SQLServer, the event is not raised.
}
}
}