概念:
SSO:Single Sign On 单点登录。SSO是多个应用系统中,用户只需要登录一次,就可以访问所有相互信任的应用系统。
原理:
CAS的系统原理图就是这的,上图只是第一次系统进行验证的时候。之后的验证就会清楚很多了。
实现:
如果我们要知道CAS是如何在MVC中实现的,我们就要对MVC的框架能够了解,其中,MVC中有两个对象,我觉得是非常需要知道的:
1、Membership对象:验证用户凭据并管理用户设置。
2、FormsAuthentication对象:为 Web 应用程序管理 Forms 身份验证服务。
这样我们就很清楚了,Membership对象,就是我们在CAS Server中的需要验证的信息,它包含了用户的数据库。FormsAuthentication对象,就是帮助我们生成Cookie和ST等的,spring MVC中的架构封装的很好,我们看到不到人家是怎么实现的,但是我们可以知道怎么用它们的就可以了。
我们创建一个MVC的Controller:
<span style="font-size:18px;">namespace MvcApplication1.Controllers
{
public class AccountController : Controller
{
//
// GET: /Account/LogOn
public ActionResult LogOn()
{
return View();
}
//
// POST: /Account/LogOn
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
//中的两个参数第一个为用户的信息,第二个为是否Cookie会持久化
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
//存储Cookie信息在本地
HttpCookie cookie = FormsAuthentication.GetAuthCookie(model.UserName, model.RememberMe);
cookie.Name = "selfUserInfo";
cookie.Expires = DateTime.Now.AddDays(1);
Response.Cookies.Add(cookie);
//MVC 封装的,帮助我们进行验证url信息。
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "提供的用户名或密码不正确。");
}
}
// 如果我们进行到这一步时某个地方出错,则重新显示表单
return View(model);
}
// FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
// 1, // 版本号。
// model.UserName, // 与身份验证票关联的用户名。
// DateTime.Now, // Cookie 的发出时间。
// DateTime.Now.AddMinutes(20),// Cookie 的到期日期。
// false, // 如果 Cookie 是持久的,为 true;否则为 false。
// roles ); // 将存储在 Cookie 中的用户定义数据。roles是一个角色字符串数组
// string encryptedTicket = FormsAuthentication.Encrypt(authTicket); //加密
// //存入cookie
// HttpCookie authCookie =
//new HttpCookie(FormsAuthentication.FormsCookieName,
//encryptedTicket);
// Response.Cookies.Add(authCookie);
//
// GET: /Account/LogOff
public ActionResult LogOff()
{
FormsAuthentication.SignOut();
//string subkeyName;
//subkeyName = "selfUserInfo";
//HttpCookie aCookie = Request.Cookies["selfUserInfo"];
//aCookie.Values.Remove(subkeyName);
//aCookie.Expires = DateTime.Now.AddDays(1);
Response.Cookies.Add(aCookie);
//var memberValidation = HttpContext.Request.Cookies.Get("selfUserInfo");
//从本地的数据库中我们查询到Cookie,然后我们将Cookie进行销毁
if (Request.Cookies["selfUserInfo"] != null)
{
HttpCookie mycookie;
mycookie = Request.Cookies["selfUserInfo"];
Response.Cookies["selfUserInfo"].Expires = System.DateTime.Now.AddMonths(-1);
Response.Cookies.Remove("selfUserInfo");//清除
Response.Cookies.Add(mycookie);//写入立即过期的*/
Response.Cookies["selfUserInfo"].Expires = DateTime.Now.AddDays(-1);
}
var memberValidation = HttpContext.Request.Cookies.Get("selfUserInfo");
return RedirectToAction("Index", "Home");
}
//
// GET: /Account/Register
public ActionResult Register()
{
return View();
}
//
// POST: /Account/Register
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// 尝试注册用户
MembershipCreateStatus createStatus;
Membership.CreateUser(model.UserName, model.Password, model.Email, null, null, true, null, out createStatus);
if (createStatus == MembershipCreateStatus.Success)
{
//注册新的成员
FormsAuthentication.SetAuthCookie("login", false /* createPersistentCookie */);
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", ErrorCodeToString(createStatus));
}
}
// 如果我们进行到这一步时某个地方出错,则重新显示表单
return View(model);
}
//
// GET: /Account/ChangePassword
[MemberValidation]
public ActionResult ChangePassword()
{
return View();
}
//
// POST: /Account/ChangePassword
[MemberValidation]
[HttpPost]
public ActionResult ChangePassword(ChangePasswordModel model)
{
if (ModelState.IsValid)
{
// 在某些出错情况下,ChangePassword 将引发异常,
// 而不是返回 false。
bool changePasswordSucceeded;
try
{
MembershipUser currentUser = Membership.GetUser(User.Identity.Name, true /* userIsOnline */);
changePasswordSucceeded = currentUser.ChangePassword(model.OldPassword, model.NewPassword);
}
catch (Exception)
{
changePasswordSucceeded = false;
}
if (changePasswordSucceeded)
{
return RedirectToAction("ChangePasswordSuccess");
}
else
{
ModelState.AddModelError("", "当前密码不正确或新密码无效。");
}
}
// 如果我们进行到这一步时某个地方出错,则重新显示表单
return View(model);
}
//
// GET: /Account/ChangePasswordSuccess
public ActionResult ChangePasswordSuccess()
{
return View();
}
#region Status Codes
private static string ErrorCodeToString(MembershipCreateStatus createStatus)
{
// 请参见 http://go.microsoft.com/fwlink/?LinkID=177550 以查看
// 状态代码的完整列表。
switch (createStatus)
{
case MembershipCreateStatus.DuplicateUserName:
return "用户名已存在。请输入不同的用户名。";
case MembershipCreateStatus.DuplicateEmail:
return "该电子邮件地址的用户名已存在。请输入不同的电子邮件地址。";
case MembershipCreateStatus.InvalidPassword:
return "提供的密码无效。请输入有效的密码值。";
case MembershipCreateStatus.InvalidEmail:
return "提供的电子邮件地址无效。请检查该值并重试。";
case MembershipCreateStatus.InvalidAnswer:
return "提供的密码取回答案无效。请检查该值并重试。";
case MembershipCreateStatus.InvalidQuestion:
return "提供的密码取回问题无效。请检查该值并重试。";
case MembershipCreateStatus.InvalidUserName:
return "提供的用户名无效。请检查该值并重试。";
case MembershipCreateStatus.ProviderError:
return "身份验证提供程序返回了错误。请验证您的输入并重试。如果问题仍然存在,请与系统管理员联系。";
case MembershipCreateStatus.UserRejected:
return "已取消用户创建请求。请验证您的输入并重试。如果问题仍然存在,请与系统管理员联系。";
default:
return "发生未知错误。请验证您的输入并重试。如果问题仍然存在,请与系统管理员联系。";
}
}
#endregion
public ActionResult ValidateCode()
{
ValidateCodeHelper helper = new ValidateCodeHelper();
string strCode = helper.CreateValidateCode(4);
Session["validateCode"] = strCode;
var byteData = helper.CreateValidateGraphic(strCode);
return File(byteData, "image/jpeg");
}
}
}</span>
我们可以看到其中在需要添加权限的Action上面打有这样的标签 [MemberValidation]。
然后我们需要写一个这样的一种特性AuthorizeAttribute:
我们创建一个类集成这个特性就行了:
<span style="font-size:18px;"> public class MemberValidationAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
//获取Cookies中的Login
//var memberValidation = System.Web.HttpContext.Current.Request.Cookies.Get("login");
//var memberValidation = filterContext.HttpContext.User.Identity.Name;
var memberValidation = filterContext.HttpContext.Request.Cookies.Get("selfUserInfo");
//如果memberValidation为null 或者 memberValidation不等于Success
if (memberValidation == null)
{
//页面跳转到 登录页面
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Account", action = "LogOn" }));
return;
}
//通过验证
return;
}
}</span>
大概的意思其实很简单,我就要看看本地上是不是有Cookie,有的话,我们就验证通过,没有的话,就要从新登陆界面进行验证。
我们创建一个访问页面HomePage:
<span style="font-size:18px;"> public class HomePageController : Controller
{
//
// GET: /HomePage/
[MemberValidation]
public ActionResult HomePage()
{
return View();
}
}</span>
在这个页面上,我们加载所有的应用系统,我们将这个页面成为门户。
看一下HomePage.cshtml:
<span style="font-size:18px;">@{
ViewBag.Title = "Index";
}
<html>
<head>
<title>HomePage</title>
@*<script src="../../Scripts/addPage.js"></script>*@
@*<script src="../../Scripts/EasyuiLayout.js"></script>*@
<link rel="stylesheet" type="text/css" href="../../Content/jquery-easyui-1.3.2/themes/default/easyui.css">
<link rel="stylesheet" type="text/css" href="../../Content/jquery-easyui-1.3.2/themes/icon.css">
<link rel="stylesheet" type="text/css" href="../../Content/jquery-easyui-1.3.2/demo/demo.css">
<script type="text/javascript" src="../../Content/jquery-easyui-1.3.2/jquery-1.8.0.min.js"></script>
<script type="text/javascript" src="../../Content/jquery-easyui-1.3.2/jquery.easyui.min.js"></script>
<script type="text/javascript" src="../../Content/jquery.balloon.js"></script>
<script src="../../Content/jquery-easyui-1.3.2/locale/easyui-lang-zh_CN.js"></script>
<link href ="../../CSS/index.css" rel ="stylesheet"/>
<script src="../../Scripts/MyScript/ITOO_Common.js"></script>
</head>
<body>
<input type="button" value="权限字典" οnclick="addTab('字典表设置', 'http://192.168.24.247:7089/DictionaryType/index?ticket=123')" />
<input type="button" value="新生报到" οnclick="addTab('新生报到', 'http://192.168.24.183:8076/FreshStudentReport/FreshStudentReport')" />
<div id="tt" class="easyui-tabs" style="width: 1100px; height: 800px; margin: 0; margin-left: auto; margin-right: auto; border: 1px; overflow: hidden;">
</div>
</body>
</html>
<script>
function addTab(tableName, controlerAddress) {
$("#tt").tabs('add', {
title: tableName,
content: '<div style="width:100%;height:99%;">'
+ '<iframe id="abc" name="PageFrame" frameBorder="0" width="100%" height="100%" src=' + controlerAddress + '>'
+ '</iframe>'
+ '</div>',
closable: true
});
}
</script>
</span>
大家可以看到,我们访问的新生报到界面,用的是ip端口号和controller/Action,就可以进行访问了,在FreshStudentReportController下的FreshStudentReport的action中我们要打上标签[MemberValidation],才能继续判断在本地是否有Cookie,可以让它进行验证。
总结:
spring MVC框架本身是封装的很好的,我们要学会如何去用它,就要清楚里面涉及到的对象。其实,我们先前一直想要实现能够在各个系统中进行验证,没有弄明白一个问题,就是Cookie是存储在本地浏览器中,只有在本地中,我们才可以做各种的判断和验证,否则,就没有办法可以看出该用户是否有权限,或者就是一个用户登陆了之后,所有的用户都可以不需要验证就可以访问所有的系统。