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.
        }
    }


}

 

posted @ 2018-12-25 16:33  漂移青春  阅读(241)  评论(0编辑  收藏  举报