一、 环境搭建

创建认证模块

image-20211030224049077

image-20211030224240060

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.oy.gulimall</groupId>
<artifactId>gulimall-auth-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gulimall-auth-server</name>
<description>认证服务(社交登录、Oauth2.0、单点登录)</description>

<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.1.18.RELEASE</spring-boot.version>
<spring-cloud.version>Greenwich.SR6</spring-cloud.version>
</properties>

<dependencies>
<dependency>
<groupId>com.oy.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>

application.yml

1
2
3
4
5
6
7
8
9
10
11
spring:
application:
name: gulimall-auth-server
cloud:
nacos:
discovery:
server-addr: 192.168.56.10:8848
thymeleaf:
cache: false
server:
port: 20000

bootstrap.properties

1
2
3
4
5
6
7
8
spring.application.name=gulimall-auth-server
spring.cloud.nacos.config.server-addr=192.168.56.10:8848
server.port=20000
spring.cloud.nacos.config.namespace=29dda87b-1fde-405c-afd8-c5d79f7c5efe

spring.cloud.nacos.config.ext-config[0].data-id=gulimall-auth-server.yml
spring.cloud.nacos.config.ext-config[0].group=DEFAULT_GROUP
spring.cloud.nacos.config.ext-config[0].refresh=true

主启动类

1
2
3
4
5
6
7
8
9
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class GulimallAuthServerApplication {

public static void main(String[] args) {
SpringApplication.run(GulimallAuthServerApplication.class, args);
}
}

启动验证

image-20211030232057180

页面及域名访问初始化

  • 修改hosts实现域名访问
1
2
3
4
5
# gulimall
192.168.56.10 gulimall.com
192.168.56.10 search.gulimall.com
192.168.56.10 item.gulimall.com
192.168.56.10 auth.gulimall.com
  • 配置网关转发域名
1
2
3
4
- id: gumall_auth_server
uri: lb://gulimall-auth-server
predicates:
- Host=auth.gulimall.com

引入登录页面

将资料高级篇登录页面和注册页面放到 Nginx 动静分离配置。

image-20211031001455263

image-20211031001507197

二、注册功能

1、验证码倒计时

image-20211031225253641

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//点击发送验证码按钮触发下面函数
$("#sendCode").click(function () {
//如果有disabled,说明最近已经点过,则什么都不做
if($(this).hasClass("disabled")){

}else {
//调用函数使得当前的文本进行倒计时功能
timeOutChangeStyle();
//发送验证码
var phone=$("#phoneNum").val();
$.get("/sms/sendCode?phone="+phone,function (data){
if (data.code!=0){
alert(data.msg);
}
})
}
})

let time = 60;
function timeOutChangeStyle() {
//开启倒计时后设置标志属性disable,使得该按钮不能再次被点击
$("#sendCode").attr("class", "disabled");
//当时间为0时,说明倒计时完成,则重置
if(time==0){
$("#sendCode").text("点击发送验证码");
time=60;
$("#sendCode").attr("class", "");
}else {
//每秒调用一次当前函数,使得time--
$("#sendCode").text(time+"s后再次发送");
time--;
setTimeout("timeOutChangeStyle()", 1000);
}
}

2、 整合短信服务

在阿里云网页购买试用的短信服务

gulimall-third-party中编写发送短信组件,其中hostpathappcode可以在配置文件中使用前缀spring.cloud.alicloud.sms进行配置

image-20211031233109079

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Data
@ConfigurationProperties(prefix = "spring.cloud.alicloud.sms")
@Controller
public class SmsComponent {

private String host;
private String path;
private String appcode;

public void sendCode(String phone,String code) {
// String host = "http://dingxin.market.alicloudapi.com";
// String path = "/dx/sendSms";
String method = "POST";
// String appcode = "你自己的AppCode";
Map<String, String> headers = new HashMap<String, String>();
//最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
headers.put("Authorization", "APPCODE " + appcode);
Map<String, String> querys = new HashMap<String, String>();
querys.put("mobile",phone);
querys.put("param", "code:"+code);
querys.put("tpl_id", "TP1711063");
Map<String, String> bodys = new HashMap<String, String>();


try {
/**
* 重要提示如下:
* HttpUtils请从
* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
* 下载
*
* 相应的依赖请参照
* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml
*/
HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
System.out.println(response.toString());
//获取response的body
//System.out.println(EntityUtils.toString(response.getEntity()));
} catch (Exception e) {
e.printStackTrace();
}
}
}

