分布式Session实现登录功能

使用SpringSession + Redis 的方式实现分布式Session登录功能

1.导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>

2.配置Redis

1
2
3
4
5
6
7
8
9
10
11
12
spring:
redis:
host: XX.XXX.XXX.XX
port: 6379
password: 123456
database: 0
lettuce:
pool:
max-active: 10
max-idle: 10
min-idle: 1
time-between-eviction-runs: 10s

3.添加注解

在启动类上添加**@EnableRedisHttpSession**注解

4.验证码登录

此处使用近期的项目“黑马点评”作为例子

4.1.发送验证码

Controller

1
2
3
4
5
6
7
8
9
10
11
12
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
/**
* 发送手机验证码
*/
@PostMapping("code")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
return userService.code(phone,session);
}
}

Service

1
2
3
public interface IUserService extends IService<User> {
Result code(String phone, HttpSession session);
}

ServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 发送验证码
* @param phone
* @param session
* @return
*/
@Override
public Result code(String phone, HttpSession session) {
//校验手机号
boolean phoneInvalid = RegexUtils.isPhoneInvalid(phone);
if (phoneInvalid){
return Result.fail("手机号格式错误");
}
//生成验证码
String code = RandomUtil.randomNumbers(6);
//保存验证码到session
session.setAttribute("code",code);
//发送验证码
log.info("验证码发送成功:"+code);
//返回
return Result.ok();
}

4.2.登录

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
/**
* 登录功能
* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
*/
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
return userService.login(loginForm,session);
}
}

Service

1
2
3
public interface IUserService extends IService<User> {
Result login(LoginFormDTO loginForm, HttpSession session);
}

ServiceImpl

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
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
//校验手机号
String phone = loginForm.getPhone();
boolean phoneInvalid = RegexUtils.isPhoneInvalid(phone);
if (phoneInvalid){
return Result.fail("手机号格式错误");
}
//校验验证码
String code = (String) session.getAttribute("code");
if (!loginForm.getCode().equals(code)){
return Result.fail("验证码错误");
}
//查询是否存在该用户
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getPhone, phone);
User user = userMapper.selectOne(wrapper);
//不存在则创建新用户
if(user == null){
user = creatUser(phone);
}
//将用户保存到session中
session.setAttribute("user",user);

return Result.ok();
}
/**
* 创建新用户
*/
private User creatUser(String phone) {
User user = User.builder()
.phone(phone)
.nickName("user_"+RandomUtil.randomString(12))
.build();
userMapper.insert(user);
return user;
}

4.3.登录校验

拦截器

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
@Component
public class SessionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
//从session中取出user来判断是否登录
HttpSession session = request.getSession();
User user = (User) session.getAttribute("user");
//如果取出为空则未登录,需要拦截
if(user==null){
response.setStatus(401);
return false;
}
//已登录,则将user存入ThreadLocal
UserHolder.saveUser(BeanUtil.copyProperties(user,UserDTO.class));
//放行
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) throws Exception {
UserHolder.removeUser();
}
}

注册拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
@Resource
@Autowired
private SessionInterceptor sessionInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(sessionInterceptor)//添加拦截器
.addPathPatterns("/**")//拦截所有请求
.excludePathPatterns(
"/user/login",
"/user/code",
"/shop/**",
"/shop-type/**",
"/blog/hot",
"/voucher/**",
"/session/**"
);//排除
}
}

4.4.测试

完成以上代码后,当访问localhost:8080/login.html

当发送验证码后,来到Redis可以看到,Session以及自动被保存到Redis中了,并且其携带的验证码及其他参数也在其中。

点击登录,登录成功后,User信息也成功存入了Session

5.SpringSession配置

默认的session过期时间为30分钟,即1800s

可以在启动类上的**@EnableRedisHttpSession()** 配置 maxInactiveIntervalInSeconds 属性来设置过期时间,单位为s

如**@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 300)**,那么session的过期时间就是300s