.NET单点登录实践

Posted DotNet

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了.NET单点登录实践相关的知识,希望对你有一定的参考价值。


来源:漫漫摸索

链接:cnblogs.com/minesnil-forfaith/p/6062943.html


前言


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


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


名称定义


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


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站主要逻辑


用户首先访问A站,A站中会生成Token,并存入Cache中。Token是A访问P的钥匙,P在回调给A的时候需要携带这个Token。A请求P,P验证Token,P回调A,A检测Token是否是发送出去的Token,验证之后Token即失效,防止Token被再次使用。


Token的生成是通过取时间戳的不同字段进行MD5加密生成,当然这里可以再加个盐进行防伪。


/// <summary>

/// 生成秘钥

/// </summary>

/// <param name="timestamp"></param>

/// <returns></returns>

public static string CreateToken(DateTime timestamp)

{

            StringBuilder securityKey = new StringBuilder(MD5Encypt(timestamp.ToString("yyyy")));

            securityKey.Append(MD5Encypt(timestamp.ToString("MM")));

            securityKey.Append(MD5Encypt(timestamp.ToString("dd")));

            securityKey.Append(MD5Encypt(timestamp.ToString("HH")));

            securityKey.Append(MD5Encypt(timestamp.ToString("mm")));

            securityKey.Append(MD5Encypt(timestamp.ToString("ss")));

            return MD5Encypt(securityKey.ToString());

}


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


   /// <summary>

    /// 授权枚举

    /// </summary>

    public enum AuthCodeEnum

    {

        Public = 1,

        Login = 2

    }

    /// <summary>

    /// 授权过滤器

    /// </summary>

    public class AuthAttribute : ActionFilterAttribute

    {

        /// <summary> 

        /// 权限代码 

        /// </summary> 

        public AuthCodeEnum Code { get; set; }

        /// <summary> 

        /// 验证权限

        /// </summary> 

        /// <param name="filterContext"></param> 

        public override void OnActionExecuting(ActionExecutingContext filterContext)

        {

            var request = filterContext.HttpContext.Request;

            var session = filterContext.HttpContext.Session;

            //如果存在身份信息 

            if (Common.CurrentUser == null)

            {

                if (Code == AuthCodeEnum.Public)

                {

                    return;

                }

                string reqToken = request["Token"];

                string ticket = request["Ticket"];

                Cache cache = HttpContext.Current.Cache;

                //没有获取到Token或者Token验证不通过或者没有取到从P回调的ticket 都进行再次请求P

                TokenModel tokenModel= cache.Get(ConstantHelper.TOKEN_KEY)==null?null:(TokenModel)cache.Get(ConstantHelper.TOKEN_KEY);

                if (string.IsNullOrEmpty(reqToken) || tokenModel == null || tokenModel.Token!= reqToken ||

                    string.IsNullOrEmpty(ticket))

                {

                    DateTime timestamp = DateTime.Now;

                    string returnUrl = request.Url.AbsoluteUri;

                    tokenModel = new TokenModel

                    {

                        TimeStamp = timestamp,

                        Token = AuthernUtil.CreateToken(timestamp)

                    };

                    //Token加入缓存中,设计过期时间为20分钟

                    cache.Add(ConstantHelper.TOKEN_KEY, tokenModel, null, DateTime.Now.AddMinutes(20),Cache.NoSlidingExpiration,CacheItemPriority.Default, null);

                    filterContext.Result = new ContentResult

       {

         Content = GetAuthernScript(AuthernUtil.GetAutherUrl(tokenModel.Token, timestamp), returnUrl)

       };

             return;

   }

                LoginService service = new LoginService();

                var userinfo = service.GetUserInfo(ticket);

                session[ConstantHelper.USER_SESSION_KEY] = userinfo;

                //验证通过,cache中去掉Token,保证每个token只能使用一次

                cache.Remove(ConstantHelper.TOKEN_KEY);

            }

        }

        /// <summary>

        /// 生成跳转脚本

        /// </summary>

        /// <returns></returns>

        private string GetAuthernScript(string authernUrl, string returnUrl)

        {

            StringBuilder sbScript = new StringBuilder();

            sbScript.Append("<script type='text/javascript'>");

            sbScript.AppendFormat("window.location.href='{0}&returnUrl=' + encodeURIComponent('{1}');", authernUrl, returnUrl);

            sbScript.Append("</script>");

            return sbScript.ToString();

        }

    }


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

调取过滤器


[Auth(Code = AuthCodeEnum.Login)]

public ActionResult Index()

{

    return View();

}


P站主要逻辑


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


/// <summary>

/// 授权登陆验证

/// </summary>

/// <returns></returns>

[HttpPost]

public ActionResult PassportVertify()

{

     var cookie=Request.Cookies[ConstantHelper.USER_COOKIE_KEY];

    if (cookie == null ||string.IsNullOrEmpty(cookie.ToString()))

{

return RedirectToAction("Login", new { ReturnUrl = Request["ReturnUrl"] ,Token= Request["Token"] });

}

string userinfo = cookie.ToString();

var success= passportservice.AuthernVertify(Request["Token"], Convert.ToDateTime(Request["TimeStamp"]));

if (!success)

{

return RedirectToAction("Login", new { ReturnUrl = Request["ReturnUrl"], Token = Request["Token"] });

}

return Redirect(passportservice.GetReturnUrl(userinfo, Request["Token"],Request["ReturnUrl"]));

}


已登陆则验证Token


/// <summary>

 /// 验证令牌

/// </summary>

 /// <param name="token">令牌</param>

/// <param name="timestamp">时间戳</param>

 /// <returns></returns>

public bool AuthernVertify(string token,DateTime timestamp)

{

    return AuthernUtil.CreateToken(timestamp) == token;

}


测试说明


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


关注「DotNet」 

看更多精选 .Net 技术文章

↓↓↓

以上是关于.NET单点登录实践的主要内容,如果未能解决你的问题,请参考以下文章

.NET 统一用户管理 -- 单点登录

.NET Core5.0 JWT鉴权SSO单点登录

Asp.Net 单点登录(SSO)|禁止重复登陆|登录强制下线

漏洞分析与实践之基于SAML实现的单点登录系统

Golang实践录:使用gin实现 cas 单点登录

WCF 服务 - ASP.net 托管 - 单点登录,如何传递凭据