实验环境配置
HOST文件配置如下:
127.0.0.1 app.com
127.0.0.1 sso.com
IIS配置如下:
应用程序池采用.Net Framework 4.0
注意IIS绑定的域名,两个完全不同域的域名。
app.com网站配置如下:
sso.com网站配置如下:
memcached缓存:
数据库配置:
数据库采用EntityFramework 6.0.0,首次运行会自动创建相应的数据库和表结构。
授权验证过程演示:
在浏览器地址栏中访问:http://app.com,如果用户还未登陆则网站会自动重定向至:http://sso.com/passport,同时通过QueryString传参数的方式将对应的AppKey应用标识传递过来,运行截图如下:
URL地址:http://sso.com/passport?appkey=670b14728ad9902aecba32e22fa4f6bd&username=
输入正确的登陆账号和密码后,点击登陆按钮系统自动301重定向至应用会掉首页,毁掉成功后如下所示:
由于在不同的域下进行SSO授权登陆,所以采用QueryString方式返回授权标识。同域网站下可采用Cookie方式。由于301重定向请求是由浏览器发送的,所以在如果授权标识放入Handers中的话,浏览器重定向的时候会丢失。重定向成功后,程序自动将授权标识写入到Cookie中,点击其他页面地址时,URL地址栏中将不再会看到授权标示信息。Cookie设置如下:
登陆成功后的后续授权验证(访问其他需要授权访问的页面):
校验地址:http://sso.com/api/passport?sessionkey=xxxxxx&remark=xxxxxx
返回结果:true,false
客户端可以根据实际业务情况,选择提示用户授权已丢失,需要重新获得授权。默认自动重定向至SSO登陆页面,即:http://sso.com/passport?appkey=670b14728ad9902aecba32e22fa4f6bd&username=seo@ljja.cn 同时登陆页面邮箱地址文本框会自定补全用户的登陆账号,用户只需输入登陆密码即可,授权成功后会话有效期自动延长一年时间。
SSO数据库验证日志:
用户授权验证日志:
用户授权会话Session:
数据库用户账号和应用信息:
应用授权登陆验证页面核心代码:
1 /// <summary>
2 /// 公钥:AppKey
3 /// 私钥:AppSecret
4 /// 会话:SessionKey
5 /// </summary>
6 public class PassportController : Controller
7 {
8 private readonly IAppInfoService _appInfoService = new AppInfoService();
9 private readonly IAppUserService _appUserService = new AppUserService();
10 private readonly IUserAuthSessionService _authSessionService = new UserAuthSessionService();
11 private readonly IUserAuthOperateService _userAuthOperateService = new UserAuthOperateService();
12
13 private const string AppInfo = "AppInfo";
14 private const string SessionKey = "SessionKey";
15 private const string SessionUserName = "SessionUserName";
16
17 //默认登录界面
18 public ActionResult Index(string appKey = "", string username = "")
19 {
20 TempData[AppInfo] = _appInfoService.Get(appKey);
21
22 var viewModel = new PassportLoginRequest
23 {
24 AppKey = appKey,
25 UserName = username
26 };
27
28 return View(viewModel);
29 }
30
31 //授权登录
32 [HttpPost]
33 public ActionResult Index(PassportLoginRequest model)
34 {
35 //获取应用信息
36 var appInfo = _appInfoService.Get(model.AppKey);
37 if (appInfo == null)
38 {
39 //应用不存在
40 return View(model);
41 }
42
43 TempData[AppInfo] = appInfo;
44
45 if (ModelState.IsValid == false)
46 {
47 //实体验证失败
48 return View(model);
49 }
50
51 //过滤字段无效字符
52 model.Trim();
53
54 //获取用户信息
55 var userInfo = _appUserService.Get(model.UserName);
56 if (userInfo == null)
57 {
58 //用户不存在
59 return View(model);
60 }
61
62 if (userInfo.UserPwd != model.Password.ToMd5())
63 {
64 //密码不正确
65 return View(model);
66 }
67
68 //获取当前未到期的Session
69 var currentSession = _authSessionService.ExistsByValid(appInfo.AppKey, userInfo.UserName);
70 if (currentSession == null)
71 {
72 //构建Session
73 currentSession = new UserAuthSession
74 {
75 AppKey = appInfo.AppKey,
76 CreateTime = DateTime.Now,
77 InvalidTime = DateTime.Now.AddYears(1),
78 IpAddress = Request.UserHostAddress,
79 SessionKey = Guid.NewGuid().ToString().ToMd5(),
80 UserName = userInfo.UserName
81 };
82
83 //创建Session
84 _authSessionService.Create(currentSession);
85 }
86 else
87 {
88 //延长有效期,默认一年
89 _authSessionService.ExtendValid(currentSession.SessionKey);
90 }
91
92 //记录用户授权日志
93 _userAuthOperateService.Create(new UserAuthOperate
94 {
95 CreateTime = DateTime.Now,
96 IpAddress = Request.UserHostAddress,
97 Remark = string.Format("{0} 登录 {1} 授权成功", currentSession.UserName, appInfo.Title),
98 SessionKey = currentSession.SessionKey
99 }); 104
105 var redirectUrl = string.Format("{0}?SessionKey={1}&SessionUserName={2}",
106 appInfo.ReturnUrl,
107 currentSession.SessionKey,
108 userInfo.UserName);
109
110 //跳转默认回调页面
111 return Redirect(redirectUrl);
112 }
113 }
Memcached会话标识验证核心代码:
public class PassportController : ApiController
{
private readonly IUserAuthSessionService _authSessionService = new UserAuthSessionService();
private readonly IUserAuthOperateService _userAuthOperateService = new UserAuthOperateService();
public bool Get(string sessionKey = "", string remark = "")
{
if (_authSessionService.GetCache(sessionKey))
{
_userAuthOperateService.Create(new UserAuthOperate
{
CreateTime = DateTime.Now,
IpAddress = Request.RequestUri.Host,
Remark = string.Format("验证成功-{0}", remark),
SessionKey = sessionKey
});
return true;
}
_userAuthOperateService.Create(new UserAuthOperate
{
CreateTime = DateTime.Now,
IpAddress = Request.RequestUri.Host,
Remark = string.Format("验证失败-{0}", remark),
SessionKey = sessionKey
});
return false;
}
}
Client授权验证Filters Attribute
public class SSOAuthAttribute : ActionFilterAttribute
{
public const string SessionKey = "SessionKey";
public const string SessionUserName = "SessionUserName";
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var cookieSessionkey = "";
var cookieSessionUserName = "";
//SessionKey by QueryString
if (filterContext.HttpContext.Request.QueryString[SessionKey] != null)
{
cookieSessionkey = filterContext.HttpContext.Request.QueryString[SessionKey];
filterContext.HttpContext.Response.Cookies.Add(new HttpCookie(SessionKey, cookieSessionkey));
}
//SessionUserName by QueryString
if (filterContext.HttpContext.Request.QueryString[SessionUserName] != null)
{
cookieSessionUserName = filterContext.HttpContext.Request.QueryString[SessionUserName];
filterContext.HttpContext.Response.Cookies.Add(new HttpCookie(SessionUserName, cookieSessionUserName));
}
//从Cookie读取SessionKey
if (filterContext.HttpContext.Request.Cookies[SessionKey] != null)
{
cookieSessionkey = filterContext.HttpContext.Request.Cookies[SessionKey].Value;
}
//从Cookie读取SessionUserName
if (filterContext.HttpContext.Request.Cookies[SessionUserName] != null)
{
cookieSessionUserName = filterContext.HttpContext.Request.Cookies[SessionUserName].Value;
}
if (string.IsNullOrEmpty(cookieSessionkey) || string.IsNullOrEmpty(cookieSessionUserName))
{
//直接登录
filterContext.Result = SsoLoginResult(cookieSessionUserName);
}
else
{
//验证
if (CheckLogin(cookieSessionkey, filterContext.HttpContext.Request.RawUrl) == false)
{
//会话丢失,跳转到登录页面
filterContext.Result = SsoLoginResult(cookieSessionUserName);
}
}
base.OnActionExecuting(filterContext);
}
public static bool CheckLogin(string sessionKey, string remark = "")
{
var httpClient = new HttpClient
{
BaseAddress = new Uri(ConfigurationManager.AppSettings["SSOPassport"])
};
var requestUri = string.Format("api/Passport?sessionKey={0}&remark={1}", sessionKey, remark);
try
{
var resp = httpClient.GetAsync(requestUri).Result;
resp.EnsureSuccessStatusCode();
return resp.Content.ReadAsAsync<bool>().Result;
}
catch (Exception ex)
{
throw ex;
}
}
private static ActionResult SsoLoginResult(string username)
{
return new RedirectResult(string.Format("{0}/passport?appkey={1}&username={2}",
ConfigurationManager.AppSettings["SSOPassport"],
ConfigurationManager.AppSettings["SSOAppKey"],
username));
}
}
示例SSO验证特性使用方法:
[SSOAuth]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
总结:
从草稿示例代码中可以看到代码性能上还有很多优化的地方,还有SSO应用授权登陆页面的用户账号不存在、密码错误等一系列的提示信息等。在业务代码运行基本正确的后期,可以考虑往更多的安全性层面优化,比如启用AppSecret私钥签名验证,IP范围验证,固定会话请求攻击、SSO授权登陆界面的验证码、会话缓存自动重建、SSo服务器、缓存的水平扩展等。
源码地址:https://github.com/smartbooks/SmartSSO
分享到:
相关推荐
ASP.NET MVC SSO单点登录设计与实现是网络应用程序中常用的一种身份验证机制,它允许用户在一个应用系统中登录后,无需再次输入凭证就能访问其他关联的应用系统。本篇文章主要探讨了如何在ASP.NET MVC环境下构建这样...
asp.net mvc 单点登录Demo 无需第三方单独域名 实现原理可以参见codeproject中的单点登录的一篇文章: http://www.codeproject.com/Articles/106439/Single-Sign-On-SSO-for-cross-domain-ASP-NET-applic
在ASP.NET MVC 3中实现单点登录(Single Sign-On, SSO)是一项重要的功能,它允许用户在一个应用系统中登录后,无需再次验证即可访问其他相互信任的应用系统。 SSO的主要目标是提高用户体验,减少登录过程中的繁琐...
***实现单点登录(SSO)的方法涉及多个方面的知识,包括单点登录的基本概念、实现原理以及***环境下相关技术的应用。下面将详细介绍这些知识点。 单点登录(SSO)是一种用户登录机制,允许用户仅通过一次身份验证...
8. **单点登出(Single Sign-Out, SSO)**:与SSO相对应,也需要实现单点登出功能,即用户在一处登出时,所有关联的应用系统也同时登出。这通常通过广播登出请求到所有应用来实现。 在"asp.net 跨域单点登陆Demo"中...
ASP.NET SSO(Single Sign-On)单点登录是一种身份验证机制,它允许用户在一个应用程序中登录后,无需再次登录即可访问其他相互信任的应用程序。在ASP.NET框架中,实现SSO可以帮助开发者创建更加用户友好、高效且...
.NET Core 2.0 和 MVC 框架的结合为开发者提供了一个高效、跨平台的开发环境,而SSO(Single Sign-On)单点登录则是许多企业级应用中不可或缺的安全特性。在这个项目中,我们将深入探讨如何利用Redis或Memory Cache...
.NET SSO解决方案主要涉及到的是如何在多个应用系统之间实现单点登录(Single Sign-On),这是一种让用户在访问由多个独立系统组成的企业级应用时只需要登录一次的技术。在ASP.NET MVC框架下,SSO可以帮助提高用户...
通用模块Core 核心功能...Account:用户认证,可自己扩张到SSO单点登录 Account: 安全验证码实现 Account: 轻量级权限系统 OA:提供OA里人员,部门管理及分配的场景实例 CRM:客户管理系统原型 CMS:内容发布系统原型
.NET CAS(Central Authentication Service,中央认证服务)是Java平台上的一个开源身份验证框架,但同样可以与.NET环境集成,实现跨域的单点登录(Single Sign-On,简称SSO)。在.NET环境中,开发者通常会利用CAS...
在这个"CAS单点登录安装笔记4 -- asp.net client端的设置"中,我们将深入探讨如何将ASP.NET应用程序配置为使用CAS服务器进行身份验证。 首先,要使ASP.NET客户端与CAS服务器协同工作,我们需要在客户端应用程序中...
SSO单点登录是一种身份验证机制,允许用户在一个系统中登录后,无须再次认证即可访问其他相互信任的系统。这大大提升了用户体验,减少了登录步骤。 1. **SSO原理**:用户首次登录SSO系统(认证中心)后,会生成一个...
通用模块Core 核心功能模块,包括缓存管理,配置...Account:用户认证,可自己扩张到SSO单点登录 Account: 安全验证码实现 Account: 轻量级权限系统 OA:提供OA里人员,部门管理及分配的场景实例 CRM:客户关系管理系统
Web单点登录(Single Sign-On, SSO)是一种身份验证机制,它允许用户在一个系统上登录后,无需再次输入凭证即可访问多个相互关联的应用系统。这个功能在许多企业级应用和网络服务中广泛使用,提高了用户体验,同时也...
本解决方案源码是针对 ASP.NET 平台设计的,日期为20121213,旨在帮助开发者理解和实现 ASP.NET 应用中的单点登录功能。 单点登录的核心原理是共享身份验证状态,通常通过中央认证服务(Central Authentication ...
9. **单点登录(SSO)**:对于多应用环境,ASP.NET还支持单点登录(Single Sign-On, SSO),让用户在一次登录后可以访问所有相互信任的应用。 10. **ASP.NET Core中的改进**:ASP.NET Core对身份验证和授权进行了...
在介绍*** mvc中的权限过滤和单点登录(禁止重复登录)时,首先需要了解*** mvc框架的基本知识,以及在实际开发中如何运用这些知识点来实现安全的用户认证和权限控制。 ### 权限过滤 权限过滤在*** mvc框架中通常...
总结来说,这个示例项目是关于使用ASP.NET MVC4、WS-Federation和SAML2实现单点登录功能的。开发者通过PowerShell脚本进行配置,提供了一种高效且安全的方法来管理和验证跨多个应用的用户身份。理解这些技术及其相互...
这是一个SAML客户端库,而不是SAML服务器,它允许向您的ASP.NET应用程序添加SAML单点登录,但不能向其他应用程序提供身份验证服务。 安装 由一个简短的C#文件组成,您可以将其放入项目中(或)并开始使用它。 用法...
在.NET开发环境中,单点登录(Single Sign-On, SSO)和权限管理是构建大型企业级Web应用不可或缺的组成部分。SSO允许用户在一个系统中登录后,无须再次输入凭证即可访问其他相互信任的系统,提高了用户体验并简化了...