• .net 单点登录实践


    前言

      最近轮到我在小组晨会来分享知识点,突然想到单点登录,准备来分享下如何实现单点登录,所以有了下文。实现方案以及代码可能写得不是很严谨,有漏洞的地方或者错误的地方欢迎大家指正。  

      刚开始头脑中没有思路,直接在博客园里面看看别人是如何来实现的,看了几篇文章发现,发现解决方案有点问题,或者说不算实现了单点登录

    名称定义

      为了方便说明先说明几个文中出现的名词的含义:

      P站:统一登录授权验证中心,demo中 域名是www.passport.com:801

      A站:处于不同域名下的测试网站,demo中 域名是www.a.com:802

      B站:处于不同域名下的测试网站,demo中 域名是www.b.com:803

      Token:用户访问P站的秘钥
      Ticket:用来保存用户信息的加密字符串

    单点登录

      访问A站需要登陆的就跳转P站中进行登陆,P站登陆之后跳转回至A站,用户再次访问B站需要登陆的页面,用户不需要进行登陆操作就可以正常访问。

    实现思路

      未登录用户访问A站,首先会重定向跳转至P站授权中心,P站首先通过检测Cookie来判断当前不是处于登陆状态,就跳转至登陆页面进行登陆操作,登陆成功之后把用户信息加密ticket附在A的请求地址上返回,A站通过解密ticket来获取用户信息,解密成功并存进Session中(这样用户在A中就处于登陆状态了),访问通过;当用户再次访问B站的时候,对于B站来说,用户是处于未登录状态,则同样会重定向跳转至P站授权中心,P站检测Cookie,判断当前用户处于登陆状态,就把当前用户信息加密成ticket附在B的请求地址上返回,后面的操作就和A站处理一样;这样都登陆之后再次访问A或者B,A和B中Session中都存储了用户信息,就不会再次请求P站了。

    简单关系图

    泳道流程图

    主要逻辑说明

    A站主要逻辑

      用户首先访问A站,A站中会生成Token,并存入Cache中。Token是A访问P的钥匙,P在回调给A的时候需要携带这个Token。A请求P,P验证Token,P回调A,A检测Token是否是发送出去的Token,验证之后Token即失效,防止Token被再次使用。
      Token的生成是通过取时间戳的不同字段进行MD5加密生成,当然这里可以再加个盐进行防伪。
     1         /// <summary>
     2         /// 生成秘钥
     3         /// </summary>
     4         /// <param name="timestamp"></param>
     5         /// <returns></returns>
     6         public static string CreateToken(DateTime timestamp)
     7         {
     8             StringBuilder securityKey = new StringBuilder(MD5Encypt(timestamp.ToString("yyyy")));
     9             securityKey.Append(MD5Encypt(timestamp.ToString("MM")));
    10             securityKey.Append(MD5Encypt(timestamp.ToString("dd")));
    11             securityKey.Append(MD5Encypt(timestamp.ToString("HH")));
    12             securityKey.Append(MD5Encypt(timestamp.ToString("mm")));
    13             securityKey.Append(MD5Encypt(timestamp.ToString("ss")));
    14             return MD5Encypt(securityKey.ToString());
    15         }

    P回调A的时候进行,A中对Token进行校验,校验不成功则请求P站统一授权验证。

     1     /// <summary>
     2     /// 授权枚举
     3     /// </summary>
     4     public enum AuthCodeEnum
     5     {
     6         Public = 1,
     7         Login = 2
     8     }
     9 
    10     /// <summary>
    11     /// 授权过滤器
    12     /// </summary>
    13     public class AuthAttribute : ActionFilterAttribute
    14     {
    15         /// <summary> 
    16         /// 权限代码 
    17         /// </summary> 
    18         public AuthCodeEnum Code { get; set; }
    19 
    20         /// <summary> 
    21         /// 验证权限
    22         /// </summary> 
    23         /// <param name="filterContext"></param> 
    24         public override void OnActionExecuting(ActionExecutingContext filterContext)
    25         {
    26             var request = filterContext.HttpContext.Request;
    27             var session = filterContext.HttpContext.Session;
    28             //如果存在身份信息 
    29             if (Common.CurrentUser == null)
    30             {
    31                 if (Code == AuthCodeEnum.Public)
    32                 {
    33                     return;
    34                 }
    35                 string reqToken = request["Token"];
    36                 string ticket = request["Ticket"];
    37                 Cache cache = HttpContext.Current.Cache;
    38                 //没有获取到Token或者Token验证不通过或者没有取到从P回调的ticket 都进行再次请求P
    39                 TokenModel tokenModel= cache.Get(ConstantHelper.TOKEN_KEY)==null?null:(TokenModel)cache.Get(ConstantHelper.TOKEN_KEY);
    40                 if (string.IsNullOrEmpty(reqToken) || tokenModel == null || tokenModel.Token!= reqToken ||
    41                     string.IsNullOrEmpty(ticket))
    42                 {
    43                     DateTime timestamp = DateTime.Now;
    44                     string returnUrl = request.Url.AbsoluteUri;
    45                     tokenModel = new TokenModel
    46                     {
    47                         TimeStamp = timestamp,
    48                         Token = AuthernUtil.CreateToken(timestamp)
    49                     };
    50                     //Token加入缓存中,设计过期时间为20分钟
    51                     cache.Add(ConstantHelper.TOKEN_KEY, tokenModel, null, DateTime.Now.AddMinutes(20),Cache.NoSlidingExpiration,CacheItemPriority.Default, null);
    52                     filterContext.Result = new ContentResult
    53                     {
    54                        Content = GetAuthernScript(AuthernUtil.GetAutherUrl(tokenModel.Token, timestamp), returnUrl)
    55                     };
    56                     return;
    57                 }
    58                 LoginService service = new LoginService();
    59                 var userinfo = service.GetUserInfo(ticket);
    60                 session[ConstantHelper.USER_SESSION_KEY] = userinfo;
    61                 //验证通过,cache中去掉Token,保证每个token只能使用一次
    62                 cache.Remove(ConstantHelper.TOKEN_KEY);
    63             }
    64         }
    65 
    66         /// <summary>
    67         /// 生成跳转脚本
    68         /// </summary>
    69         /// <param name="authernUrl">统一授权地址</param>
    70         /// <param name="returnUrl">回调地址</param>
    71         /// <returns></returns>
    72         private string GetAuthernScript(string authernUrl, string returnUrl)
    73         {
    74             StringBuilder sbScript = new StringBuilder();
    75             sbScript.Append("<script type='text/javascript'>");
    76             sbScript.AppendFormat("window.location.href='{0}&returnUrl=' + encodeURIComponent('{1}');", authernUrl, returnUrl);
    77             sbScript.Append("</script>");
    78             return sbScript.ToString();
    79         }
    80     }

      代码说明:这里为了方便设置Token的过期时间,所以使用Cache来存取Token,设定Token的失效时间为两分钟,当验证成功则从cache中移除Token。

    调取过滤器

    1         [Auth(Code = AuthCodeEnum.Login)]
    2         public ActionResult Index()
    3         {
    4             return View();
    5         }

     P站主要逻辑

     P站收到授权请求,P站首先通过Coookie来判断是否登陆,未登录则跳转至登陆页面进行登陆操作。

     1         /// <summary>
     2         /// 授权登陆验证
     3         /// </summary>
     4         /// <returns></returns>
     5         [HttpPost]
     6         public ActionResult PassportVertify()
     7         {
     8             var cookie=Request.Cookies[ConstantHelper.USER_COOKIE_KEY];
     9             if (cookie == null ||string.IsNullOrEmpty(cookie.ToString()))
    10             {
    11                 return RedirectToAction("Login", new { ReturnUrl = Request["ReturnUrl"] ,Token= Request["Token"] });
    12             }
    13             string userinfo = cookie.ToString();
    14             var success= passportservice.AuthernVertify(Request["Token"], Convert.ToDateTime(Request["TimeStamp"]));
    15             if (!success)
    16             {
    17                 return RedirectToAction("Login", new { ReturnUrl = Request["ReturnUrl"], Token = Request["Token"] });
    18             }
    19             return Redirect(passportservice.GetReturnUrl(userinfo, Request["Token"],Request["ReturnUrl"]));
    20         }

    已登陆则验证Token

     1        /// <summary>
     2        /// 验证令牌
     3        /// </summary>
     4        /// <param name="token">令牌</param>
     5        /// <param name="timestamp">时间戳</param>
     6        /// <returns></returns>
     7         public bool AuthernVertify(string token,DateTime timestamp)
     8         {
     9             return AuthernUtil.CreateToken(timestamp) == token;
    10         }

     测试说明

    1、修改host

    127.0.0.1 www.passport.com
    127.0.0.1 www.a.com
    127.0.0.1 www.b.com

    2、部署IIS

    P www.passport.com:801

    A www.a.com:802

    B www.b.com:803

    3、测试账号和webconfig

    <add key="PassportCenterUrl" value="http://www.passport.com:801"/>

    用户名:admin  密码:123

    demo

     https://github.com/hexuefengx/study

    最后感谢大家的阅读,这里推荐一个公众号 猿大侠的客栈,会不定时的推送分享IT相关技术文章。
  • 相关阅读:
    CF446DDZY Loves Games【高斯消元,矩阵乘法】
    PHP操作MongoDB数据库
    PHP linux spl_autoload_register区分大小写
    win7 64位安装redis 及Redis Desktop Manager使用
    svn的搭建
    php 扩展 redis
    CI reids 缓存
    拿起键盘写下我的第一封博客
    自我介绍
    课程目标
  • 原文地址:https://www.cnblogs.com/minesnil-forfaith/p/6062943.html
Copyright © 2020-2023  润新知