分布式Session
一、SpringSession
1.Session共享问题
1.1.Session原理
Session就好比是某个银行的一个用户的账户,底层实现就是一个key-value存储的Map,一个Session存储了用户很多的k-v信息,就像去招商银行(某台Session服务器)取钱,我们需要带上招商银行的银行卡(银行卡的账号对应一个JESSIONID),不需要带其他银行的银行卡,其他银行卡在招商银行不认识就。
1.2.分布式下session共享问题
- 1.同一个服务,复制多份,session不同步问题
- 2.不同服务(跨域),session不能共享问题
2.分布式Session共享问题解决方案
2.1.Session复制
最大的问题:占用大量的网络带宽,降低了服务的业务处理能力;水平扩展受限制,如1台服务1G的Session内存,总共有100台服务,每台服务都要另外保存99G的Session内存实现同步。所以该方案不可取。
2.2.客户端存储
问题:由于Session数据由浏览器来存储,每次请求都需要带上Session数据,存在很大安全隐患,并且每次请求携带session数据浪费了网络带宽。因此,此方案不会使用。
2.3.hash一致性
根据ip或sid计算hash实现负载均衡,保证了用户访问服务都是同一台机器,但也有可能机器宕机重启导致session失效,以及机器水平扩展重新hash导致session失效。但这些问题也不是很大,session本来是有效期的,所以这两种反向代理都可以使用。
2.4.统一存储
优点:服务器可以水平扩展,session没有安全隐患,服务重启或扩容都不会session丢失;不足的是需要增加一次网络调用,从redis获取数据比直接从内存读取慢很多;但这些不足都可以用SpringSession完美解决。
3.SpringSession整合
3.1.导入依赖
implementation 'org.springframework.session:spring-session-data-redis'
3.2.配置Session存储方式
spring.session.store-type=redis
server.servlet.session.timeout=30m
3.3.启动类整合redis作为session存储
@EnableRedisHttpSession //整合redis作为session存储
3.4.Controller使用Session
入参加上:HttpSession session
存储session信息:
session.setAttribute("loginUser", data);
3.5.需要解决的问题
- 1、默认发的令牌。session=N2ZjZWFjYmQtMGMzNi00Y2M2LWIwZjctZmIxMThiZTU5NzU3。作用域:当前域:(解决子域session共享问题)
- 2、使用JSON的序列化方式来序列化对象数据到redis中
4.自定义SpringSession完成子域Session共享问题
4.1.SpringSession自定义Cookie
代码配置:
@Configuration
public class GulimallSessionConfig {
/**
* Once you have setup Spring Session you can easily customize how the session cookie is written by exposing a
* CookieSerializer as a Spring Bean. Out of the box, Spring Session comes with DefaultCookieSerializer. Simply
* exposing the DefaultCookieSerializer as a Spring Bean will augment the existing configuration when using
* configurations like @EnableRedisHttpSession.
* (一旦您设置了Spring Session,就可以通过将CookieSerializer公开为Spring Bean来
* 轻松定制会话cookie的编写方式。开箱即用,Spring Session附带了
* DefaultCookieSerializer。当使用@EnableRedisHttpSession等配置时,简单地将
* DefaultCookieSerializer公开为SpringBean将增加现有配置。)
*
* @return
*/
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("JSESSIONID");
serializer.setDomainName("gulimall.com"); //解决子域session共享问题
return serializer;
}
}
4.2.自定义SpringSession Redis序列化器
官方地址:https://docs.spring.io/spring-session/docs/2.1.0.BUILD-SNAPSHOT/reference/html5/#samples
代码配置:
/**
* 自定义SpringSession Redis序列化器
* Custom RedisSerializer
* You can customize the serialization by creating a Bean named springSessionDefaultRedisSerializer that
* implements RedisSerializer<Object>.
*
* @return
*/
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
5.SpringSession核心原理
核心原理:
@EnableRedisHttpSession导入RedisHttpSessionConfiguration配置
1、给容器中添加了一个组件
SessionRepository=>RedisIndexedSessionRepository:redis操作session。session的增删改查封装类
2、SessionRepositoryFilter=>Filter:session存储过滤器:每个请求过来都必须经过Filter
1)、创建的时候,就自动从容器中获取到SessionRepository;
2)、原生的request、response都被包装:SessionRepositoryRequestWrapper、SessionRepositoryResponseWrapper
3)、以后获取session。request.getSession()=>SessionRepositoryRequestWrapper
4)、wrappedRequest.getSession();==> SessionRepository中获取到的:sessionRepository.findById(sessionId)
使用了装饰者模式;
自动延期:redis中的数据也是有过期时间。
二、单点登录
1.单点登录流程(Redis+Cookie+令牌机制)
SpringSession只能解决同域名下的单点登录,但不能解决多系统(不同域名)下的单点登录问题
分布式单点登录框架参考:https://gitee.com/xuxueli0323/xxl-sso
1.1.单点登录流程图
1.2.单点登录流程-代码实现
1.2.1.单点登录的认证服务器sso-server
1.导入依赖:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
2.配置文件application.properties
server.port=8080
spring.redis.host=192.168.56.10
spring.redis.port=6379
3.登录页login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录页</title>
</head>
<body>
<form action="/doLogin" method="post">
用户名:<input type="text" name="username"/><br/>
密码:<input type="password" name="password"/><br/>
<input type="hidden" name="redirect_url" th:value="${url}"/>
<input type="submit" value="登录">
</form>
</body>
</html>
4.LoginController
@Controller
public class LoginController {
@Autowired
private StringRedisTemplate redisTemplate;
@ResponseBody
@GetMapping("userInfo")
public String userInfo(@RequestParam("token") String token) {
String userInfo = redisTemplate.opsForValue().get(token);
return userInfo;
}
@GetMapping("login.html")
public String loginPage(@RequestParam("redirect_url") String url, Model model,
@CookieValue(value = "sso_token", required = false) String ssoToken) {
if (!StringUtils.isEmpty(ssoToken)) {
//说明之前有人登录过,浏览器留下了痕迹
return "redirect:" + url + "?token=" + ssoToken;
}
model.addAttribute("url", url);
return "login";
}
@PostMapping("doLogin")
public String doLogin(@RequestParam("username") String username,
@RequestParam("password") String password,
@RequestParam("redirect_url") String url,
HttpServletResponse response) {
if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) {
//登录成功,跳回之前页面
//把登录成功的用户存起来。
String uuid = UUID.randomUUID().toString().replace("-", "");
redisTemplate.opsForValue().set(uuid, username);
Cookie ssoToken = new Cookie("sso_token", uuid);
response.addCookie(ssoToken);
return "redirect:" + url + "?token=" + uuid;
}
//登录失败,展示登录页
return "login";
}
}
1.2.2.单点登录的客户端sso-client
1.导入依赖:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
2.配置文件application.properties
server.port=8081
sso.server=http://ssoserver.com:8080
sso.client=http://client1.com:8081
3.受访问保护的资源页employees.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>员工列表</title>
</head>
<body>
<h1>欢迎:[[${session.loginUser}]]</h1>
<ul>
<li th:each="emp:${emps}">姓名:[[${emp}]]</li>
</ul>
</body>
</html>
4.HelloController
@Controller
public class HelloController {
@Value("${sso.server}")
private String ssoServerUrl;
@Value("${sso.client}")
private String ssoClientUrl;
/**
* 无需登录就可以访问
*
* @return
*/
@ResponseBody
@GetMapping("hello")
public String hello() {
return "hello";
}
@GetMapping("employees")
public String employees(Model model, HttpSession session,
@RequestParam(value = "token", required = false) String token) {
if (!StringUtils.isEmpty(token)){
//去ssoserver登录成功回来就会带上token
//去ssoserver获取当前token真正对应的用户信息
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> forEntity = restTemplate.getForEntity(ssoServerUrl + "/userInfo?token=" + token, String.class);
session.setAttribute("loginUser", forEntity.getBody());
}
Object loginUser = session.getAttribute("loginUser");
if (loginUser == null) {
//没登录,跳转到登陆服务器进行登录
//跳转过去以后,使用url上的查询参数标识我们自己是哪个页面
return "redirect:" + ssoServerUrl + "/login.html?redirect_url=" + ssoClientUrl + "/employees";
}
List<String> emps = new ArrayList<>();
emps.add("张三");
emps.add("李四");
model.addAttribute("emps", emps);
return "employees";
}
}
本文来自博客园,作者:冰枫丶,转载请注明原文链接:https://www.cnblogs.com/lqsblog/p/16845085.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)