{{ v.name }}
{{ v.cls }}类
{{ v.price }} ¥{{ v.price }}
sso英文全称singlesignon,单点登录。sso是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
实现单点登录的实质就是要解决如何产生和存储信任,再就是其他系统如何验证这个信任的有效性,因此要点也就以下几个:
只要解决了以上的问题,达到了开头讲得效果就可以说是sso。最简单实现sso的方法就是用cookie,实现流程如下所示:
但是目前cookie的实现存在两个问题:
第一个问题可以通过对cookie来处理,第二个问题却是硬伤了。
sso的实现除了cookie之外还有许多实现方式,这里暂且分析一下一个基于cookie的实现源码。
首先给出本次分析的结论,具体源码贴在结论之后。
以上就是这个sso系统的具体实现逻辑。分析出来实现逻辑比较简单。可适用于一般的小型单域名的网站。
以下为具体实现代码:
web.xml
publicvoiddofilter(servletrequestrequest,servletresponseresponse,filterchainchain)throwsioexception,servletexception{ssoinfoinfo=ssocookieutil.vistssocookie((httpservletrequest)request);ssomanager.setssoinfo(info);info.setrequestobj((httpservletrequest)request);info.setresponseobj((httpservletresponse)response);try{//passtherequestalongthefilterchainchain.dofilter(request,response);}finally{ssomanager.clearssoinfo();}}
记录sso的ticket的bean,为了额外信息的获取,同时记录了httpservletrequest和httpservletresponse(这里只是为了额外信息的记录,比如访问ip地址等等)。
privateuseruser;privatestringticket;privatestringuid;//标识pc主机的idprivatestringapp;/***是否已经进行过ticket的校验*/privatebooleanisvalidated=false;privatehttpservletrequestrequestobj;privatehttpservletresponseresponseobj;publicbooleanislogin(){if(ticket==null){returnfalse;}if(!isvalidated){thrownewnotvalidateexception();}returnuser!=null;}
一个cookie的操作类
publicstaticssoinfovistssocookie(httpservletrequestrequest){cookie[]cookies=getallcookies(request);if(cookies==null||cookies.length==0){returnnewssoinfo(null);}stringticket=null;stringuid="none";for(cookiecookie:cookies){if(ticket_grant_ticket_cookie.equals(cookie.getname())){ticket=cookie.getvalue();}elseif(uid_cookie.equals(cookie.getname())){ticket=cookie.getvalue();}}ssoinfosi=newssoinfo(ticket);si.setuid(uid);stringapp=ssomanager.config.getvalue(ssomanager.config_app_id);si.setapp(app);returnsi;}
初次访问会返回一个ticket为null的ssoinfo。
存储线程级别的ssoinfo。(注意,上面是在filter中进行的初始化,此时请求继续分发)
ssomanager.setssoinfo(info);
privatestaticthreadlocal
useraccountinterceptor.prehandle
if(ssomanager.validatewebticket()){//登陆状态stringuserid=ssomanager.getssoinfo().getuser().getuserid();
publicstaticbooleanvalidatewebticket(){ssoinfosi=tempstore.get();if(si==null){logger.warn("thessoinfoobjectismissed,checkwhethersomeunexpectedoperationonthreadlocalisexecuted!");returnfalse;}validate2server(si);returnsi.islogin();}
validate2server
privatestaticvoidvalidate2server(ssoinfosi){if(si==null){return;}if(si.isvalidated()){return;}if(si.getticket()==null||si.getticket().length()==0){si.setvalidated(true);return;}…………(后台是远程调用验证系统传入ticket)}
验证系统
@requestmapping(value="validate.html")@responsebodypublicstringvalidatelogin(@requestparam(value="t",required=false)stringticket,stringapp,@requestparam(value="did",required=false)stringdeviceid){if(stringutils.isempty(ticket)){returnseterrorview("ticket值为空");}elseif(stringutils.isempty(app)){returnseterrorview("app类型不能为空");}elseif(stringutils.isempty(deviceid)){returnseterrorview("设备id为空");}try{stringuser=authmanager.checktgt(ticket,app);if(user!=null){returnbuildsuccessresponse(user);}else{returnbuilderrorresponse(user);}}catch(exceptione){logger.error("登录异常(unexpected)",e);returnseterrorview("服务异常,请稍后再试");}}
这里以ticket作为key来从redis中获取userid的信息
publicstringchecktgt(stringtgt,stringapp){stringuser=null;try{user=redistemplate.get(tgt);inttmpapp=stringutil.getintvalue(app,ssoconstant.app_site);if(!stringutils.isempty(user)){prolongticket(tgt,app,user,getvaliatetime(tmpapp));}}catch(exceptione){logger.error("检查tgt异常:",e);}returnuser;}
以上就是认证的整个流程,下面是登陆流程
try{userindb=loginserviceimpl.login(user);}catch(passwordnotmatchexceptione){if(logger.isinfoenabled()){logger.info("登录失败,密码错误");}}//4.登陆成功情况下,生成ticketuser.settype(ssoconstant.app_site);stringticket=authmanager.generatesitetgt(request,response,""+user.gettype(),userindb);
publicstringgeneratesitetgt(httpservletrequestrequest,httpservletresponseresponse,stringapp,useruser){stringtgt=null;try{uuiduuid=uuid.randomuuid();tgt=app+"-"+ssoconstant.ticket_grant_ticket+"-"+uuid.tostring().replaceall("-","");inttmpapp=stringutil.getintvalue(app,ssoconstant.app_site);intlonglogin=getvaliatetime(tmpapp);setupticket(tgt,app,"",user,longlogin);cookieticket=newcookie(ssoconstant.ticket_grant_ticket_cookie,tgt);stringdomain=propertiesutil.getstring(ssoconstant.propery_domain);ticket.setdomain(domain);ticket.setpath("/");ticket.setmaxage(longlogin);response.addcookie(ticket);}catch(exceptione){logger.error("生成tgt异常:",e);}returntgt;}
privatevoidsetupticket(stringticket,stringapp,stringdeviceid,useruser,intlonglogin){if(ticket==null){return;}if(longlogin<1){//保存30分钟longlogin=ssoconstant.ticket_grant_ticket_time_out_default;}stringoldticket=redistemplate.get(app+"_"+user.getuserid());if(oldticket!=null){redistemplate.delkey(oldticket);}redistemplate.setex(ticket,longlogin,user.getid()+":"+user.getuserid());redistemplate.setex(app+"_"+user.getuserid(),longlogin,ticket);}