本文只是理一理思路
1. Server端
@RestController
@RequestMapping("userManager")
public class AuthrizeController {
//登陆记录
private static HashMap<String, String> accessRecord = new HashMap<>();
@PostMapping("/login")
public String login(String username, String password) {
//登陆逻辑判断略
//生成序列 过期时间等略
String access = UUID.randomUUID().toString();
String token = UUID.randomUUID().toString();
accessRecord.put(token, access);
return token + "," + access;
}
@GetMapping("/isLogin/{token}")
public String isLogin(@PathVariable("token") String token) {
if (accessRecord.containsKey(token)) {
//只要有key 就代表登陆 返回access
return accessRecord.get(token);
} else {
//没有登陆过 就返回404
return "404";
}
}
}
2. Client端
使用restTemplate 调用认证中心服务接口
@RestController
@RequestMapping("/client")
public class SSOLoginController {
@Autowired
RestTemplate restTemplate;
@PostMapping("/login")
public String login(@RequestParam String username, @RequestParam String password,
HttpServletRequest request, HttpServletResponse response) {
// 向注册中心登陆
HashMap<String, String> param = new HashMap<>();
param.put("username", username);
param.put("password", password);
String ret = restTemplate.postForObject("http://127.0.0.1:8080/userManager/login", param, String.class);
if (ret != null) {
//返回值为acces
// 登陆成功就 将login放入本地session
String[] split = ret.split(",");
String token = split[0];
String access = split[1];
// access 应该得设置过期时间
request.getSession().setAttribute(token, access);
response.addCookie(new Cookie("MyToken", token));
}
return "success";
}
// 用户拿着token来访问资源
@GetMapping("/accessToClient")
public String access(HttpServletRequest request, HttpServletResponse response) {
String token = null;
Cookie[] cookies = request.getCookies();
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals("MyToken")) {
// 找出拿到token的
token = cookies[i].getValue();
}
}
if (null == token || "".equals(token)) {
//token 不存在
return "请先登陆,再来访问资源,重定向到登陆界面!";
}
//验证token是否有效
//1. 本地 查找是否有token session 并验证其有效性
String access = (String) request.getSession().getAttribute(token);
if (access != null) {
return "访问到资源:123456";
} else {
//前往认证中心查找 是否再其他系统登陆过
String ret = restTemplate.getForObject("http://127.0.0.1:8080/userManager/isLogin/" + token, String.class);
if ("404".equals(ret)) {
//认证中心 认为未登陆
return "请先登陆,再来访问资源,重定向到登陆界面!";
} else {
//登陆 ,且返回 access
request.getSession().setAttribute(token, ret);
Cookie myToken = new Cookie("MyToken", token);
myToken.setMaxAge(1);
response.addCookie(myToken);
return "访问到资源:123456";
}
}
}
}
3. 启动项目
没有必要创建2个Client项目
允许并行执行就好了
每次启动,修改一下Client端的端口,避免端口冲突
4. 验证
- 先来尝试一下能否访问到
2.不能访问到就登陆
3.换一个端口访问资源
成功访问到了
5. 实现原理
1.未登陆状态:
浏览器发来的请求里是不包含验证身份的Cookies的,那么来了就拦截,重定向到登陆页面
登陆,本地session注册一次,认证中心注册一次,这样不用每次来了都到认证中心查
2.登陆状态:
浏览器发来的请求里包含了验证身份的Cookies,cookie为(MyToken,token)
-> 从cookies里面找出MyToken
-> 用token在本地session查是否登陆过的记录,是否登陆了,登陆了直接放行
-> 未登陆,用传过来的token去认证中心查,是否再其他系统登陆了
-> 其他系统登陆了,在本地增加登陆记录,放行
-> 其他系统未登陆,重定向引导登陆
我实现的是 token -> access
本地和认证中心是否有access,代表登陆记录
access 可设置过期时间,等众多扩展
问题: 退出登陆问题
用户在系统一,系统二都留有本地登陆记录,如果在系统一退出登陆,再去访问系统二每次都会被放行怎么办?
解决:本地备份和认证中心 的access都设置过期时间就好了
系统二能访问也是一段时间内能访问
或者在退出的时候,往登陆的子系统发送退出消息,让他们注销本地记录也行
再者 所有系统公用缓存层,或者Session
以上个人拙见
6. 流程图
网上搜来的一张图: