SSO单点登录:
SSO英文全称Single Sign On。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的登录映射到其他应用中用于同一个用户的登录的机制。它是目前比较流行的企业业务整合的解决方案之一
- 在之前的单机环境中,我们通常是将用户登陆过的信息保存在服务端的Seesion中,通过判断Session中是否存在用户信息来确认用户是否登录。
- 在分布式环境中,以上方案就行不通了,因为分布式环境通常是由多台web服务器协调工作。而用户的登陆信息(Session)只能保存在一台web服务器中,并且这个登陆信息(Seesion)对于其他服务器是不可见的。
举个栗子,当一个用户需要访问A系统与B系统,用户首先访问A系统并且登陆,随后用户访问B系统,但是B系统中依然认定当前是未登录状态,所以依然会让用户进行登录。
实现原理:将SSO独立作为一个子系统管理用户信息,其他应用通过此系统确认用户的登陆状态。此系统应实现的功能:注册、登陆、验证
响应类:
/**
* fileName:LoginResp
* description:
* author:hcq
* createTime:2019-03-23 10:34
*/
public class LoginResp { //许多博主用E3Result命名,不明白含义,请各位大佬不吝告知
private Integer code;//状态码
private String massage;//提示信息
private String token;//Token
private Object data;//用户信息
private LoginResp(Integer code, String massage, Object data){
this.code=code;
this.massage=massage;
this.data=data;
}
public static LoginResp build(int code, String massage, Object data){
return new LoginResp(code,massage,data);
}
......省略getset
public LoginResp setToken(String token) {
this.token = token;
return this;
}
}
控制层:
Login
@Controller
public class LoginController {
@Value("${TOKEN_KEY}")
private String TOKEN_KEY;
@Autowired
LoginService service;
@RequestMapping("/loginPage")
public String showLogin(){ //向其他应用提供的登陆页面
return "/html/login.html";
}
@RequestMapping("/login")
@ResponseBody
public LoginResp login(String name, String password,HttpServletResponse response){
LoginResp resp = service.Login(name, password);
if(resp.getCode()==200){//判断用户是否登录成功
String token = resp.getToken();
response.addCookie(new Cookie(TOKEN_KEY,token));// 将token保存在cookie中,浏览器关闭即失效(类似sessionid)
}
return resp;
}
}
Token验证
/**
* fileName:TokenController
* description: 根据token查询用户信息
* author:hcq
* createTime:2019-03-23 10:47
*/
@RestController
public class TokenController {
@Autowired
TokenService service;
@RequestMapping("/user/token")
public Object getUserByToken(String token,String callback) {
String resp=JSON.toJSONString(service.getData(token));
if(callback!=null){//跨域请求
resp=callback+"("+resp+")";
}
return resp;
}
}
服务层:
Login Service
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
JedisUtil jedis;
@Autowired
UserDao dao;
@Override
public LoginResp Login(String name, String password) {
LoginResp resp=null;
Map user=dao.login(name,password);//查询数据库
if(user!=null){
//生成Token
String token= UUID.randomUUID().toString();
user.remove("upass");
//将用户信息存入redis,并设置过期时间
jedis.putObject("Session:"+token,user);
jedis.expire("SESSION:" + token,30*60);
resp=LoginResp.build(200,"登陆成功",null).setToken(token);
}else{//登陆失败
resp=LoginResp.build(400,"用户名或密码错误",null);
}
return resp;
}
}
Token Service
@Service
public class TokenServiceImpl implements TokenService {
@Autowired
JedisUtil jedis;
@Override
public Object getData(String token) {
String user_json= (String) jedis.getObject("Session:"+token);//通过token获取用户信息
System.out.println(user_json);
if(user_json==null||user_json.equals("")){
return LoginResp.build(201, "用户登录信息已过期",null);
}else{
//重置过期时间
jedis.expire(token,30*60);
return LoginResp.build(200, "用户已登录",JSON.parseObject(user_json));
}
}
}
表现层:
表现层需要通过js获取cookie信息并请求SSO系统判断用户是否登陆。我将此步骤封装为了一个js。在需要验证的页面导入js并创建回调函数即可
Home.html
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Title</title>
</head>
<body>
</body>
</html>
<script src="/js/jquery-1.8.3.min.js"></script>
<script src="/js/check_login.js"></script>
<script>
function UserInfoCallback(data){//回调函数 data:用户信息
$("body").html(JSON.stringify(data))
}
</script>
check_login.js
var token=getCookie("TOKEN_KEY"); //获取Token信息
$(function(){
$.ajax({
url:"http://localhost:8080/user/token",
type:'get',
data:{
token:token
},dataType:"jsonp",
success:function(data){
if(parseInt(data.code)!=200){//登陆失败
window.location="http://localhost:8080/loginPage"
}else{
UserInfoCallback(data.data);//回调函数,用户返回用户信息
}
}
})
})
//获取cookie
function getCookie(cookieName){
var cookieValue="";
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i];
if (cookie.substring(0, cookieName.length + 2).trim() == cookieName.trim() + "=") {
cookieValue = cookie.substring(cookieName.length + 2, cookie.length);
break;
}
}
}
return cookieValue;
}
请求流程: 用户第一次请求SSO系统,登陆成功后返回给客户端一个Cookie,以后的每次请求都会携带着这个Cookie(Token),在需要认证的页面中引入js,ajax拿着这个Token请求SSO系统查找用户登陆信息并返回,
总结: 其原理就是模拟了服务器端Seesion的实现,只不过是将Session保存在了redis中使多个应用可以共享,
感谢两位大佬的文章:
https://blog.csdn.net/qq_39056805/article/details/80789008
https://www.cnblogs.com/yueshutong/p/9468035.html