JWT Token 校验登录

JWT & token

1. 什么是token认证?

基于token的用户认证是一种服务端无状态 的认证方式,所谓服务端无状态指的token本身包含登录用户所有的相关数据,而客户端在认证后的每次请求都会携带token,因此服务器端无需存放token数据。

当用户认证后,服务端生成一个token发给客户端,客户端可以放到 cookie 或 localStorage 等存储中,每次请求时带上 token,服务端收到token通过验证后即可确认用户身份。

2. 什么是JWT?

JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简洁的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。

3. JWT令牌结构

JWT令牌由Header、Payload、Signature三部分组成,每部分中间使用点(.)分隔,比如:xxxxx.yyyyy.zzzzz

头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC、SHA256或RSA)。

// header 内容

{

"alg": "HS256",

"typ": "JWT"

}

// 将上边的内容使用Base64Url编码,得到一个字符串就是JWT令牌的第一部分。

base64UrlEncode(header)

Payload

第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比 如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。 此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。

// payload 内容

{

"sub": "1234567890",

"name": "456",

"admin": true

}

// 最后将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。

base64UrlEncode(payload)

Signature

第三部分是签名,此部分用于防止jwt内容被篡改。 这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明 签名算法进行签名。

// signature 内容

HMACSHA256(

base64UrlEncode(header) + "." +

base64UrlEncode(payload),

secret)

// 签名所使用的密钥

secret

token实现单点登录

JWT 生成及校验 Token

jwt 工具类

java 复制代码
public class AppJwtUtil {

  // 设置token过期时间30分钟
  private static final long EXPIRE_TIME = 30 * 60 * 1000; // 30分钟
  // 加密KEY
  private static final String TOKEN_ENCRY_KEY = "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY";

  // 生产ID
  public static String getToken(Long id) {
    Map<String, Object> claimMaps = new HashMap<>();
    claimMaps.put("id", id);
    long currentTime = System.currentTimeMillis();
    return Jwts.builder()
            .setId(UUID.randomUUID().toString())
            .setIssuedAt(new Date(currentTime)) // 签发时间
            .setSubject("system") // 说明
            .setIssuer("heima") // 签发者信息
            .setAudience("app") // 接收用户
            .compressWith(CompressionCodecs.GZIP) // 数据压缩方式
            .signWith(SignatureAlgorithm.HS512, TOKEN_ENCRY_KEY) // 加密方式
            .setExpiration(new Date(currentTime + EXPIRE_TIME)) // 过期时间戳
            .addClaims(claimMaps) // cla信息
            .compact();
  }

  /**
   * 获取payload body信息
   *
   * @param token
   * @return
   */
  public static Claims getClaimsBody(String token) {
    try {
      Jws<Claims> jwt = Jwts.parser().setSigningKey(TOKEN_ENCRY_KEY).parseClaimsJws(token);
      return jwt.getBody();
    } catch (Exception e) {
      return null;
    }
  }

  /**
   * 获取hearder body信息
   *
   * @param token
   * @return
   */
  public static JwsHeader getHeaderBody(String token) {
    Jws<Claims> jwt = Jwts.parser().setSigningKey(TOKEN_ENCRY_KEY).parseClaimsJws(token);
    return jwt.getHeader();
  }

  /**
   * 是否过期
   *
   * @param claims
   * @return 1:有效,0:无效
   */
  public static int verifyToken(Claims claims) {
    if (claims == null) {
      return 0;
    }
    // 当前时间在有效期范围内
    if(new Date().before(claims.getExpiration())){
      return 1;
    }
    return 0;
  }

}

后端使用token校验登录

service层生成token代码

java 复制代码
@Service
public class WmUserServiceImpl extends ServiceImpl<WmUserMapper, WmUser> implements WmUserService {

    @Override
    public ResponseResult login(WmLoginDto dto) {
        //1.检查参数
        if(StringUtils.isBlank(dto.getName()) || StringUtils.isBlank(dto.getPassword())){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID,"用户名或密码为空");
        }

        //2.查询用户
        WmUser wmUser = getOne(Wrappers.<WmUser>lambdaQuery().eq(WmUser::getName, dto.getName()));
        if(wmUser == null){
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
        }

        //3.比对密码
        String loginPwd = dto.getPassword(); //明文密码
        String dbPwd = wmUser.getPassword(); //密文密码

        boolean result = BCrypt.checkpw(loginPwd, dbPwd);

        if(result){
            //4.返回数据  jwt
            Map<String,Object> map  = new HashMap<>();
            map.put("token", AppJwtUtil.getToken(wmUser.getId().longValue()));
            wmUser.setSalt("");
            wmUser.setPassword("");
            map.put("user",wmUser);
            return ResponseResult.okResult(map);

        }else {
            return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
        }
    }
}

拦截器校验token代码

java 复制代码
@Component
@Slf4j
public class AuthorizeFilter implements Ordered, GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取request和response对象
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        //2.判断是否是登录接口,是直接放行
        String path = request.getURI().getPath();
        if(path.equals("/user/api/v1/login/login_auth/")){
            return chain.filter(exchange);
        }

        //3.非登录接口,获取token
        String token = request.getHeaders().getFirst("token");


        //4.判断token是否存在,不存在返回401(无权访问)
        if(StringUtils.isBlank(token)){
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }


        //5.判断token是否有效,无效返回401(无权访问)
        Claims claimsBody = AppJwtUtil.getClaimsBody(token);
        try {

            int i = AppJwtUtil.verifyToken(claimsBody);
            if(i==0){
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }
        } catch (Exception e) {
            e.printStackTrace();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        //从token载荷里获取用户ID
        String userId = String.valueOf(claimsBody.get("id"));
        //将用户ID设置到请求头
        request.mutate().header("userId",userId);


        //6.放行
        return chain.filter(exchange);
    }

    /**
     * 优先级设置  值越小  优先级越高
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

防止token伪造

  1. 如上代码所示,生成 token 时保存userId,再次请求时比对请求携带的 uerId 和 token 体中解析出来的 userId 是否匹配
  2. 生成 token 时保存请求ip地址,再次请求时比对当前请求ip地址和 token 中解析出来的ip地址是否匹配
相关推荐
李白的天不白1 天前
SmartAdmin(基于 Spring Boot 框架)中配置跨域请求 VUE3 设置请求头
java·前端
橙子进阶之路1 天前
Java线程(CompletableFuture)
java·开发语言
鹅城剑仙1 天前
Java CompletableFuture 异步编程完全指南
java
2601_961875241 天前
法考备考计划表|学习计划|资料已整理
java·开发语言·学习·eclipse·tomcat·c#·hibernate
重生之我是Java开发战士1 天前
【Java SE】多线程(三):单例模式,阻塞队列,线程池与定时器
java·javascript·单例模式
AI人工智能+电脑小能手1 天前
【大白话说Java面试题 第115题】【并发篇】第15题:说一下悲观锁和乐观锁的区别?
java·开发语言·面试
沪漂阿龙1 天前
LangChain 系列之Agent:从固定流程到模型自主决策
服务器·数据库·langchain
心之伊始1 天前
Spring Boot Actuator + Micrometer 实战:自定义业务指标并接入 Prometheus 观测接口耗时
java·spring boot·prometheus·actuator·micrometer
Full Stack Developme1 天前
Spring Integration 教程
java·后端·spring
爱勇宝1 天前
AI 时代,前端工程师的话语权正在下降?
前端·后端