编写controller,给别的服务提供远程调用发送验证码的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Controller
@RequestMapping(value = "/sms")
public class SmsSendController {

@Resource
private SmsComponent smsComponent;

/**
* 提供给别的服务进行调用
* @param phone 电话号码
* @param code 验证码
* @return
*/
@ResponseBody
@GetMapping(value = "/sendCode")
public R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code) {

//发送验证码
smsComponent.sendCode(phone,code);
System.out.println(phone+code);
return R.ok();
}
}

(3) 接口防刷

由于发送验证码的接口暴露,为了防止恶意攻击,我们不能随意让接口被调用。

  • 在redis中以phone-code将电话号码和验证码进行存储并将当前时间与code一起存储
    • 如果调用时以当前phone取出的v不为空且当前时间在存储时间的60s以内,说明60s内该号码已经调用过,返回错误信息
    • 60s以后再次调用,需要删除之前存储的phone-code
    • code存在一个过期时间,我们设置为10min,10min内验证该验证码有效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@GetMapping("/sms/sendCode")
@ResponseBody
public R sendCode(@RequestParam("phone")String phone) {
//接口防刷,在redis中缓存phone-code
ValueOperations<String, String> ops = redisTemplate.opsForValue();
String prePhone = AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone;
String v = ops.get(prePhone);
if (!StringUtils.isEmpty(v)) {
long pre = Long.parseLong(v.split("_")[1]);
//如果存储的时间小于60s,说明60s内发送过验证码
if (System.currentTimeMillis() - pre < 60000) {
return R.error(BizCodeEnum.SMS_CODE_EXCEPTION.getCode(), BizCodeEnum.SMS_CODE_EXCEPTION.getMsg());
}
}
//如果存在的话,删除之前的验证码
redisTemplate.delete(prePhone);
//获取到6位数字的验证码
String code = String.valueOf((int)((Math.random() + 1) * 100000));
//在redis中进行存储并设置过期时间
ops.set(prePhone,code+"_"+System.currentTimeMillis(),10, TimeUnit.MINUTES);
thirdPartFeignService.sendCode(phone, code);
return R.ok();
}

(4) 注册接口编写

gulimall-auth-server服务中编写注册的主体逻辑

  • 若JSR303校验未通过,则通过BindingResult封装错误信息,并重定向至注册页面

  • 若通过JSR303校验,则需要从redis中取值判断验证码是否正确,正确的话通过会员服务注册

  • 会员服务调用成功则重定向至登录页,否则封装远程服务返回的错误信息返回至注册页面

