开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第13天,点击查看活动详情
JWT登录+redis存储token及用户信息获取
使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:
- 客户端使用用户名跟密码请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
- 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
- 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
- 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
#JWT
JWT就是一个加密的字符串,作为验证信息在计算机之间传递,只有可以访问持有对应正确的加密密钥的计算机才能对其进行解密,从而验证携带这个令牌(Token)的请求是否合法。
登录使用JWT技术:jwt 可以生成 一个加密的token,做为用户登录的令牌,当用户登录成功之后,发放给客户端。请求需要登录的资源或者接口的时候,将token携带,后端验证token是否合法。
#JWT的组成:
- head头部,唯一在头部里面要包含的是 alg 这个属性,如果是加密的 JWT,这个属性的值就是使用的签名或者解密用的算法。如果是未加密的 JWT,这个属性的值要设置成 none。{"type":"JWT","alg":"HS256"} 固定,这部分是可以被解密的。
- payload(数据),存放信息,比如,用户id,过期时间等等,可以被解密,不能存放敏感信息
- signature(签证),由head、payload加上密钥加密而成,密钥不丢失的情况下,可以认为是安全的。
JWT验证主要验证signature部分是否合法。
#Springboot的JWT工具类
Step1: 导入依赖包
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
Step2: 工具类生成token与测试
public class JWTUtils {
private static final String jwtToken = "123456Mszlu!@#$$";
public static String createToken(Long userId){
Map<String,Object> claims = new HashMap<>();
claims.put("userId",userId);
JwtBuilder jwtBuilder = Jwts.builder()
.signWith(SignatureAlgorithm.HS256, jwtToken) // 签发算法(head部分),秘钥为jwtToken
.setClaims(claims) // body数据,要唯一,自行设置。payload部分数据
.setIssuedAt(new Date()) // 设置签发时间:保证每次生成的token不同
.setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 60 * 1000));// 一天的有效时间
String token = jwtBuilder.compact();
return token;
}
//token解析
public static Map<String, Object> checkToken(String token){
try {
Jwt parse = Jwts.parser().setSigningKey(jwtToken).parse(token);
return (Map<String, Object>) parse.getBody();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
#登录
#登录流程与逻辑分析
客户使用账号密码发起登录请求,服务端收到请求后会做验证。项目中,没有直接对user表的pojo进行操作,而是将账号、密码、昵称三个字段封装成了一个登录参数类。
当收到客户端请求,后端会先去验证参数是否合法(账号密码是否有缺失),然后对密码进行MD5盐加密,如password = DigestUtils.md5Hex(password + slat);(到这里能确定登录参数合法)然后根据用户名和密码到user表中做查询,如果存在就去生成token,返回给前端。
值得一提的是,项目中,查询user表设置了只查询需要的字段并只查询到一条数据就不再查了,提升了性能。另外,还将token放入了redis中(token:user信息),并设置了过期时间。**登录认证的时候先认证token是否合法,去redis认证是否存在。
@Autowired
private SysUserService sysUserService;
@Autowired
private RedisTemplate<String,String> redisTemplate;
private static final String slat = "mszlu!@#";
@Override
public Result login(LoginParam loginParam) {
/**
* 1. 检查参数是否合法
* 2. 根据用户名和密码去user表中查询 是否存在
* 3. 如果不存在 登录失败
* 4. 如果存在 ,使用jwt 生成token 返回给前端
* 5. token放入redis当中,redis token:user信息 设置过期时间
* (登录认证的时候 先认证token字符串是否合法,去redis认证是否存在)
*/
String account = loginParam.getAccount();
String password = loginParam.getPassword();
if (StringUtils.isBlank(account) || StringUtils.isBlank(password)){
return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg());
}
password = DigestUtils.md5Hex(password + slat);
SysUser sysUser = sysUserService.findUser(account,password);
if (sysUser == null){
return Result.fail(ErrorCode.ACCOUNT_PWD_NOT_EXIST.getCode(),ErrorCode.ACCOUNT_PWD_NOT_EXIST.getMsg());
}
String token = JWTUtils.createToken(sysUser.getId());
redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS);
return Result.success(token);
}
@Override
public SysUser findUser(String account, String password) {
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUser::getAccount,account);
queryWrapper.eq(SysUser::getPassword,password);
queryWrapper.select(SysUser::getAccount,SysUser::getId,SysUser::getAvatar,SysUser::getNickname);
queryWrapper.last("limit 1");
return sysUserMapper.selectOne(queryWrapper);
}
#获取用户信息
前端:Authorization将String类型的token信息存在头部。
@GetMapping("currentUser")
public Result currentUser(@RequestHeader("Authorization") String token){
return sysUserService.findUserByToken(token);
}
#获取逻辑
Token解析
@Override
public SysUser checkToken(String token) {
if (StringUtils.isBlank(token)){
return null;
}
Map<String, Object> stringObjectMap = JWTUtils.checkToken(token);
if (stringObjectMap == null){
return null;
}
String userJson = redisTemplate.opsForValue().get("TOKEN_" + token);
if (StringUtils.isBlank(userJson)){
return null;
}
SysUser sysUser = JSON.parseObject(userJson, SysUser.class);
return sysUser;
}
后端会先将获取到的token进行解析,看解析结果是否存在。如果成功我们会获取到账号、ID、昵称等信息。在项目中,我们将这部分用户信息封装在了loginUserVo实体中。
@Override
public Result findUserByToken(String token) {
/**
* 1. token合法性校验
* 是否为空,解析是否成功 redis是否存在
* 2. 如果校验失败 返回错误
* 3. 如果成功,返回对应的结果 LoginUserVo
*/
SysUser sysUser = loginService.checkToken(token);
if (sysUser == null){
return Result.fail(ErrorCode.TOKEN_ERROR.getCode(),ErrorCode.TOKEN_ERROR.getMsg());
}
LoginUserVo loginUserVo = new LoginUserVo();
loginUserVo.setId(String.valueOf(sysUser.getId()));
loginUserVo.setNickname(sysUser.getNickname());
loginUserVo.setAvatar(sysUser.getAvatar());
loginUserVo.setAccount(sysUser.getAccount());
return Result.success(loginUserVo);
}