SSO英文全称Single Sign On,单点登录; SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
1)、任何系统都必须去登陆服务器进行登录 2)、服务器就记住了登录状态 3)、其他系统访问受保护资源,需要再次登录,跳转到sso_server登录的时候,服务器告诉客户端,已经登录过,无须登录。登录过得信息
单点登录开源框架:https://gitee.com/xuxueli0323/xxl-sso
每一个应用下都有一个相同的cookie。单点登录的核心就是不同系统之间同步cookie即可。
由于不同域名之间cookie没法共享 认证服务器只能将自己旗下的登录过的用户的标识以url地址参数的方式交给另外一个域名。让这个域名对应的服务器,自己妥善保存这个cookie信息。 所以说解决跨域名cookie共享问题我们采用通过url地址携带
application.properties
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
##单点登录url
sso.server.url=http://ssoserver.com:8082
## 单点登录请求路径
sso.server.loginpath=/login
SsoConfig 读取配置文件中的配置
package com.xiepanpan.gmall.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @author: xiepanpan
* @Date: 2020/3/2
* @Description: sso配置类
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "sso.server")
public class SsoConfig {
private String url;
private String loginPath;
}
HelloController.java
package com.xiepanpan.gmall.controller;
import com.xiepanpan.gmall.config.SsoConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author: xiepanpan
* @Date: 2020/3/2
* @Description:
*/
@Controller
public class HelloController {
@Autowired
SsoConfig ssoConfig;
@GetMapping("/")
public String index(Model model, @CookieValue(value = "sso_user",required = false)String ssoUserCookie,
@RequestParam(value = "sso_user",required = false) String ssoUserParam,
HttpServletRequest request,
HttpServletResponse response) throws IOException {
StringBuffer requestURL = request.getRequestURL();
if (!StringUtils.isEmpty(ssoUserParam)) {
//没有去登录页面登录 跳转回来 说明已经远程登录了
Cookie sso_user = new Cookie("sso_user",ssoUserParam);
response.addCookie(sso_user);
}
//判断是否登录
if (StringUtils.isEmpty(ssoUserCookie)) {
//没登录 重定向到登录服务器
String url = ssoConfig.getUrl() + ssoConfig.getLoginPath() + "?redirect_url=" + requestURL.toString();
response.sendRedirect(url);
return null;
}
//登录了,redis.get(ssoUserCookie)获取到用户信息,
model.addAttribute("loginUser","XP");
return "index";
}
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>client客户端</title>
</head>
<body>
<h1>欢迎 <label th:text="${loginUser}"></label></h1>
</body>
</html>
配置文件
server.port=8082
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.redis.host=192.168.217.130
LoginController.java
package com.xiepanpan.gmall.controller;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author: xiepanpan
* @Date: 2020/3/2
* @Description: 登录控制层
*/
@Controller
public class LoginController {
@Autowired
StringRedisTemplate redisTemplate;
/**
* 登录
* @param redirectUrl
* @param ssoUser
* @param response
* @return
* @throws IOException
*/
@GetMapping("/login")
public String login(@RequestParam(value = "redirect_url")String redirectUrl,
@CookieValue(value = "sso_user",required = false)String ssoUser,
HttpServletResponse response, Model model) throws IOException {
//判断是否登录过
if (!StringUtils.isEmpty(ssoUser)) {
//登录过 回到之前的地方 并且把当前ssoserver获取到的cookie以url参数方式传递到其他域名 实现cookie同步
String url = redirectUrl + "?" + "sso_user=" + ssoUser;
response.sendRedirect(url);
return null;
}else {
//转到login.html页面
model.addAttribute("redirect_url",redirectUrl);
return "login";
}
}
/**
* 处理登录操作
* @param username
* @param password
* @param response
* @param redirectUrl
* @throws IOException
*/
@PostMapping("/doLogin")
public void doLogin(String username,String password,HttpServletResponse response,
@RequestParam(value = "redirect_url")String redirectUrl) throws IOException {
//1. 模拟用户信息
Map<String,Object> map = new HashMap<>();
map.put("username",username);
map.put("email",username+"@qq.com");
//2. 标识用户登录
String token = UUID.randomUUID().toString();
redisTemplate.opsForValue().set(token, JSON.toJSONString(map));
//3. 登录成功做两件事
// (1) 浏览器保存当前token作为cookie sso_user=token
Cookie cookie = new Cookie("sso_user",token);
response.addCookie(cookie);
// (2) 重定向到之前的路径
response.sendRedirect(redirectUrl+"?"+"sso_user="+token);
}
}
登录页面login.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form th:action="@{/doLogin}" method="post">
用户名:<input name="username"/><br/>
密码:<input name="password" type="password"><br/>
<input type="hidden" name="redirect_url" th:value="${redirect_url}"/>
<input type="submit" value="登录"/>
</form>
</body>
</html>
整个流程图:
大体思路是这样的 模拟两个客户端 一个认证中心 第一个客户端通过认证中心输入密码登录 第二个客户端再通过认证中心登录就能访问到数据 添加hosts
127.0.0.1 client1.com
127.0.0.1 client2.com
127.0.0.1 ssoserver.com
client1流程
client2流程
这只是一个简单的模拟 当然还有很多问题。。
客户端每次发送请求要去服务器认证中心校验 cookie中对应的value是否有效 如果被篡改 匹配不到 要重新登录
那么每次都要去服务器认证 怎么摆脱认证中心 自己认证行不行 可以使用jwt+RSA jwt就是把请求认证中心返回的随机值改成jwt形式 jwt里面包含用户信息 只要客户端自己能从jwt中解析出来用户信息就可以了 因为请求认证中心的本质也是获取用户信息 这样就不用频繁的每次请求都调用认证中心去认证了
多端登录和适配:
参考博客: https://blog.csdn.net/keketrtr/article/details/73771652