注: RedirectAttributes可以通过session保存信息并在重定向的时候携带过去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Data
public class UserRegisterVo {

@NotEmpty(message = "用户名不能为空")
@Length(min = 6, max = 19, message = "用户名长度在6-18字符")
private String userName;

@NotEmpty(message = "密码必须填写")
@Length(min = 6,max = 18,message = "密码必须填写6-18位字符")
private String password;

@NotEmpty(message = "手机号不能为空")
@Pattern(regexp = "^[1]([3-9])[0-9]{9}$",message = "手机号格式不正确")
private String phone;

@NotEmpty(message = "验证码不能为空")
private String code;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@PostMapping("/register")
public String register(@Valid UserRegisterVo registerVo, BindingResult result, RedirectAttributes attributes) {
//1.判断校验是否通过
Map<String, String> errors = new HashMap<>();
if (result.hasErrors()){
//1.1 如果校验不通过,则封装校验结果
result.getFieldErrors().forEach(item->{
errors.put(item.getField(), item.getDefaultMessage());
//1.2 将错误信息封装到session中
attributes.addFlashAttribute("errors", errors);
});
//1.2 重定向到注册页
return "redirect:http://auth.gulimall.com/reg.html";
}else {
//2.若JSR303校验通过
//判断验证码是否正确
String code = redisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + registerVo.getPhone());
//2.1 如果对应手机的验证码不为空且与提交上的相等-》验证码正确
if (!StringUtils.isEmpty(code) && registerVo.getCode().equals(code.split("_")[0])) {
//2.1.1 使得验证后的验证码失效
redisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX + registerVo.getPhone());

//2.1.2 远程调用会员服务注册
R r = memberFeignService.register(registerVo);
if (r.getCode() == 0) {
//调用成功,重定向登录页
return "redirect:http://auth.gulimall.com/login.html";
}else {
//调用失败,返回注册页并显示错误信息
String msg = (String) r.get("msg");
errors.put("msg", msg);
attributes.addFlashAttribute("errors", errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
}else {
//2.2 验证码错误
errors.put("code", "验证码错误");
attributes.addFlashAttribute("errors", errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
}
}

通过gulimall-member会员服务注册逻辑

  • 通过异常机制判断当前注册会员名和电话号码是否已经注册,如果已经注册,则抛出对应的自定义异常,并在返回时封装对应的错误信息
  • 如果没有注册,则封装传递过来的会员信息,并设置默认的会员等级、创建时间
1
2
3
4
5
6
7
8
9
10
11
12
13
@RequestMapping("/register")
public R register(@RequestBody MemberRegisterVo registerVo) {
try {
memberService.register(registerVo);
//异常机制:通过捕获对应的自定义异常判断出现何种错误并封装错误信息
} catch (UserExistException userException) {
return R.error(BizCodeEnum.USER_EXIST_EXCEPTION.getCode(), BizCodeEnum.USER_EXIST_EXCEPTION.getMsg());
} catch (PhoneNumExistException phoneException) {
return R.error(BizCodeEnum.PHONE_EXIST_EXCEPTION.getCode(), BizCodeEnum.PHONE_EXIST_EXCEPTION.getMsg());
}
return R.ok();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public void register(MemberRegisterVo registerVo) {
//1 检查电话号是否唯一
checkPhoneUnique(registerVo.getPhone());
//2 检查用户名是否唯一
checkUserNameUnique(registerVo.getUserName());
//3 该用户信息唯一,进行插入
MemberEntity entity = new MemberEntity();
//3.1 保存基本信息
entity.setUsername(registerVo.getUserName());
entity.setMobile(registerVo.getPhone());
entity.setCreateTime(new Date());
//3.2 使用加密保存密码
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encodePassword = passwordEncoder.encode(registerVo.getPassword());
entity.setPassword(encodePassword);
//3.3 设置会员默认等级
//3.3.1 找到会员默认登记
MemberLevelEntity defaultLevel = memberLevelService.getOne(new QueryWrapper<MemberLevelEntity>().eq("default_status", 1));
//3.3.2 设置会员等级为默认
entity.setLevelId(defaultLevel.getId());

// 4 保存用户信息
this.save(entity);
}

private void checkUserNameUnique(String userName) {
Integer count = baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("username", userName));
if (count > 0) {
throw new UserExistException();
}
}

private void checkPhoneUnique(String phone) {
Integer count = baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone));
if (count > 0) {
throw new PhoneNumExistException();
}
}

3. 用户名密码登录

gulimall-auth-server模块中的主体逻辑

  • 通过会员服务远程调用登录接口
    • 如果调用成功,重定向至首页
    • 如果调用失败,则封装错误信息并携带错误信息重定向至登录页
1
2
3
4
5
6
7
8
9
10
11
12
13
@RequestMapping("/login")
public String login(UserLoginVo vo,RedirectAttributes attributes){
R r = memberFeignService.login(vo);
if (r.getCode() == 0) {
return "redirect:http://gulimall.com/";
}else {
String msg = (String) r.get("msg");
Map<String, String> errors = new HashMap<>();
errors.put("msg", msg);
attributes.addFlashAttribute("errors", errors);
return "redirect:http://auth.gulimall.com/login.html";
}
}

gulimall-member模块中完成登录

