Home > Article > Backend Development > Detailed explanation of ASP.NET MVC SSO single sign-on design example
This article mainly introduces ASP.NET MVC SSO single sign-on design and implementation. It has certain reference value. Those who are interested can learn more.
Experimental environment configuration
The HOST file configuration is as follows:
127.0.0.1 app.com
127.0.0.1 sso.com
IIS configuration is as follows:
#The application pool uses .Net Framework 4.0
Pay attention to the domain names bound to IIS, which are two completely different domain names.
app.com website configuration is as follows:
sso.com website configuration is as follows:
memcachedCache:
Database configuration:
## The database uses EntityFramework 6.0. 0, the corresponding database and table structure will be automatically created on the first run.Authorization verification process demonstration:
Visit: http://app.com in the browser address bar. If the user has not logged in, the website will automatically redirect Go to: http://sso.com/passport, and pass the corresponding AppKey application ID through QueryString parameters. The running screenshot is as follows: URL address: http://sso.com/passport ?appkey=670b14728ad9902aecba32e22fa4f6bd&username=## After entering the correct login account and password, click the login button and the system will automatically redirect 301 to the application and the homepage will drop. After the destruction is successful, it will be as shown below. :
Since SSO authorization login is performed under different domains, QueryString method is used to return the authorization ID. Cookies can be used on websites of the same domain. Since the 301 redirect request is sent by the browser, if the authorization identifier is placed in Handers, it will be lost when the browser redirects. After the redirection is successful, the program automatically writes the authorization mark into the cookie. When clicking on other page addresses, the authorization mark information will no longer be seen in the URL address bar. Cookie settings are as follows:
# Subsequent authorization verification after successful login (access to other pages that require authorization):
Verification address: http:// sso.com/api/passport?
sessionkey=xxxxxx&remark=xxxxxxReturn result: true, false
The client can choose to prompt the user for authorization based on the actual business situation Lost and needs to be reauthorized. By default, it is automatically redirected to the SSO login page, namely: http://sso.com/passport?appkey=670b14728ad9902aecba32e22fa4f6bd&username=seo@ljja.cn. At the same time, the email address text box on the login page will automatically complete the user's login account. The user only needs to Just enter the login password. After successful authorization, the session validity period will be automatically extended for one year.
SSO database verification log:User authorization verification log:
User authorization session Session:
Database user account and application information:
Core code of application authorization login verification page:
/// <summary> /// 公钥:AppKey /// 私钥:AppSecret /// 会话:SessionKey /// </summary> public class PassportController : Controller { private readonly IAppInfoService _appInfoService = new AppInfoService(); private readonly IAppUserService _appUserService = new AppUserService(); private readonly IUserAuthSessionService _authSessionService = new UserAuthSessionService(); private readonly IUserAuthOperateService _userAuthOperateService = new UserAuthOperateService(); private const string AppInfo = "AppInfo"; private const string SessionKey = "SessionKey"; private const string SessionUserName = "SessionUserName"; //默认登录界面 public ActionResult Index(string appKey = "", string username = "") { TempData[AppInfo] = _appInfoService.Get(appKey); var viewModel = new PassportLoginRequest { AppKey = appKey, UserName = username }; return View(viewModel); } //授权登录 [HttpPost] public ActionResult Index(PassportLoginRequest model) { //获取应用信息 var appInfo = _appInfoService.Get(model.AppKey); if (appInfo == null) { //应用不存在 return View(model); } TempData[AppInfo] = appInfo; if (ModelState.IsValid == false) { //实体验证失败 return View(model); } //过滤字段无效字符 model.Trim(); //获取用户信息 var userInfo = _appUserService.Get(model.UserName); if (userInfo == null) { //用户不存在 return View(model); } if (userInfo.UserPwd != model.Password.ToMd5()) { //密码不正确 return View(model); } //获取当前未到期的Session var currentSession = _authSessionService.ExistsByValid(appInfo.AppKey, userInfo.UserName); if (currentSession == null) { //构建Session currentSession = new UserAuthSession { AppKey = appInfo.AppKey, CreateTime = DateTime.Now, InvalidTime = DateTime.Now.AddYears(1), IpAddress = Request.UserHostAddress, SessionKey = Guid.NewGuid().ToString().ToMd5(), UserName = userInfo.UserName }; //创建Session _authSessionService.Create(currentSession); } else { //延长有效期,默认一年 _authSessionService.ExtendValid(currentSession.SessionKey); } //记录用户授权日志 _userAuthOperateService.Create(new UserAuthOperate { CreateTime = DateTime.Now, IpAddress = Request.UserHostAddress, Remark = string.Format("{0} 登录 {1} 授权成功", currentSession.UserName, appInfo.Title), SessionKey = currentSession.SessionKey }); 104 var redirectUrl = string.Format("{0}?SessionKey={1}&SessionUserName={2}", appInfo.ReturnUrl, currentSession.SessionKey, userInfo.UserName); //跳转默认回调页面 return Redirect(redirectUrl); } } 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; } }
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)); } }
Example SSO verification attribute usage:
[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(); } }
Summary:
From the draft sample code, we can see that there are many optimizations in code performance, as well as SSO application authorization login A series of prompt messages such as the user account on the page does not exist, the password is incorrect, etc. In the later stage when the business code is running basically correctly, you can consider optimizing more security levels, such as enabling AppSecret private key signature verification, IP range verification, fixed session request attack, and verification code
of the SSO authorization login interface. , Automatic reconstruction of session cache, SSo server, horizontal expansion of cache, etc.The above is the detailed content of Detailed explanation of ASP.NET MVC SSO single sign-on design example. For more information, please follow other related articles on the PHP Chinese website!