SSO英文全称Single Sign On,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的登录映射到其他应用中用于同一个用户的登录的机制。它是目前比较流行的企业业务整合的解决方案之一。
为什么需要单点登录?
我们一般通过session设计登录,这种对于只有一个工程来说是没有问题的。但是对于分布式系统来说,由于牵涉到多个子系统,如果每一个系统都要输入一遍用户名,密码;这会非常麻烦。因此,单点登录应运而生。
实现原理
将登录系统单独摘出来,做成一个登录子系统。请求登陆时候访问这个子系统,当登陆验证通过的时候,生成一个token存入网站顶级域名下的cookie当中。将与这个token对应的用户状态信息存入缓存中去,并设置生存时间,此处缓存使用的是redis。当其他的子系访问必须登录才可访问的资源时候,必须先到登录系统进行验证token的存在性以及是否过期,验证通过才可进行访问。
token:这相当于一个令牌,不管你在哪个子系统下面,想要获取登陆状态就需要获得单点登录系统生成的token,这个token是在登陆成功时候生成,生成后保存到redis(缓存)中作为key,key生成规则使用的是UUID,目的是为了防止多个用户登录时候产生冲突,value为用户的信息,并设置有效时间。同时将这个key保存到cookie当中。这样当你登录其他子系统时需要验证登录状态的时候,可以通过获取浏览器cookie从而取出这个token并去缓存中查询是否存在相应数据,如果存在,就说明是在登陆状态。如果token存在而缓存中不存在说明用户登录状态过期了。如果没有token,说明用户根本没有登录。
具体实现(util以及Result是我自己封装的类,根据个人编码习惯而已)
/**
* 单点登录,登录成功后会生成一个token保存在cookie当中
* @param user 登录用户信息
* @param request 获取登录相关数据
* @param response 回复
* @return 返回登录结果
*/
@RequestMapping(value = "/login",method = RequestMethod.POST)
@ResponseBody
public Result login(User user,HttpServletRequest request,
HttpServletResponse response){
String url=request.getParameter("url");
//从service层获取信息,如果登陆成功里面会保存有token
Result result= this.userService.login(user);
//将cookie写入本地根域名下面
if (result.getStatus().equals(200)){
CookiteUtils.setCookie(request,response,TOKEN_ID, (String) result.getData());
//如果url不为空,直接跳转回原来访问的页面
if (!StringUtils.isBlank(url)){
try {
response.sendRedirect(url);
} catch (IOException e) {
e.printStackTrace();
}
}
}
return result;
}
/**
*
* @param user 用户登录相关信息
* @des: Result是我自己封装的返回类,登录时将token和用户信息存入redis
* @return
*/
public Result login(User user){
//先检验用户名与密码是否为空
if (StringUtils.isBlank(user.getUsername())||
StringUtils.isBlank(user.getPassword())){
return Result.build(400,"用户名或密码不能为空");
}
//转为UUID与数据库中的用户名密码进行比较
user.setPassword(DigestUtils.md5DigestAsHex(user.getPassword().getBytes()));
//根据用户的用户名密码查询信息
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);
}
最后通过jsonp进行跨域请求来进行测试,是否能够实现单点登录的功能。
//跨域请求,使用jsonp
var _ticket=$.cookie("TOKEN_ID");
if (!_ticket){
return;
}
$.ajax({
type:"GET",
dataType:"jsonp",
url:"http://localhost:8082/user/token/"+_ticket,//这个就是验证是否登录或者登录是否超时,因为我们我们放入redis缓存的时候设置了有效时长
success: function (data) {
//登录成功显示用户名
if (data.status==200){
var username=data.data.username;
var html=username;
$("#checkLogin").html(html);
}
}
});