最近一个项目要求进行分布式部署、保证在双十一期间系统的正常运行,虽然该系统平时访问量不是很大,但是基于业务需要,必须在至少两台服务器上部署。

该系统需要登录后才可以使用,首先需要解决分布式部署的用户状态共享问题,在项目中使用的是Forms身份验证,

如果是用Session,可以考虑使用微软的Azure Redis Cache(https://msdn.microsoft.com/library/azure/dn690522.aspx)将session存储到Redis中。

1、针对Forms配置项的改造,主要是在web.cofig中配置了machineKey,参考https://msdn.microsoft.com/zh-cn/library/eb0zx8fc.aspx

  1. <compilation debug="true" targetFramework="4.5" />
  2. <httpRuntime targetFramework="4.5" />
  3. <authentication mode="Forms">
  4. <forms name="Quote" loginUrl="~/Account/Login" protection="All" timeout="" defaultUrl="~/Home/index" path="/" />
  5. </authentication>
  6. <!--跨应用程序进行 Forms 身份验证 https://msdn.microsoft.com/zh-cn/library/eb0zx8fc.aspx-->
  7. <!--<machineKey validationKey="[your key here]" decryptionKey="[your key here]" validation="SHA1" />-->
  8. <machineKey validationKey="77A439696CB986680CEE71CB179BBFFA75AA0FE3AB875B278EE8C54536F2B364E1BDAB809BA26D4263C33863D29B4040CD55D9665E8002D26F04A80C701A4067" decryptionKey="79378FA6BD4BE839D0B8C1E94367A820C77F38FA9CD8C7F0" validation="SHA1"/>
  9. <authorization>
  10. <allow users="*" />
  11. </authorization>
  12. <anonymousIdentification enabled="true" cookieName=".DotNet" />

用户登录后 创建一个票据,放在cookie中,保存方法

  1. /// <summary>
  2. /// 创建一个票据,放在cookie中
  3. /// 票据中的数据经过加密,解决cookie的安全问题。
  4. /// </summary>
  5. /// <param name="userInfo">登录用户</param>
  6. /// <param name="issueDateTime">发布时间</param>
  7. /// <param name="experation">过期时间</param>
  8. /// <param name="isPersistent">持久性</param>
  9. public static void SetCookie(BaseUserInfo userInfo, DateTime? issueDateTime = null, DateTime? experation = null, bool isPersistent = true)
  10. {
  11. if (issueDateTime == null)
  12. {
  13. issueDateTime = DateTime.Now;
  14. }
  15. if (experation == null)
  16. {
  17. //设置COOKIE过期时间
  18. double userLoginExperation;
  19. if (double.TryParse(ConfigurationManager.AppSettings["UserLoginExperation"], out userLoginExperation))
  20. {
  21. experation = DateTime.Now.AddHours(userLoginExperation);
  22. }
  23. else
  24. {
  25. experation = DateTime.Now.AddHours();
  26. }
  27. }
  28. BaseSystemInfo.UserInfo = userInfo;
  29. BaseSystemInfo.UserInfo.ServicePassword = BaseSystemInfo.ServicePassword;
  30. BaseSystemInfo.UserInfo.ServiceUserName = BaseSystemInfo.ServiceUserName;
  31. BaseSystemInfo.UserInfo.SystemCode = BaseSystemInfo.SystemCode;
  32. JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer();
  33. string userData = javaScriptSerializer.Serialize(BaseSystemInfo.UserInfo);
  34. //生成验证票据,其中包括用户名、生效时间、过期时间、是否永久保存和用户数据等。
  35. FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(, userInfo.NickName, (DateTime)issueDateTime, (DateTime)experation, isPersistent, userData, FormsAuthentication.FormsCookiePath);
  36. HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
  37. cookie.Expires = (DateTime)experation;
  38. HttpResponse response = HttpContext.Current.Response;
  39. //指定客户端脚本是否可以访问[默认为false]
  40. cookie.HttpOnly = true;
  41. //指定统一的Path,比便能通存通取
  42. cookie.Path = "/";
  43. //设置跨域,这样在其它二级域名下就都可以访问到了 同一个网站下
  44. //cookie.Domain = "jinhuoyan.net";
  45. response.AppendCookie(cookie);
  46. }

此处提供一下MachineKey的生成方法:

  1. #region public ActionResult CreateMachineKey(int m,int n) 生成machineKey密钥
  2. /// <summary>
  3. /// 生成machineKey密钥
  4. /// </summary>
  5. /// <param name="m"></param>
  6. /// <param name="n"></param>
  7. /// <remarks>
  8. /// 参数:
  9. /// 第一个参数是用于创建 decryptionKey 属性的字节数。
  10. /// 第二个参数是用于创建 validationKey 属性的字节数。
  11. /// 注意:所创建的十六进制字符串的大小是从命令行传入值的大小的两倍。例如,如果您为密钥指定 24 字节,则转换后相应的字符串长度为 48 字节。
  12. /// decryptionKey 的有效值为 8 或 24。此属性将为数据加密标准 (DES) 创建一个 16 字节密钥,或者为三重 DES 创建一个 48 字节密钥。
  13. /// validationKey 的有效值为 20 到 64。此属性将创建长度从 40 到 128 字节的密钥。
  14. /// 代码的输出是一个完整的<machineKey>元素,您可以将其复制并粘贴到Machine.config文件中。
  15. /// </remarks>
  16. [CheckLogin]
  17. public ActionResult CreateMachineKey(int m, int n)
  18. {
  19. //String[] commandLineArgs = System.Environment.GetCommandLineArgs();
  20. //string decryptionKey = CreateKey(System.Convert.ToInt32(commandLineArgs[1]));
  21. //string validationKey = CreateKey(System.Convert.ToInt32(commandLineArgs[2]));
  22. string decryptionKey = CreateKey(m);
  23. string validationKey = CreateKey(n);
  24. string result = string.Format("<machineKey validationKey=\"{0}\" decryptionKey=\"{1}\" validation=\"SHA1\"/>", validationKey, decryptionKey);
  25. return Content(result);
  26. }
  27.  
  28. [NonAction]
  29. static String CreateKey(int numBytes)
  30. {
  31. RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
  32. byte[] buff = new byte[numBytes];
  33. rng.GetBytes(buff);
  34. return BytesToHexString(buff);
  35. }
  36. [NonAction]
  37. static String BytesToHexString(byte[] bytes)
  38. {
  39. StringBuilder hexString = new StringBuilder();
  40.  
  41. for (int counter = ; counter < bytes.Length; counter++)
  42. {
  43. hexString.Append(String.Format("{0:X2}", bytes[counter]));
  44. }
  45. return hexString.ToString();
  46. }
  47. #endregion

2、创建一个属性,用于在Action上标注对应的菜单的Code

  1. /// <summary>
  2. /// CustomerResourceAttribute
  3. /// 自定义的对方法应用的属性,在Action上标注权限菜单对应的Code
  4. ///
  5. /// 修改纪录
  6. ///
  7. /// 2015-10-11 版本:1.0 SongBiao 创建文件。
  8. ///
  9. /// <author>
  10. /// <name>SongBiao</name>
  11. /// <date>2015-10-11</date>
  12. /// </author>
  13. /// </summary>
  14.  
  15. [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
  16. public sealed class CustomerResourceAttribute : Attribute
  17. {
  18. private readonly string _resourceName;
  19.  
  20. public CustomerResourceAttribute(string resourceName)
  21. {
  22. _resourceName = resourceName;
  23. }
  24. /// <summary>
  25. /// 资源名称
  26. /// </summary>
  27. public string ResourceName
  28. {
  29. get { return _resourceName; }
  30. }
  31.  
  32. /// <summary>
  33. /// 资源描述
  34. /// </summary>
  35. public string Descript { get; set; }
  36. }

3、创建一个用户检测用户是否登录的标签

  1. /// <summary>
  2. /// CheckLoginAttribute
  3. /// 用于检测用户是否处于登录状态的标签
  4. /// 某些功能只需要用户登录就可以使用
  5. ///
  6. /// 修改纪录
  7. ///
  8. /// 2015-10-11 版本:1.0 SongBiao 创建文件。
  9. ///
  10. /// <author>
  11. /// <name>SongBiao</name>
  12. /// <date>2015-10-11</date>
  13. /// </author>
  14. /// </summary>
  15.  
  16. public class CheckLoginAttribute :System.Web.Mvc.AuthorizeAttribute //AuthorizeAttribute
  17. {
  18. protected override bool AuthorizeCore(HttpContextBase httpContext)
  19. {
  20. bool pass = false;
  21. if (!httpContext.Request.IsAuthenticated)
  22. {
  23. httpContext.Response.StatusCode = ;//无权限状态码
  24. pass = false;
  25. }
  26. else
  27. {
  28. pass = true;
  29. }
  30. return pass;
  31. }
  32.  
  33. protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
  34. {
  35. base.HandleUnauthorizedRequest(filterContext);
  36. if (filterContext.HttpContext.Response.StatusCode == )
  37. {
  38. filterContext.Result = new RedirectResult("/");
  39. }
  40. }
  41. }

4、创建一个身份验证过滤器,实现对匿名访问、登录后可访问、验证是否具有对应菜单(action)的处理

  1. /// <summary>
  2. /// 身份验证过滤器
  3. /// 1、匿名访问
  4. /// 2、登录就可以访问
  5. /// 3、需要验证是否有菜单或按钮或资源的权限
  6. ///
  7. ///
  8. /// 修改纪录
  9. ///
  10. /// 2015-10-11 版本:1.0 SongBiao 创建文件。
  11. ///
  12. /// <author>
  13. /// <name>SongBiao</name>
  14. /// <date>2015-10-11</date>
  15. /// </author>
  16. /// </summary>
  17.  
  18. public class PermissionCheckAttribute : IAuthorizationFilter
  19. {
  20. public void OnAuthorization(AuthorizationContext filterContext)
  21. {
  22. if (filterContext == null)
  23. {
  24. throw new ArgumentNullException("filterContext");
  25. }
  26. if (filterContext.HttpContext.Request.Url == null)
  27. {
  28. throw new ArgumentNullException("filterContext");
  29. }
  30. string pageUrl = filterContext.HttpContext.Request.Url.AbsolutePath; //OperateContext.GetThisPageUrl(false);
  31. //是否是Ajax请求
  32. var bAjax = filterContext.HttpContext.Request.IsAjaxRequest();
  33.  
  34. //1、允许匿名访问 用于标记在授权期间要跳过 AuthorizeAttribute 的控制器和操作的特性
  35. var actionAnonymous = filterContext.ActionDescriptor.GetCustomAttributes(typeof(AllowAnonymousAttribute), true) as IEnumerable<AllowAnonymousAttribute>;
  36. var controllerAnonymous = filterContext.Controller.GetType().GetCustomAttributes(typeof(AllowAnonymousAttribute), true) as IEnumerable<AllowAnonymousAttribute>;
  37. if ((actionAnonymous != null && actionAnonymous.Any()) || (controllerAnonymous != null && controllerAnonymous.Any()))
  38. {
  39. return;
  40. }
  41. //2、判断登录状态 Controller Action 标签 某些功能只需判断是否登录 用户没登录 调到登录页面
  42. var checkLoginControllerAttr =filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof (CheckLoginAttribute), true) as IEnumerable<CheckLoginAttribute>;
  43. if (checkLoginControllerAttr != null && checkLoginControllerAttr.Any())
  44. {
  45. return;
  46. }
  47. var checkLoginActionAttr = filterContext.ActionDescriptor.GetCustomAttributes(typeof(CheckLoginAttribute), true) as IEnumerable<CheckLoginAttribute>;
  48. if (checkLoginActionAttr != null && checkLoginActionAttr.Any())
  49. {
  50. return;
  51. }
  52. //3、有些要判断是否有某个菜单 action的权限 具体判断某个用户是否有某个权限
  53. //用于标记在授权期间需要CustomerResourceAttribute 的操作的特性
  54. var attNames = filterContext.ActionDescriptor.GetCustomAttributes(typeof(CustomerResourceAttribute), true) as IEnumerable<CustomerResourceAttribute>;
  55. //用户具有的菜单
  56. var moduleList = OperateContext.Current.ModuleList;
  57. if (moduleList == null || !moduleList.Any())
  58. {
  59. //没有获取到任何菜单 拒绝访问
  60. filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { Controller = "Message", action = "General", bAjaxReq = bAjax, message = "没有获取您拥有的权限菜单" }));
  61. }
  62. else
  63. {
  64. //判断用户的权限菜单中的code是否与控制器上标示的资源的code一致
  65. var joinResult = (from aclEntity in moduleList
  66. join attName in attNames on aclEntity.Code equals attName.ResourceName
  67. select attName).Any();
  68. if (joinResult)
  69. {
  70. return;
  71. }
  72. else
  73. {
  74. //没有对应的权限 拒绝访问
  75. filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { Controller = "Message", action = "DenyAccess", bAjaxReq = bAjax, message = "您没有访问权限:" + pageUrl }));
  76. }
  77. }
  78. }
  79. }