  • 当数据库中含有以当前登录名为用户名或电话号且密码匹配时,验证通过,返回查询到的实体
  • 否则返回null,并在controller返回用户名或密码错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@RequestMapping("/login")
public R login(@RequestBody MemberLoginVo loginVo) {
MemberEntity entity=memberService.login(loginVo);
if (entity!=null){
return R.ok();
}else {
return R.error(BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getCode(), BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getMsg());
}
}

@Override
public MemberEntity login(MemberLoginVo loginVo) {
String loginAccount = loginVo.getLoginAccount();
//以用户名或电话号登录的进行查询
MemberEntity entity = this.getOne(new QueryWrapper<MemberEntity>().eq("username", loginAccount).or().eq("mobile", loginAccount));
if (entity!=null){
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
boolean matches = bCryptPasswordEncoder.matches(loginVo.getPassword(), entity.getPassword());
if (matches){
entity.setPassword("");
return entity;
}
}
return null;
}

4. 社交登录

(1) oauth2.0

(2) 在微博开放平台创建应用

(3) 在登录页引导用户至授权页

1
2
GET
https://api.weibo.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI
  • client_id: 创建网站应用时的app key
  • YOUR_REGISTERED_REDIRECT_URI: 认证完成后的跳转链接(需要和平台高级设置一致)

如果用户同意授权,页面跳转至 YOUR_REGISTERED_REDIRECT_URI/?code=CODE

code是我们用来换取令牌的参数

(4) 换取token

1
2
POST
https://api.weibo.com/oauth2/access_token?client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&grant_type=authorization_code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI&code=CODE
  • client_id: 创建网站应用时的app key
  • client_secret: 创建网站应用时的app secret
  • YOUR_REGISTERED_REDIRECT_URI: 认证完成后的跳转链接(需要和平台高级设置一致)
  • code:换取令牌的认证码

返回数据如下

(5) 获取用户信息

https://open.weibo.com/wiki/2/users/show

结果返回json

(6) 代码编写

认证接口

  • 通过HttpUtils发送请求获取token,并将token等信息交给member服务进行社交登录
  • 若获取token失败或远程调用服务失败,则封装错误信息重新转回登录页
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Controller
public class OauthController {

@Autowired
private MemberFeignService memberFeignService;

@RequestMapping("/oauth2.0/weibo/success")
public String authorize(String code, RedirectAttributes attributes) throws Exception {
//1. 使用code换取token,换取成功则继续2,否则重定向至登录页
Map<String, String> query = new HashMap<>();
query.put("client_id", "2144***074");
query.put("client_secret", "ff63a0d8d5*****29a19492817316ab");
query.put("grant_type", "authorization_code");
query.put("redirect_uri", "http://auth.gulimall.com/oauth2.0/weibo/success");
query.put("code", code);
//发送post请求换取token
HttpResponse response = HttpUtils.doPost("https://api.weibo.com", "/oauth2/access_token", "post", new HashMap<String, String>(), query, new HashMap<String, String>());
Map<String, String> errors = new HashMap<>();
if (response.getStatusLine().getStatusCode() == 200) {
//2. 调用member远程接口进行oauth登录,登录成功则转发至首页并携带返回用户信息,否则转发至登录页
String json = EntityUtils.toString(response.getEntity());
SocialUser socialUser = JSON.parseObject(json, new TypeReference<SocialUser>() {
});
R login = memberFeignService.login(socialUser);
//2.1 远程调用成功,返回首页并携带用户信息
if (login.getCode() == 0) {
String jsonString = JSON.toJSONString(login.get("memberEntity"));
MemberResponseVo memberResponseVo = JSON.parseObject(jsonString, new TypeReference<MemberResponseVo>() {
});
attributes.addFlashAttribute("user", memberResponseVo);
return "redirect:http://gulimall.com";
}else {
//2.2 否则返回登录页
errors.put("msg", "登录失败,请重试");
attributes.addFlashAttribute("errors", errors);
return "redirect:http://auth.gulimall.com/login.html";
}
}else {
errors.put("msg", "获得第三方授权失败,请重试");
attributes.addFlashAttribute("errors", errors);
return "redirect:http://auth.gulimall.com/login.html";
}
}

登录接口

