前言

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

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

名称定义

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

  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加密生成,当然这里可以再加个盐进行防伪。
         /// <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 = ,
Login =
} /// <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(),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>
/// <param name="authernUrl">统一授权地址</param>
/// <param name="returnUrl">回调地址</param>
/// <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

最后感谢大家的阅读,这里推荐一个公众号 猿大侠的客栈,会不定时的推送分享IT相关技术文章。

.net 单点登录实践的更多相关文章

  1. CAS单点登录实践(spring cas client配置)

    前言: 最近的项目需要将多个站点统一登录,查阅了资料Jasig cas(Central Authentication Service)(官方站点:http://www.jasig.org/cas)使用 ...

  2. Windows Azure 安全最佳实践 - 第 5 部分:基于Claim 的标识,单点登录

    基于Claim的身份标识是处理网站与 Web 服务的身份认证和访问一种简单而强大的方式,无论您是在本地工作还是面向云工作.您可以通过减少自定义实施和使用基于Claim的单一简化标识模型,创建更安全的应 ...

  3. 移动App如何实践单点登录

    移动应用实现单点登录,分析下面两种需求: 需求一: 账号在正在设备A上使用(应用是设备A上当前活动应用),  若设备B上用该账号登录应用,要求设备A上的账号立即退出. 解决办法: 应用客户端上做个定时 ...

  4. SSO单点登录系列3:cas-server端配置认证方式实践(数据源+自定义java类认证)

    落雨 cas 单点登录 本篇将讲解cas-server端的认证方式 1.最简单的认证,用户名和密码一致就登录成功 2.配置Oracle的jdbc数据源,通过spring动态查询数据库 3.配置orac ...

  5. 著名ERP厂商的SSO单点登录解决方案介绍一

          SSO英文全称Single Sign On,单点登录.SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统.它包括可以将这次主要的登录映射到其他应用中用于同一个用户 ...

  6. [原创]django+ldap+memcache实现单点登录+统一认证

    前言 由于公司内部的系统越来越多,为了方便用户使用,通过django进行了单点登录和统一认证的尝试,目前实现了django项目的单点登录和非django项目的统一认证,中间波折挺多,涉及的技术包括dj ...

  7. 使用Crowd2.7集成Confluence5.3与JIRA6.1,并安装、破解及汉化,实现单点登录【原创】

      鉴于目前没有针对Crowd.Confluence.Jira安装.集成和破解最新的方法,总结今天安装.破解及集成的经验,编写此文,方便大家进行配置也方便自己以后参考.此文参考多篇破解文章,并经过作者 ...

  8. java web SSO单点登录

    第一篇: Web应用系统的演化总是从简单到复杂,从单功能到多功能模块再到多子系统方向发展. .当前的大中型Web互联网应用基本都是多系统组成的应用群,由多个web系统协同为用户提供服务. 多系统应用群 ...

  9. SSO 基于Cookie+fliter实现单点登录 实例解析(一)

    接上文,SSO的理论讲解,接下来实践实践! 1.使用Cookie解决单点登录 技术点: 1.设置Cookie的路径为setPath("/").即Tomcat的目录下都有效 2.设置 ...

随机推荐

  1. 关于Java语言中那些修饰符

    一.在java中提供的一些修饰符,这些修饰符可以修饰类.变量和方法,在java中常见的修饰符有:abstract(抽象的).static(静态的).public(公共的).protected(受保护的 ...

  2. 为Guid数据类型的属性(property)赋值

    先来看看数据库表中的字段设计: 在数据库的数据类型为uniqueidentifier. 而在程序中对应的数据类型为GUID. property有get和set,也就是说能获取值也可以赋值.

  3. Qt安装配置

    Qt Creator: 下载: Qt 5.5.1 for Windows 32-bit(MinGW 4.9.2, 1.0 GB):http://download.qt.io/official_rele ...

  4. 笔记:xubuntu下如何让系统默认使用nvidia显卡,而不是intel集显

    经反复折腾,得到如下的解决方法: prime-select nvidia 简单吧,但关系是如果让它开机自动执行一次. 反复折腾了xinitrc ,~/.xinitrc , /etc/rc.local ...

  5. Java--String 和StringBuilder、StringBuffer 的区别?

    1.String是只读字符串,引用的字符串内容是无法改变的. 2.StringBuffer和StringBuilder的字符串对象可以直接进行修改. 3.StringBuilder没有被synchro ...

  6. mysql 用户管理和权限设置

    用户管理 mysql>use mysql; 查看 mysql> select host,user,password from user ; 创建 mysql> create user ...

  7. jstack+top定位性能问题

    定位性能问题,尤其是cpu使用率过高时,经常需要查找cpu消耗较高的线程,然后查看其堆栈,从而进入代码定位问题. 该场景下, jstack+top是一种非常经典的方式. jstack+top:   1 ...

  8. 【JS基础】数组

    filter() 返回数组中的满足回调函数中指定的条件的元素. array1.filter(callbackfn[, thisArg]) 对数组array1中的每个元素调用回调函数callbackfn ...

  9. jQuery 中bind(),live(),delegate(),on() 区别(转)

    当我们试图绑定一些事件到DOM元素上的时候,我相信上面这4个方法是最常用的.而它们之间到底有什么不同呢?在什么场合下用什么方法是最有效的呢? 准备知识: 当我们在开始的时候,有些知识是必须具备的: D ...

  10. dotnet core 使用 MongoDB 进行高性能Nosql数据库操作

    好久没有写过Blog, 每天看着开源的Java社区流口水, 心里满不是滋味. 终于等到了今年六月份 dotnet core 的正式发布, 看着dotnet 社区也一步一步走向繁荣, 一片蒸蒸日上的大好 ...