这里注意下OperateContext.Current.ModuleList,这个是取得用户具有的菜单,通用权限系统底层提供有对应的接口。

5、在FilterConfig中加入权限验证

  1. public class FilterConfig
  2. {
  3. public static void RegisterGlobalFilters(GlobalFilterCollection filters)
  4. {
  5. filters.Add(new HandleErrorAttribute());
  6. filters.Add(new PermissionCheckAttribute());
  7. }
  8. }

6、应用方法

对某个Action加入自定义验证权限的标签

不需要验证登录状态的

只要登录就可以访问的

至此,通用权限管理系统底层的权限控制、跨应用程序Forms身份验证集成完成了。如有疑问,欢迎提出。

Web分布式部署,跨应用程序Forms身份验证的集成方案的更多相关文章

  1. .net Forms身份验证不能用在应用的分布式部署中吗?

    参照网上的一些方法,使用Forms身份验证对应用进行分布式部署,发现没有成功. 应用部署的两台内网服务器:192.168.1.19,192.168.1.87,使用Nginx做负载分配,配置完全相同:每 ...

  2. ASP.NET Web API Basic Identity 中的基本身份验证

    缺点 用户凭证在请求中发送. 凭据作为明文发送. 每个请求都会发送凭据. 无法注销,除非结束浏览器会话. 易于跨站点请求伪造(CSRF); 需要反CSRF措施. 优点 互联网标准. 受所有主要浏览器支 ...

  3. ASP.NET Forms 身份验证

    ASP.NET Forms 身份验证 在开发过程中,我们需要做的事情包括: 1. 在 web.config 中设置 Forms 身份验证相关参数.2. 创建登录页. 登录页中的操作包括: 1. 验证用 ...

  4. asp.net Forms身份验证

    Web.config中的配置<system.web><authentication mode="Forms"> <forms name="K ...

  5. 采用Asp.Net的Forms身份验证时,持久Cookie的过期时间会自动扩展

    原文:http://www.cnblogs.com/sanshi/archive/2012/06/22/2558476.html 若是持久Cookie,Cookie的有效期Expiration属性有当 ...

  6. 通过Forms身份验证设置不同页面的访问权限

    使用Forms身份验证的时候,如果允许注册页面可以匿名用户访问,其他所有页面只允许注册用户访问,我们可以如下设置web.config文件来达到上述的效果: 1.在“system.web”节点下,添加登 ...

  7. asp.net Forms身份验证详解

    在做网站的时候,都会用到用户登录的功能.对于一些敏感的资源,我们只希望被授权的用户才能够访问,这让然需要用户的身份验证.对于初学者,通常将用户登录信息存放在Session中,笔者在刚接触到asp.ne ...

  8. Forms身份验证 知识总结

    最简单的Forms验证实现方法:FormsAuthentication.SetAuthCookie()方法,传递一个登录名即可FormsAuthentication.SignOut()方法退出Form ...

  9. 关于ASP.NET的“Forms身份验证”

    目录结构如图如示: 如果用户没有通过身份验证则跳转到登录页面让用户登录,在配置文件的<system.web></system.web>结点下添加如下代码: <!--身份验 ...

