目录
接口说明
编码实现
JWT依赖包
JWTUtils工具类
Controller控制层
Service业务逻辑层
Vo对象
前端测试
JWT技术
Header
Payload
Signature
接口url:/login
请求方式:POST
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
account | string | 账号 |
password | string | 密码 |
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": "token"
}
本项目使用JWT生成token,并将token作为key在redis中保存用户信息,目的是:
有可能在用户退出登录前token被盗取,加入redis中再次确认
加入redis你退出登录的时候也清除redis
io.jsonwebtoken
jjwt
0.9.1
package com.huing.blog.utils;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* jwt 可以生成 一个加密的token,做为用户登录的令牌,当用户登录成功之后,发放给客户端。
*
* 请求需要登录的资源或者接口的时候,将token携带,后端验证token是否合法。
*
* jwt 有三部分组成:A.B.C
*
* A:Header,{“type”:“JWT”,“alg”:“HS256”} 固定
*
* B:playload,存放信息,比如,用户id,过期时间等等,可以被解密,不能存放敏感信息
*
* C: 签证,A和B加上秘钥 加密而成,只要秘钥不丢失,可以认为是安全的。
*
* jwt 验证,主要就是验证C部分 是否合法。
*
* @author huing
* @create 2022-07-04 15:53
*/
public class JWTUtils {
/**
* 密钥
*/
private static final String jwtToken = "123456Huing!@#$$";
public static String createToken(Long userId) {
/**
* B部分
*/
Map claims = new HashMap<>();
claims.put("userId", userId);
JwtBuilder jwtBuilder = Jwts.builder()
.signWith(SignatureAlgorithm.HS256, jwtToken) // 签发算法,秘钥为jwtToken A部分
.setClaims(claims) // body数据,要唯一,自行设置 B部分
.setIssuedAt(new Date()) // 设置签发时间
.setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000));// 一天的有效时间
String token = jwtBuilder.compact();
return token;
}
public static Map checkToken(String token) {
try {
Jwt parse = Jwts.parser().setSigningKey(jwtToken).parse(token);
return (Map) parse.getBody();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
package com.huing.blog.controller;
import com.huing.blog.service.LoginService;
import com.huing.blog.vo.Result;
import com.huing.blog.vo.params.LoginParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author huing
* @create 2022-07-04 15:27
*/
@RestController
@RequestMapping("login")
public class LoginController {
@Autowired
private LoginService loginService;
/**
* JWT登录
*
* @param loginParam
* @return
*/
@PostMapping
public Result login(@RequestBody LoginParam loginParam){
return loginService.login(loginParam);
}
}
LoginService接口:
package com.huing.blog.service;
import com.huing.blog.vo.Result;
import com.huing.blog.vo.params.LoginParam;
/**
* @author huing
* @create 2022-07-04 15:29
*/
public interface LoginService {
/**
* JWT登录
*
* @param loginParam
*/
Result login(LoginParam loginParam);
}
LoginServiceImpl实现类:
package com.huing.blog.service.impl;
import com.alibaba.fastjson.JSON;
import com.huing.blog.dao.mapper.SysUserMapper;
import com.huing.blog.dao.pojo.SysUser;
import com.huing.blog.service.LoginService;
import com.huing.blog.service.SysUserService;
import com.huing.blog.utils.JWTUtils;
import com.huing.blog.vo.ErrorCode;
import com.huing.blog.vo.Result;
import com.huing.blog.vo.params.LoginParam;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* @author huing
* @create 2022-07-04 15:29
*/
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private SysUserService sysUserService;
@Autowired
private RedisTemplate redisTemplate;
@Value("${user.pwd.salt}")
private String salt;
@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());
}
String newPwd = DigestUtils.md5Hex(password + salt);
SysUser user = sysUserService.findUser(account, newPwd);
if (user == null) {
return Result.fail(ErrorCode.ACCOUNT_PWD_NOT_EXIST.getCode(), ErrorCode.ACCOUNT_PWD_NOT_EXIST.getMsg());
}
// 使用JWT 生成token
String token = JWTUtils.createToken(user.getId());
//有可能在你退出登录前token被盗取,加入redis你退出登录的时候也清除redis
//token放入redis中,过期时间是一天
redisTemplate.opsForValue().set("TOKEN_" + token, JSON.toJSONString(user),1, TimeUnit.DAYS);
return Result.success(token);
}
}
sysUserService.findUser(account, newPwd)接口:
/**
* 根据用户名密码查询用户数据
* @param account
* @param newPwd
* @return
*/
SysUser findUser(String account, String newPwd);
sysUserService.findUser(account, newPwd)实现类:
@Override
public SysUser findUser(String account, String newPwd) {
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUser::getAccount,account);
queryWrapper.eq(SysUser::getPassword,newPwd);
queryWrapper.select(SysUser::getAccount,SysUser::getId,SysUser::getAvatar,SysUser::getNickname);
queryWrapper.last("limit 1");
SysUser sysUser = sysUserMapper.selectOne(queryWrapper);
return sysUser;
}
application.properties
# spring读取配置文件的数据:@Value("${tag.hot.limit}")
#设置tag最热标签个数
tag.hot.limit=6
#设置Article最热文章个数
article.hotAndNew.limit=6
#设置pwd密码加密盐
user.pwd.salt=huing!@#
# redis配置
spring.redis.host=huing100
spring.redis.port=6379
LoginParam:
package com.huing.blog.vo.params;
import lombok.Data;
/**
* @author huing
* @create 2022-07-04 15:33
*/
@Data
public class LoginParam {
private String account;
private String password;
private String nickname;
}
ErrorCode:
package com.huing.blog.vo;
/**
* @author huing
* @create 2022-07-04 15:40
*/
public enum ErrorCode {
PARAMS_ERROR(10001,"参数有误"),
ACCOUNT_PWD_NOT_EXIST(10002,"用户名或密码不存在"),
TOKEN_ERROR(10003,"token不合法"),
ACCOUNT_EXIST(10004,"账号已存在"),
NO_PERMISSION(70001,"无访问权限"),
SESSION_TIME_OUT(90001,"会话超时"),
NO_LOGIN(90002,"未登录");
private int code;
private String msg;
ErrorCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
使用postman工具:
JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个Jwt token,并且这个Jwt token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。
JWT由3部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.
进行连接形成最终传输的字符串
{“type”:“JWT”,“alg”:“HS256”} 固定,JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型。
有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择:
存放信息,比如,用户id,过期时间等等,可以被解密,不能存放敏感信息
签名哈希部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密钥(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。
只要秘钥不丢失,可以认为是安全的。
jwt 验证,主要就是验证C部分 是否合法。
jwt 生成 一个加密的token,做为用户登录的令牌,当用户登录成功之后,发放给客户端。
请求需要登录的资源或者接口的时候,将token携带,后端验证token是否合法。