因公众号更改推送规则,请点“在看”并加“星标”第一时间获取精彩技术分享
-
简介
-
传统 Session 机制及身份认证方案
-
集群环境下的 Session 困境及解决方案
-
多服务下的登陆困境及 SSO 方案
-
总结

背景

最近开发新产品,然后老板说我们现在系统太多了,每次切换系统登录太麻烦了,能不能做个优化,同一账号互通掉。作为一个资深架构狮,老板的要求肯定要满足,安排!

传统 Session 机制及身份认证方案

| Cookie 与服务器的交互

| 服务器端的 session 的机制
-
服务端首先查找对应的 cookie 的值(sessionid)。 -
根据 sessionid,从服务器端 session 存储中获取对应 id 的 session 数据,进行返回。 -
如果找不到 sessionid,服务器端就创建 session,生成 sessionid 对应的 cookie,写入到响应头中。
| 基于 session 的身份认证流程


集群环境下的 Session 困境及解决方案

随着技术的发展,用户流量增大,单个服务器已经不能满足系统的需要了,分布式架构开始流行。
| Session 共享方案
-
session 复制 -
session 集中存储
①session 复制

②session 集中存储


多服务下的登陆困境及 SSO 方案

| SSO 的产生背景

| SSO 的底层原理 CAS
①CAS 实现单点登录流程
-
当 b.com 打开时,发现自己未登陆,于是跳转到 ouath.com 去登陆 -
ouath.com 登陆页面被打开,用户输入帐户/密码登陆成功 -
ouath.com 登陆成功,种 cookie 到 ouath.com 域名下 -
把 sessionid 放入后台 redis,存放<ticket,sesssionid>数据结构,然后页面重定向到 A 系统 -
当 b.com 重新被打开,发现仍然是未登陆,但是有了一个 ticket 值 -
当 b.com 用 ticket 值,到 redis 里查到 sessionid,并做 session 同步,然后种 cookie 给自己,页面原地重定向 -
当 b.com 打开自己页面,此时有了 cookie,后台校验登陆状态,成功

