在我们实现登录的时候:
传统思路:
- 登录页面把用户名密码提交给服务器
- 服务器验证用户名密码是否正确,并返回校验结果给后端
- 如果密码正确。服务器端创建Session,通过Cookie把sessionId返回给浏览器
那么,在这种情况下存在问题:
session存储在服务器中,所以服务器重启,session就丢失了,用户刚登录完,就遇到了服务器重启,此时用户就需要重新登录(严重影响心情!)
解决方案:
- session存储在一个公用机械(或者缓存等)比如redis(服务端)
- 比较常见的就是token(客户端)----》常见!
token是一个带有一定信息的字符串,不能伪造(是客户端进行访问时候携带的)
token不是加密,主要是不能伪造!
那么,此时JWT令牌应运而生!!(当然,处理该问题的不止JWT令牌一个方式!只不过JWT令牌是开源而且免费的,比较适合学生study!)
令牌其实就是一个用户的身份标识,名称起的很高大上,其实本质就是一个字符串!
比如我们出行在外,会带自己的身份证,需要验证身份时,就掏出身份证,此时身份证就是一个令牌!身份证不能伪造,可以辨别真假!
当我们使用令牌技术来实现登录的时候:
- 根据用户名和密码,验证密码是否正确
- 如果密码正确,后端生成token,并返回给前端
- 后续访问时,携带token(通常放在Http请求的Header中),后端校验token的合法性
JWT组成:
JWT由三部分组成,每部分中间使用点(.)分隔,比如aaaa.bbb.ccc
- Header(头部):头部包括令牌类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)
- Payload(负载):负载部分是存放有效信息的地方,里面是一些自定义内容,比如:{"userId":"123","userName":"zhangsan"},也可以存在jwt提供的现场字段,比如exp(过期时间戳)等(此部分不建议存放敏感信息,因为此部分可以解码还原原始内容!)
- Signature(签名):此部分用于防止jwt内容被篡改,确保安全性!(防止被篡改,而不是防止被解析)
JWT之所以安全,就是因为最后的签名.jwt当中任何⼀个字符被篡改,整个令牌都会校验失败.
就好⽐我们的⾝份证,之所以能标识⼀个⼈的⾝份,是因为他不能被篡改,⽽不是因为内容加密.(任何⼈都可以看到⾝份证的信息,jwt也是)
用户登录
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/login")
public Result login(String userName, String password){
/**
* 1. 参数校验
* 2. 密码校验
* 3. 生成token, 并返回
*/
if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)){
return Result.fail("用户名或密码为空");
}
//获取数据库中的密码
UserInfo userInfo = userService.queryByName(userName);
if (userInfo==null || userInfo.getId()<0){
return Result.fail("用户不存在");
}
if (!password.equals(userInfo.getPassword())){
return Result.fail("账号或密码错误!");
}
// if (!SecurityUtils.verify(password, userInfo.getPassword())){
// return Result.fail("账号或密码错误!");
// }
//生成token, 并返回
Map<String, Object> claim = new HashMap<>();
claim.put("id", userInfo.getId());
claim.put("name", userInfo.getUserName());
String token = JwtUtils.genToken(claim);
return Result.success(token);
}
/**
* 获取登录用户的信息
*/
@RequestMapping("/getUserInfo")
public UserInfo getUserInfo(HttpServletRequest request){
//1. 从token中获取用户ID
//2. 根据用户ID, 获取用户信息
String token = request.getHeader("user_token");
Integer userId = JwtUtils.getUserIdFromToken(token);
if (userId==null){
return null;
}
return userService.queryById(userId);
}
}
JWT工具类:
/**
* Jwt工具类
*/
@Slf4j
public class JwtUtils {
//过期时间: 1小时
private static final long expiration = 60 * 60 * 1000;
private static final String secretString = "5CRMLhF7dQnOLCNjJw8dawYK2zTUxS4jDgUW2L99Tdo=";
private static final Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString));
public static String genToken(Map<String, Object> claim){
String token = Jwts.builder()
.setClaims(claim) //设置载荷信息
.setExpiration(new Date(System.currentTimeMillis()+expiration)) //设置过期时间
.signWith(key) //设置签名
.compact();
return token;
}
public static Claims parseToken(String token){
if (token==null){
return null;
}
JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();
Claims claims = null;
try {
claims = build.parseClaimsJws(token).getBody();
}catch (Exception e){
// e.printStackTrace();
log.error("解析token失败, token:"+token);
}
return claims;
}
public static Integer getUserIdFromToken(String token){
Claims claims = parseToken(token);
if (claims==null){
return null;
}
return (Integer) claims.get("id");
}
// public static void main(String[] args) {
// String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiemhhbmdzYW4iLCJpZCI6MSwiZXhwIjoxNzA1ODAyNzUyfQ.atKGO-q32Dy5PYfFZZgoogHwbkULivFR_7sXF1Dw-so";
// Claims claims = parseToken(token);
// System.out.println(claims);
// }
}
不完全代码,仅供参考!