单点登录一般需要至少两个站、一个登录站、一个接入站(确切的说应该是N个接入站),各个站需要实现的功能如图:
简单说明:登录站提供登录页面和退出功能,并提供用户信息的获取服务。接入站需要提供对应的登录成功回写服务,目的是为了存储本地登录信息,可以使用cookie等存储模式。
主站代码如下:
public class LoginController : Controller
{
private UserService UService => new UserService();
const string SessionKeyForToken = "CurrentUserLoginToken";
/// <summary>
/// 登录首页
/// </summary>
/// <param name="returnBackPath">返回的页面地址</param>
/// <param name="callBackPath">回写地址</param>
/// <returns></returns>
[HttpGet]
public ActionResult Index(string returnBackPath, string callBackPath)
{
var request = new LoginRequest
{
CallBackPath = callBackPath,
ReTurnBackPath = returnBackPath
};
var objToken = Session[SessionKeyForToken];
//简单检查用户是否已经登录,可以结合数据库或票据之类的进行验证
if (objToken != null && UService.CheckLoginByToken(objToken.ToString()))
{
//已经登录的则跳转到指定的Url
return RedirectPermanent($"{request.CallBackPath}?token={objToken.ToString()}&returnBackPath={request.ReTurnBackPath}");
}
return View(request);
}
/// <summary>
/// 登录
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[HttpPost]
public ActionResult Index(LoginRequest request)
{
try
{
var userModel = UService.GetUserCount(request.LoginName, request.Password);
if (userModel == null)
{
request.ErrorMsg = "账号密码错误";
return View(request);
}
var token = Guid.NewGuid().ToString().Replace("-", "");
CacheHelper.SetCache(token, userModel, DateTime.Now.AddHours(2));//存储用户登录信息
Session[SessionKeyForToken] = token;//存储用户的token,这边可以自己设置下session的过期时间
//登录成功跳转到指定的Url
return RedirectPermanent($"{request.CallBackPath}?token={token}&returnBackPath={request.ReTurnBackPath}");
}
catch (Exception ex)
{
request.ErrorMsg = ex.Message;
return View(request);
}
}
/// <summary>
/// 退出登录
/// </summary>
/// <param name="returnBackPath">返回的页面地址</param>
/// <param name="callBackPath">回写地址</param>
/// <param name="token">token登录信息</param>
/// <returns></returns>
[HttpGet]
public ActionResult LoginOut(string returnBackPath, string callBackPath, string token)
{
var objSession = Session[SessionKeyForToken];//防止token传值出错
if (objSession != null)
{
CacheHelper.RemoveKeyCache(objSession.ToString());//清除用户信息缓存
Session[SessionKeyForToken] = null;
}
else
{
CacheHelper.RemoveKeyCache(token);//清除用户信息缓存
}
return RedirectToAction("Index", "Login", new { returnBackPath = returnBackPath, callBackPath = callBackPath });
}
}
@model RightManagerPro.Models.LoginRequest
<head>
<title>单点登录DEMO</title>
</head>
<body>
<h2>登录</h2>
<div>
@using (Html.BeginForm("Index", "Login", FormMethod.Post))
{
<span>用户名:</span>
@Html.TextBoxFor(t => t.LoginName, new { maxlength = 50 })
<br />
<br />
<span>密码:</span>
@Html.TextBoxFor(t => t.Password, new { maxlength = 50 })
<br />
<br />
<input type="submit" value="登录" />
<span style="color:red;">@Model.ErrorMsg</span>
@Html.HiddenFor(m => m.CallBackPath)
@Html.HiddenFor(m => m.ReTurnBackPath)
}
</div>
</body>
获取用户信息服务代码:
public class AccountController : ApiController
{
private UserService UService => new UserService();
/// <summary>
/// 获取人员信息
/// </summary>
/// <param name="token">token值</param>
/// <returns></returns>
[HttpPost]
public UserModel GetUseInfo(string token)
{
return UService.GetUserInfoByToken(token);
}
}
public class UserService
{
/// <summary>
/// 根据Token检测用户的信息
/// </summary>
/// <param name="token">token值</param>
/// <returns></returns>
public bool CheckLoginByToken(string token)
{
if (string.IsNullOrWhiteSpace(token))
{
return false;
}
//通过Token获取登录实体(账户密码)
var userModel = CacheHelper.GetCache(token);
if (userModel == null)
{
return false;
}
//使用账户密码进行检查登录
var model = (UserModel)userModel;
var result = GetUserCount(model.LoginName, model.Password);
return result != null;
}
/// <summary>
/// 根据Token获取用户的信息
/// </summary>
/// <param name="token">token值</param>
/// <returns></returns>
public UserModel GetUserInfoByToken(string token)
{
if (string.IsNullOrWhiteSpace(token))
{
return null;
}
//通过Token获取登录实体(账户密码)
var userModel = CacheHelper.GetCache(token);
if (userModel == null)
{
return null;
}
//使用账户密码进行检查登录
var model = (UserModel)userModel;
return GetUserCount(model.LoginName, model.Password);
}
/// <summary>
/// 获取用户信息
/// </summary>
/// <param name="loginName">用户名</param>
/// <param name="password">密码</param>
/// <returns></returns>
public UserModel GetUserCount(string loginName, string password)
{
//TODO-验证用户的账号密码正确性
return new UserModel
{
LoginName = loginName,
Password = password
};
}
}
其他辅助代码:
/// <summary>
/// 登录请求实体
/// </summary>
public class LoginRequest
{
/// <summary>
/// 用户名
/// </summary>
public string LoginName { get; set; } = string.Empty;
/// <summary>
/// 密码
/// </summary>
public string Password { get; set; } = string.Empty;
/// <summary>
/// 回调地址授权Token
/// </summary>
public string CallBackPath { get; set; } = string.Empty;
/// <summary>
/// 返回的页面地址
/// </summary>
public string ReTurnBackPath { get; set; } = string.Empty;
/// <summary>
/// 错误提示信息
/// </summary>
public string ErrorMsg { get; set; } = string.Empty;
}
public class UserModel
{
/// <summary>
/// 用户名
/// </summary>
public string LoginName { set; get; }
/// <summary>
/// 密码
/// </summary>
public string Password { set; get; }
}
接入站代码:
public class OauthController : Controller
{
/// <summary>
/// 授权登录回调方法
/// </summary>
/// <param name="token">token</param>
/// <param name="returnBackPath">返回页面</param>
/// <returns></returns>
public ActionResult AuthCallback(string token, string returnBackPath)
{
var errorInfos = string.Empty;
try
{
var cookie = new HttpCookie(Constant.AccessToken, token)
{
//cookie过期时间固定设置为2小时,与token过期时间一致
Expires = DateTime.Now.AddHours(2),
Path = Constant.WebRootPath
};
Response.AppendCookie(cookie);
if (string.IsNullOrEmpty(returnBackPath))
{
return RedirectToAction("Index", "Home");
}
return RedirectPermanent(returnBackPath);
}
catch (Exception ex)
{
errorInfos += ";发生异常:" + ex.Message + ex.StackTrace;
}
ViewBag.ErrorInfo = errorInfos;
return View("Error", errorInfos);
}
public ActionResult Error(string errorInfos)
{
ViewBag.ErrorInfo = errorInfos;
return View();
}
}
/// <summary>
/// 首页
/// </summary>
/// <returns></returns>
public ActionResult Index()
{
var model = new UserModel { LoginName = "未登录,请登录" };
var token = HttpContext.Request.Cookies[Constant.AccessToken];
if (token == null || string.IsNullOrEmpty(token.Value))//没有登录
{
return View(model);
}
model = OauthService.GetUserInfo(token.Value);
if (model == null)
{
return View(new UserModel { LoginName = "未登录,请登录" });
}
return View(model);
}
/// <summary>
/// 登录
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public ActionResult Login()
{
Response.Redirect(OauthService.GenerateLoginUrl(Constant.LoginUrl));
return View();
}
/// <summary>
/// 退出登录
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public ActionResult LoginOut()
{
var tokenKey = string.Empty;
var url = string.Empty;
var cookies = HttpContext.Request.Cookies[Constant.AccessToken];
if (cookies != null && !string.IsNullOrEmpty(cookies.Value))
{
tokenKey = cookies.Value;
cookies.Expires = DateTime.Now.AddDays(-1);
cookies.Path = Constant.WebRootPath;
HttpContext.Response.Cookies.Add(cookies);
HttpContext.Request.Cookies.Remove(Constant.AccessToken);
}
Response.Redirect(OauthService.GetLoginOutUrl(tokenKey));
return View();
}
网站部署:
【实现的效果如下图:】
非登录状态——
登录——
登录之后——
完整的代码Demo下载:
登录主站代码:https://pan.baidu.com/s/1dpqAtzRTHq0-k_S_GBEoiA 09lw
接入站代码:https://pan.baidu.com/s/1ZVnGhKIG5T8LF9b0hUjbWw nnbn