  • 登录包含两种流程,实际上包括了注册和登录
  • 如果之前未使用该社交账号登录,则使用token调用开放api获取社交账号相关信息,注册并将结果返回
  • 如果之前已经使用该社交账号登录,则更新token并将结果返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@RequestMapping("/oauth2/login")
public R login(@RequestBody SocialUser socialUser) {
MemberEntity entity=memberService.login(socialUser);
if (entity!=null){
return R.ok().put("memberEntity",entity);
}else {
return R.error();
}
}

@Override
public MemberEntity login(SocialUser socialUser){
MemberEntity uid = this.getOne(new QueryWrapper<MemberEntity>().eq("uid", socialUser.getUid()));
//1 如果之前未登陆过,则查询其社交信息进行注册
if (uid == null) {
Map<String, String> query = new HashMap<>();
query.put("access_token",socialUser.getAccess_token());
query.put("uid", socialUser.getUid());
//调用微博api接口获取用户信息
String json = null;
try {
HttpResponse response = HttpUtils.doGet("https://api.weibo.com", "/2/users/show.json", "get", new HashMap<>(), query);
json = EntityUtils.toString(response.getEntity());
} catch (Exception e) {
e.printStackTrace();
}
JSONObject jsonObject = JSON.parseObject(json);
//获得昵称,性别,头像
String name = jsonObject.getString("name");
String gender = jsonObject.getString("gender");
String profile_image_url = jsonObject.getString("profile_image_url");
//封装用户信息并保存
uid = new MemberEntity();
MemberLevelEntity defaultLevel = memberLevelService.getOne(new QueryWrapper<MemberLevelEntity>().eq("default_status", 1));
uid.setLevelId(defaultLevel.getId());
uid.setNickname(name);
uid.setGender("m".equals(gender)?0:1);
uid.setHeader(profile_image_url);
uid.setAccessToken(socialUser.getAccess_token());
uid.setUid(socialUser.getUid());
uid.setExpiresIn(socialUser.getExpires_in());
this.save(uid);
}else {
//2 否则更新令牌等信息并返回
uid.setAccessToken(socialUser.getAccess_token());
uid.setUid(socialUser.getUid());
uid.setExpiresIn(socialUser.getExpires_in());
this.updateById(uid);
}
return uid;
}

5. SpringSession

(1) session 原理

jsessionid相当于银行卡,存在服务器的session相当于存储的现金,每次通过jsessionid取出保存的数据

问题:但是正常情况下session不可跨域,它有自己的作用范围

(2) 分布式下session共享问题

(3) 解决方案

1) session复制

2) 客户端存储

3) hash一致性

4) 统一存储

(4) SpringSession整合redis

通过SpringSession修改session的作用域

1) 环境搭建

导入依赖

https://docs.spring.io/spring-session/docs/2.5.0/reference/html5/#samples

auth 服务、product 服务 pom文件

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

修改配置

1
2
3
4
5
spring:
redis:
host: 192.168.56.102
session:
store-type: redis

添加注解

1
2
@EnableRedisHttpSession
public class GulimallAuthServerApplication {
2) 自定义配置
  • 由于默认使用jdk进行序列化,通过导入RedisSerializer修改为json序列化

  • 并且通过修改CookieSerializer扩大session的作用域至**.gulimall.com

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
public class GulimallSessionConfig {

@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer();
}

@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("GULISESSIONID");
serializer.setDomainName("gulimall.com");
return serializer;
}
}

(5) SpringSession核心原理 - 装饰者模式

  • 原生的获取session时是通过HttpServletRequest获取的
  • 这里对request进行包装,并且重写了包装request的getSession()方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);

//对原生的request、response进行包装
SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
request, response, this.servletContext);
SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
wrappedRequest, response);

try {
filterChain.doFilter(wrappedRequest, wrappedResponse);
}
finally {
wrappedRequest.commitSession();
}
}