JWT 使用

  • 前端访问后台
  • a系统和b系统访问

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。

JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输

JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

JWT的构成

第一部分我们称它为头部(header),

第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),

第三部分是签证(signature).

header

jwt的头部承载两部分信息:

  • 声明类型,这里是jwt
  • 声明加密的算法 通常直接使用 HMAC SHA256

完整的头部就像下面这样的JSON:

{
  'typ': 'JWT',
  'alg': 'HS256'
}

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

playload

载荷就是存放有效信息的地方 这些有效信息包含三个部分

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明

标准中注册的声明

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

公共的声明
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

定义一个payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

然后将其进行base64加密,得到Jwt的第二部分。

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

signature

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

  eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRta

如何应用

一般是在请求头里加入Authorization,并加上Bearer标注:

fetch('api/user/1', {
  headers: {
    'Authorization': 'Bearer ' + token
  }
})

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IB6lV4WS-1689563766871)(http://qiniu.xiaotao.cloud/1821058-2e28fe6c997a60c9.png)]

img

优点

  • 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
  • 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
  • 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
  • 它不需要在服务端保存会话信息, 所以它易于应用的扩展

安全相关

  • 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
  • 保护好secret私钥,该私钥非常重要。
  • 如果可以,请使用https协议

使用

java 中 使用 JWT

<!--jwt 在java 中的使用-->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.4.0</version>
</dependency>

生成签名

/**
 *  生成签名
 */
@Test
public void t1(){
    Calendar instance = Calendar.getInstance();
    instance.add(Calendar.SECOND,100);   // 100秒后过期

    String token = JWT.create()
            .withClaim("userId",21)   
            .withClaim("userName","xiaohuihui")  // playload
            .withExpiresAt(instance.getTime())   // 指定令牌过期时间
            .sign(Algorithm.HMAC256("kirtuicxmnhlkeqwem$#%$^%^$#$"));  // signature  签名内容自定义

    System.out.println(token);
}

image-20220104140816590

验证签名

/**
     * 验证签名
     */
    @Test
    public void t2(){
        JWTVerifier build = JWT.require(Algorithm.HMAC256("kirtuicxmnhlkeqwem$#%$^%^$#$")).build();
        DecodedJWT verify = build.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6InhpYW9odWlodWkiLCJleHAiOjE2NDEyNzYwNzYsInVzZXJJZCI6MjF9.HhPEyOyQwamGAbZV0AEMMy24Rw7zKifpQxub4WfHpAU");   // 上面生成的字符串
        System.out.println(verify.getClaim("userName").asString());
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BB3mDiGA-1689563766873)(http://qiniu.xiaotao.cloud/image-20220104141233841.png)]

异常:

AlgorithmMismatchException: 算法不匹配异常
TokenExpiredException: 令牌过期时间

JWTUtil

public class JwtUtils {
    public static final String SIGN = "#%f4524df$^dsf23&**&^%$5dsf%^$fd245223sf#$";

    /**
     * 生成· token
     * @return
     */
    public static String getToken(Map<String,String> map){
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.DATE,7);   // 默认7天后过期
        // 创建 jwt Builder
        JWTCreator.Builder builder = JWT.create();
        // playload  使用 lambda 遍历 map
        map.forEach((k,v) -> builder.withClaim(k,v));

        String token = builder.withExpiresAt(instance.getTime())   // 指定令牌过期时间
                .sign(Algorithm.HMAC256(SIGN));  // signature  签名

        return token;
    }

    /**
     * 验证 token  只要不合法 就会抛出异常
     */
    public static void verify(String token){
        JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
    }

    /**
     * 获取 token 信息
     */
    public static DecodedJWT getDecodedJWT(String token){
        DecodedJWT verify = JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
        return verify;
    }
}

SpringBoot 整合 JWT

controller login

/**
     * 管理后台登录   JWT
     * @param username
     */
    @PostMapping(value = "/user/login")
    @ResponseBody
    public RestResponseBo doLogin(@RequestParam("loginName") String username,
                                  @RequestParam("password") String password,
                                  @RequestParam(required = false) String rember_me,
                                  HttpServletRequest request,
                                  HttpServletResponse response) {
        //读取缓存信息值:用于判断用户登录失败次数
        Integer error_count = cache.get("login_error_count");
        try {
            TUsers users = new TUsers();
            users.setUsername(username);
            users.setPassword(password);
            TUsers user = usersService.login(users);

            // JWT
            Map<String,String> payload = new HashMap<>();
            payload.put("userId",user.getUid()+"");
            payload.put("userName",user.getUsername());
            // 生成JWT 令牌
            String token = JwtUtils.getToken(payload);


            //将当前登录用户 存入session中
            request.getSession().setAttribute(WebConst.LOGIN_SESSION_KEY, user);
            if (StringUtils.isNotBlank(rember_me)) {
                //存入cook中 通过工具类
                TaleUtils.setCookie(response, user.getUid());
            }
            //登录信息 存入日志表中
            logService.insertLog(LogActions.LOGIN.getAction(), null, request.getRemoteAddr(), user.getUid());
        } catch (Exception e) {
            error_count = null == error_count ? 1 : error_count + 1;
            if (error_count > 3) {
                return RestResponseBo.fail("您输入密码已经错误超过3次,请10分钟后尝试");
            }
            //错误后存入 缓存中
            cache.set("login_error_count", error_count, 10 * 60);
            String msg = "登录失败";
            if (e instanceof TipException) {
                msg = e.getMessage();
            } else {
                LOGGER.error(msg, e);
            }
            return RestResponseBo.fail(msg);
        }
        return RestResponseBo.ok();
    }

后面需要保护的请求

 @RequestMapping("/user/test")
public Map<String,Object> test(String token){
    HashMap<String, Object> map = new HashMap<>();
    try {
        DecodedJWT verify = JwtUtils.getDecodedJWT(token);  // 验证令牌
        map.put("status",true);
        map.put("msg","请求成功");
    }catch (SignatureVerificationException e){
        e.printStackTrace();
        map.put("msg","无效签名");
    }catch (TokenExpiredException e){
        e.printStackTrace();
        map.put("msg","token 过期");
    }catch (AlgorithmMismatchException e){
        e.printStackTrace();
        map.put("msg","两次算法不一致");
    }catch (Exception e){
        map.put("msg","token 无效");
    }
    map.put("status",false);
    return map;
}

JWTInterceptor

/**
 * jwt 请求拦截处理
 */
public class JWTInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HashMap<String, Object> map = new HashMap<>();
        // 获取请求头中的令牌
        String token = request.getHeader("token");

        if(token == null){
            request.setAttribute("msg","你没有权限访问 请先登录 ");
            request.getRequestDispatcher("/login.html").forward(request,response);
            return false;
        }

        try {
            JwtUtils.getDecodedJWT(token);  // 验证令牌
            return true;  // 发行
        }catch (SignatureVerificationException e){
            e.printStackTrace();
            map.put("msg","无效签名");
        }catch (TokenExpiredException e){
            e.printStackTrace();
            map.put("msg","token 过期");
        }catch (AlgorithmMismatchException e){
            e.printStackTrace();
            map.put("msg","两次算法不一致");
        }catch (Exception e){
            map.put("msg","token 无效");
        }
        map.put("status",false);  // 设置状态
        // 将 map 转为 json 响应
        String json = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().print(json);
        return false;
    }
}

将拦截器注入到SpirngMVC

/**
     * 登录请求拦截转发
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JWTInterceptor())
                .addPathPatterns("/**")           //拦截的请求
                .excludePathPatterns("/login.html","/","/user/login","/css/**","/js/**","/img/**");  //不拦截的请求
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值