SSO单点登录实现方案
最近打算做个项目,需要用到单点登录,搜了看了一下资料找到一种单点登录方案,特此记录。
一、实现原理
将登录系统单独摘出来,做成一个登录子系统。请求登陆时候访问这个子系统,当登陆验证通过的时候,生成一个token存入网站顶级域名下的cookie当中。将与这个token对应的用户状态信息存入缓存中去,并设置生存时间,此处缓存使用的是redis。当其他的子系访问必须登录才可访问的资源时候,必须先到登录系统进行验证token的存在性以及是否过期,验证通过才可进行访问。
具体流程:(侵权删除)
二、重要的地方:
(1)cookie保存在一级域名下面(我叫做根域名):比如:对于百度,baidu.com就是一级域名,而www.baidu.com或者zhidao.baidu.com这样的域名都是二级域名,而我们可以从二级域名下访问一级域名的cookie。这样我们就可以访问保存到本地的token
(2)token:这相当于一个令牌,不管你在哪个子系统下面,想要获取登陆状态就需要获得单点登录系统生成的token,这个token是在登陆成功时候生成,生成后保存到redis(缓存)中作为key,key生成规则使用的是UUID,目的是为了防止多个用户登录时候产生冲突,value为用户的信息,并设置有效时间。同时将这个key保存到cookie当中。这样当你需要验证登录状态的时候,只需要取出这个token并去缓存中查询是否存在相应数据,如果存在,就说明是在登陆状态。如果token存在而缓存中不存在说明用户登录状态过期了。如果没有token,说明用户根本没有登录。
三、具体的实现
核心部分的代码:其中Result保存处理的结果。
UserServiceImpl:
//根据用户的用户名密码查询信息 User temp=userMapper.findByUsernameAndPassword(user); //如果用户名密码正确会查询出信息,不对的话无法查询到 if(temp==null){ return Result.build(400,"用户名或密码不正确"); } //查询到的话生成token作为键,用户的信息作为值保存到redis中 String token_ID=UUID.randomUUID().toString().replaceAll("-",""); temp.setPassword(null); jedis.set(TOKEN_ID+":"+token_ID, JsonUtils.objectToJson(temp)); jedis.expire(TOKEN_ID+":"+token_ID,300); //返回给controller层,并从controller层中将token保存到根cookie中 return Result.ok(token_ID);
UserController:
//从service层获取信息,如果登陆成功里面会保存有token Result result= this.userService.login(user); //将cookie写入本地根域名下面 if (result.getStatus().equals(200)){ CookiteUtils.setCookie(request,response,TOKEN_ID,result.getMsg()); } return result;
四、其他系统调用sso系统
我设计的是user子系统,里面只有一个登录状态的检测功能。User子系统中通过ajax异步发送信息,调用sso系统的代码进行登录状态的检测,并将数据返回给调用处。但是由于js无法跨域请求数据,所以使用了jsonp(不懂的话百度一下这方面的知识)。大体意思不能通过js跨域请求数据,但可以跨域请求js函数或js文件。
//跨域请求,使用jsonp $.ajax({ type:"GET", dataType:"jsonp", url:"http://localhost:8080/user/token/"+_ticket, success: function (data) { //登录成功显示用户名 if (data.status==200){ var username=data.data.username; var html=username; $("#checkLogin").html(html); } } });
具体代码放在我的github https://github.com/GregZQ/sso
ps:如果有不正确的地方请各位提出指正,谢谢。