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地址是否匹配
相关推荐
栗子叶3 分钟前
Java对象创建的过程
java·开发语言·jvm
勇哥java实战分享8 分钟前
短信平台 Pro 版本 ,比开源版本更强大
后端
学历真的很重要13 分钟前
LangChain V1.0 Context Engineering(上下文工程)详细指南
人工智能·后端·学习·语言模型·面试·职场和发展·langchain
有一个好名字13 分钟前
力扣-从字符串中移除星号
java·算法·leetcode
计算机毕设VX:Fegn089516 分钟前
计算机毕业设计|基于springboot + vue二手家电管理系统(源码+数据库+文档)
vue.js·spring boot·后端·课程设计
zfj32123 分钟前
CyclicBarrier、CountDownLatch、Semaphore 各自的作用和用法区别
java·开发语言·countdownlatch·semaphore·cyclicbarrier
2501_9167665428 分钟前
【JVM】类的加载机制
java·jvm
Sag_ever29 分钟前
Java数组详解
java
张np30 分钟前
java基础-ConcurrentHashMap
java·开发语言
上进小菜猪32 分钟前
基于 YOLOv8 的智能杂草检测识别实战 [目标检测完整源码]
后端