②单点登录流程演示
CAS 登录服务 demo 核心代码如下:
public class UserForm implements Serializable{
private static final long serialVersionUID = 1L;
private String username;
private String password;
private String backurl;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getBackurl() {
return backurl;
}
public void setBackurl(String backurl) {
this.backurl = backurl;
}
}
@Controller
public class IndexController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("/toLogin")
public String toLogin(Model model,HttpServletRequest request) {
Object userInfo = request.getSession().getAttribute(LoginFilter.USER_INFO);
//不为空,则是已登陆状态
if (null != userInfo){
String ticket = UUID.randomUUID().toString();
redisTemplate.opsForValue().set(ticket,userInfo,2, TimeUnit.SECONDS);
return "redirect:"+request.getParameter("url")+"?ticket="+ticket;
}
UserForm user = new UserForm();
user.setUsername("laowang");
user.setPassword("laowang");
user.setBackurl(request.getParameter("url"));
model.addAttribute("user", user);
return "login";
}
@PostMapping("/login")
public void login(@ModelAttribute UserForm user,HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException {
System.out.println("backurl:"+user.getBackurl());
request.getSession().setAttribute(LoginFilter.USER_INFO,user);
//登陆成功,创建用户信息票据
String ticket = UUID.randomUUID().toString();
redisTemplate.opsForValue().set(ticket,user,20, TimeUnit.SECONDS);
//重定向,回原url ---a.com
if (null == user.getBackurl() || user.getBackurl().length()==0){
response.sendRedirect("/index");
} else {
response.sendRedirect(user.getBackurl()+"?ticket="+ticket);
}
}
@GetMapping("/index")
public ModelAndView index(HttpServletRequest request) {
ModelAndView modelAndView = new ModelAndView();
Object user = request.getSession().getAttribute(LoginFilter.USER_INFO);
UserForm userInfo = (UserForm) user;
modelAndView.setViewName("index");
modelAndView.addObject("user", userInfo);
request.getSession().setAttribute("test","123");
return modelAndView;
}
}
public class LoginFilter implements Filter {
public static final String USER_INFO = "user";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
Object userInfo = request.getSession().getAttribute(USER_INFO);;
//如果未登陆,则拒绝请求,转向登陆页面
String requestUrl = request.getServletPath();
if (!"/toLogin".equals(requestUrl)//不是登陆页面
&& !requestUrl.startsWith("/login")//不是去登陆
&& null == userInfo) {//不是登陆状态
request.getRequestDispatcher("/toLogin").forward(request,response);
return ;
}
filterChain.doFilter(request,servletResponse);
}
@Override
public void destroy() {
}
}
@Configuration
public class LoginConfig {
//配置filter生效
@Bean
public FilterRegistrationBean sessionFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new LoginFilter());
registration.addUrlPatterns("/*");
registration.addInitParameter("paramName", "paramValue");
registration.setName("sessionFilter");
registration.setOrder(1);
return registration;
}
}
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>enjoy login</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<div text-align="center">
<h1>请登陆</h1>
<form action="#" th:action="@{/login}" th:object="${user}" method="post">
<p>用户名: <input type="text" th:field="*{username}" /></p>
<p>密 码: <input type="text" th:field="*{password}" /></p>
<p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p>
<input type="text" th:field="*{backurl}" hidden="hidden" />
</form>
</div>
</body>
</html>
web 系统 demo 核心代码如下:
public class SSOFilter implements Filter {
private RedisTemplate redisTemplate;
public static final String USER_INFO = "user";
public SSOFilter(RedisTemplate redisTemplate){
this.redisTemplate = redisTemplate;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
Object userInfo = request.getSession().getAttribute(USER_INFO);;
//如果未登陆,则拒绝请求,转向登陆页面
String requestUrl = request.getServletPath();
if (!"/toLogin".equals(requestUrl)//不是登陆页面
&& !requestUrl.startsWith("/login")//不是去登陆
&& null == userInfo) {//不是登陆状态
String ticket = request.getParameter("ticket");
//有票据,则使用票据去尝试拿取用户信息
if (null != ticket){
userInfo = redisTemplate.opsForValue().get(ticket);
}
//无法得到用户信息,则去登陆页面
if (null == userInfo){
response.sendRedirect("http://127.0.0.1:8080/toLogin?url="+request.getRequestURL().toString());
return ;
}
/**
* 将用户信息,加载进session中
*/
UserForm user = (UserForm) userInfo;
request.getSession().setAttribute(SSOFilter.USER_INFO,user);
redisTemplate.delete(ticket);
}
filterChain.doFilter(request,servletResponse);
}
@Override
public void destroy() {
}
}
@Controller
public class IndexController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("/index")
public ModelAndView index(HttpServletRequest request) {
ModelAndView modelAndView = new ModelAndView();
Object userInfo = request.getSession().getAttribute(SSOFilter.USER_INFO);
UserForm user = (UserForm) userInfo;
modelAndView.setViewName("index");
modelAndView.addObject("user", user);
request.getSession().setAttribute("test","123");
return modelAndView;
}
}
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>enjoy index</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<div th:object="${user}">
<h1>cas-website:欢迎你"></h1>
</div>
</body>
</html>
③CAS 的单点登录和 OAuth2 的区别
如喜欢本文,请点击右上角,把文章分享到朋友圈
作者:王老狮
来源:https://juejin.cn/post/7123787027652280356
版权申明:内容来源网络,仅供分享学习,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢!
(完)
读到这里说明你喜欢本公众号的文章,欢迎 置顶(标星)本公众号 架构师指南,这样就可以第一时间获取推送了~ 在本公众号 架构师指南,后台回复:架构师指南,领取2T学习资料 ! 推荐阅读 1. 架构师图解淘宝10年后台架构演进 2. 怎么画出好的架构图,架构师必备技能 ! 3. 架构师谈推荐系统:从千人千面到千域千面 4. 基于Spring Cloud的微服务架构分析
本篇文章来源于微信公众号:程序IT圈
原创文章,作者:software,如若转载,请注明出处:https://www.sldh123.com/5528.html