이 문서에서는 주로 ASP.NET MVC SSO Single Sign-On의 설계 및 구현을 소개합니다. 관심 있는 사용자는 자세히 알아볼 수 있습니다.
실험 환경 구성
HOST 파일 구성은 다음과 같습니다.
127.0.0.1 app.com
127.0.0.1 sso.com
IIS 구성은 다음과 같습니다.
응용 프로그램 풀 사용 .Net Framework 4.0
IIS에 바인딩된 도메인 이름에 주의하세요. 이는 완전히 다른 두 도메인 이름입니다.
app.com 웹사이트 구성은 다음과 같습니다.
sso.com 웹사이트 구성은 다음과 같습니다.
memcachedCache:
데이터베이스 구성:
데이터베이스는 EntityFramework 6.0.0을 사용하므로 해당 데이터베이스와 테이블 구조는 첫 번째 실행 시 자동으로 생성됩니다.
인증 확인 프로세스 데모:
방문: 브라우저 주소 표시줄의 http://app.com 사용자가 로그인하지 않은 경우 웹사이트는 자동으로 http://sso.com/passport로 리디렉션됩니다. 동시에 QueryString 매개변수를 통해 해당 AppKey 애플리케이션 ID를 전달합니다.
URL 주소: http://sso.com/passport?appkey=670b14728ad9902aecba32e22fa4f6bd&username=
로그인 계정 및 비밀번호를 입력한 후 로그인 버튼을 클릭하면 시스템이 자동으로 301 애플리케이션으로 리디렉션되고 홈페이지가 삭제됩니다.
SSO 인증 로그인이므로 다른 도메인에서 수행되는 경우 QueryString 메서드를 사용하여 인증 ID를 반환합니다. 쿠키는 동일한 도메인의 웹사이트에서 사용될 수 있습니다. 301 리디렉션 요청은 브라우저에서 전송되므로 인증 식별자가 Handers에 배치되면 브라우저가 리디렉션할 때 해당 요청이 손실됩니다. 리디렉션이 성공한 후 프로그램은 자동으로 인증 표시를 쿠키에 기록합니다. 다른 페이지 주소를 클릭하면 인증 표시 정보가 더 이상 URL 주소 표시줄에 표시되지 않습니다. 쿠키 설정은 다음과 같습니다.
로그인 성공 후 후속 인증 확인(인증이 필요한 다른 페이지에 접속):
인증 주소: http://sso.com/api/passport?sessionkey=xxxxxx&remark =xxxxxx
반환 결과: true, false
클라이언트는 실제 비즈니스 상황에 따라 인증이 손실되었으며 재인증이 필요하다는 메시지를 사용자에게 표시하도록 선택할 수 있습니다. 기본적으로 SSO 로그인 페이지(http://sso.com/passport?appkey=670b14728ad9902aecba32e22fa4f6bd&username=seo@ljja.cn)로 자동 리디렉션됩니다. 동시에 로그인 페이지의 이메일 주소 텍스트 상자는 다음과 같습니다. 사용자의 로그인 계정이 자동으로 완성됩니다. 사용자는 로그인 비밀번호만 입력하면 됩니다. 인증이 성공적으로 완료되면 세션 유효 기간이 자동으로 1년 연장됩니다.
SSO 데이터베이스 확인 로그:
사용자 인증 확인 로그:
사용자 인증 세션 세션:
데이터베이스 사용자 계정 및 애플리케이션 정보:
애플리케이션 인증 로그인 확인 페이지 핵심 코드 :
/// <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)); } }
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 서버, 캐시 수평 확장 등
위 내용은 ASP.NET MVC SSO Single Sign-On 디자인 예제에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!