Session登陆方案的优缺点:

  • 优点:Servlet容器自带,使用方便,性能高效
  • 缺点:协议变成有状态,无法实现集群下的 session 共享

Token + Redis 登陆方案:

登陆流程:

  1. 登陆接口接收前端传入的用户名密码参数
  2. 验证用户名和密码是否正确
  3. 基于当前用户信息,生成JWT令牌
  4. 返回 JWT 令牌给前端

登陆拦截器:

  1. 从请求中获取到 JWT 令牌
  2. 利用 JWT 的 SDK 对令牌进行解析,判断是否能够通过校验
  3. 只要令牌解析可以通过,就代表令牌是有效的,用户是登陆过的。

Token 续期:

  • 使用 access_token 和 refresh_token,其中 refresh_token 的过期时间是 access_token 的两倍,当 access_token 过期后使用 refresh_token 重新获取 access_token 和 refresh_token,如果 refresh_token 过期则重新登陆。
  • 使用 Redis + JWT

登陆拦截

该部分内容见:安全模块

引入安全模块的依赖

<!-- 安全模块,做登陆拦截 -->
<dependency>
<groupId>com.swx</groupId>
<artifactId>trip-common-security</artifactId>
</dependency>

接口信息

路径地址 http://localhost:9000/users/login
请求方式 POST
请求参数 username, password
返回结果 { token, userInfoVo }

返回VO

使用返回VO而非完整用户对象,可以避免返回敏感信息,被分析出表结构。

在 trip-users-api 模块中新建包 com.swx.user.vo,在该包下定义 LoginUserVo 类,用于接收请求参数

LoginUserVo
@Getter
@Setter
public class LoginUserVo {

private Long id;
private String nickname; // 昵称
private String phone; // 手机
private String email; // 邮箱
private Integer gender; // 性别
private String city; // 所在城市
private String headImgUrl; // 头像
private String info; // 个性签名
}

定义Service

找到 UserInfoService,定义登陆方法:

UserInfoService
/**
* 登陆接口
*
* @param username 用户名
* @param password 密码
* @return {token, 用户}
*/
Map<String, Object> login(String username, String password);

在 UserServiceImpl 中实现该方法:

UserServiceImpl
@Service
public class UserServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {

private final TokenService tokenService;

public UserServiceImpl(TokenService tokenService) {
this.tokenService = tokenService;
}
/**
* 登陆接口
*
* @param username 用户名
* @param password 密码
* @return {token, 用户}
*/
@Override
public Map<String, Object> login(String username, String password) {
// 1. 基于用户名查询用户对象
UserInfo userInfo = this.findByPhone(username);
if (userInfo == null) {
throw new BizException(500401, "用户名或密码错误");
}
// 2. 对参数密码进行加密
String encryptPassword = Md5Utils.getMD5(password + username);
// 3. 校验前端密码和数据库密码是否一致
if (!encryptPassword.equalsIgnoreCase(userInfo.getPassword())) {
throw new BizException(500401, "用户名或密码错误");
}

// 根据用户信息生成 jwt token
LoginUser loginUser = new LoginUser();
BeanUtils.copyProperties(userInfo, loginUser);
String jwtToken = tokenService.createToken(loginUser);

// 构建 Map 对象,存入Token 和用户对象,返回
Map<String, Object> data = new HashMap<>();
LoginUserVo loginUserVo = new LoginUserVo();
BeanUtils.copyProperties(userInfo, loginUserVo);
data.put("token", jwtToken);
data.put("user", loginUserVo);
return data;
}
}

定义Controller

在 UserInfoController 下定义登陆方法:

UserInfoController
@PostMapping("/login")
public R<Map<String, Object>> login(String username, String password) {
Map<String, Object> map = userInfoService.login(username, password);
return R.ok(map);
}