JWT的使用

什么是JWT

JWT(Json Web Token),是一种工具,格式为XXXX.XXXX.XXXX的字符串,JWT以一种安全的方式在用户和服务器之间传递存放在JWT中的不敏感信息。

为什么要用JWT

设想这样一个场景,在我们登录一个网站之后,再把网页或者浏览器关闭,下一次打开网页的时候可能显示的还是登录的状态,不需要再次进行登录操作,通过JWT就可以实现这样一个用户认证的功能。当然使用Session可以实现这个功能,但是使用Session的同时也会增加服务器的存储压力,而JWT是将存储的压力分布到各个客户端机器上,从而减轻服务器的压力

Jwt的构成

JWT由3个子字符串组成,分别为Header,Payload以及Signature,结合JWT的格式即:Header.Payload.Signature。(Claim是描述Json的信息的一个Json,将Claim转码之后生成Payload)

Header

Header是由以下这个格式的Json通过Base64编码(编码不是加密,是可以通过反编码的方式获取到这个原来的Json,所以JWT中存放的一般是不敏感的信息)生成的字符串,Header中存放的内容是说明编码对象是一个JWT以及使用“SHA-256”的算法进行加密(加密用于生成Signature)


"typ":"JWT", 
"alg":"HS256" 
}  这个内容是固定的

Claim

Claim是一个Json,Claim中存放的内容是JWT自身的标准属性,所有的标准属性都是可选的,可以自行添加,比如:JWT的签发者、JWT的接收者、JWT的持续时间等;同时Claim中也可以存放一些自定义的属性,这个自定义的属性就是在用户认证中用于标明用户身份的一个属性,比如用户存放在数据库中的id,为了安全起见,一般不会将用户名及密码这类敏感的信息存放在Claim中。将Claim通过Base64转码之后生成的一串字符串称作Payload。

 plyload

      载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

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

     标注中注册的声明(建议不强制使用)

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

 公共的声明:

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

     私有的声明

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

     定义一个payload

 
  1. {

  2. "sub": "1234567890",

  3. "name": "John Doe",

  4. "admin": true

  5. }

    然后将其base64加密,得到jwt的一部分

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

Signature

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

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

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

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

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

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

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

注意:secret是保存在服务器端的,jwt的签发也是在服务端的,secret就是用来进行jwt的签发和jwt的验证,所以它就是你服务端的私钥,在任何场景都不应该流露出去,一旦客户端得知这个secret,那就意味着客户端可以自我签发jwt了

 应用 

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

fetch('api/user/1', {

headers: {

'Authorization': 'Bearer ' + token

}

})

优点:

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

       安全相关

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

工具类:这里我们的业务场景是将用户的userid存入到jwt中加密,后期会从jwt验证成功后取出userid查询查询用户信息。

package training.guojiang.coterie.util;


import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * jwt的工具类
 */
public class JwtTokenUtil {


    private static final String jwtToken="secret";

    /**
     *生成一个jwt的token
     * @return
     */
    public static String createToken(Long userId){
        Map<String,Object> claims=new HashMap<>();
        claims.put("userId",userId);
        JwtBuilder builder = Jwts.builder()
                .setIssuer("autho")
                .signWith(SignatureAlgorithm.HS256,jwtToken)
                .setClaims(claims)//body数据,要唯一,自行设置
                .setIssuedAt(new Date())//设置签发时间
                .setExpiration(new Date(System.currentTimeMillis()+24*60*60*60*1000));//有效时间为一天
        String token=builder.compact();
        return token;
    }

    /**
     *解析body
     * @return
     */
    public static Map<String,Object> checkToken(String token){
        try{
            Jwt parse = Jwts.parser().setSigningKey(jwtToken).parse(token);
            return (Map<String, Object>) parse.getBody();
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }


}

业务使用场景:

package training.guojiang.coterie.service.impl;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import training.guojiang.coterie.dto.SmsLoginDto;
import training.guojiang.coterie.model.User;
import training.guojiang.coterie.model.UserBind;
import training.guojiang.coterie.repository.UserBindRepository;
import training.guojiang.coterie.repository.UserRepository;
import training.guojiang.coterie.service.SmsLoginService;
import training.guojiang.coterie.util.JwtTokenUtil;
import training.guojiang.coterie.vo.SmsLogin;

import javax.transaction.Transactional;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 手机短信验证码登录实现类
 */

@Transactional
@Service
public class SmsLoginServiceImpl implements SmsLoginService {

    @Autowired
    private UserBindRepository userBindRepository;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private RedisTemplate redisTemplate;

    


    /**
     * 手机短信验证码
     * @param smsLoginDto
     * @return
     */
    @Override
    public SmsLogin smsLogin(SmsLoginDto smsLoginDto) {
        //获取手机号:
        String mobile=smsLoginDto.getMobile();
        //从前端获取code1:
        String code1 = smsLoginDto.getCode();
        //获取openid:
        String openid = smsLoginDto.getOpen_id();

        //1:从redis中获取验证码:
        String code =(String) redisTemplate.boundValueOps(mobile).get();
        //2:如果res中不存在验证码code,返回错误提示
        if (code==null){
           return SmsLogin.builder().status(false).code(400).message("验证码已经失效从新获取").build();
        }
        if(!code.equals(code1)){
           return SmsLogin.builder().status(false).code(400).message("验证码错误").build();
        }

        //创建user表
        User user = User.builder().mobile(mobile).build();
        userRepository.save(user);
        Long id = user.getId();

        //根据openid查询userbind表
        UserBind userBind = userBindRepository.findByOpenId(openid);
        userBind.setUserId(id);
        //更新绑定表
        userBindRepository.save(userBind);

        //生成jwt access_token
        String access_token = JwtTokenUtil.createToken(id);
        //封装返回的数据:
        Map<String,String> map=new HashMap<>();
        map.put("token_type","Bearer");
        map.put("access_token",access_token);
        return  SmsLogin.builder().status(true).code(200).data(map).build();
    }



}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值