随机推荐

  1. POJ1275Cashier Employment(查分约束系统)

    链接1275Cashier Employment 题目大意就是说有一些人来应聘一个超级市场的工作,每个人的应聘的起始时间在0~23时之间,而超市在时间i需要R[i]个工作人员,而每个人的工作时间都是8 ...

  2. JDBC 与 ODBC 区别

    一. 二.

  3. chrome emulator use-agent 设置 chrom模拟手机客户端

    谷歌升级以后,发现找不到use-agent设置了 在Element 下点击ESC 出现console,再点击Emulation就出现了

  4. button 禁止

    1.按钮的id为btnzhuce==> 控制按钮为禁用:  $("#btnzhuce").attr({"disabled":"disabled& ...

  5. effective c++ (一)

    条款01:把C++看作一个语言联邦 C++是一种多重范型编程语言,一个同时支持过程(procedural),面向对象(object-oriented),函数形式(functional),泛型形式(ge ...

  6. C# 使用ping命令

    方法一:调用cmd 的ping命令 private static string CmdPing(string strIp) { Process p = new Process(); p.StartIn ...

  7. C#中的Collection 2

    Core Generic interface IEnumerable<T>:you can interate my elemnts, no need to know the count, ...

  8. OC:属性的内部实现原理、dealloc内释放实例变量、便利构造器方法的实现原理、collection的内存管理

    代码: // // main.m #import <Foundation/Foundation.h> #import "Person.h" #import " ...

  9. 关于新建JSP文件后,文件开头报错的处理

    新建了一个web工程,之后建立了jsp页面,刚建立完成,文件开头就报错:The superclass "javax.servlet.http.HttpServlet" was no ...

  10. HDU 4099 Revenge of Fibonacci(高精度+字典树)

    题意:对给定前缀(长度不超过40),找到一个最小的n,使得Fibonacci(n)前缀与给定前缀相同,如果在[0,99999]内找不到解,输出-1. 思路:用高精度加法计算斐波那契数列,因为给定前缀长 ...