JWT

简介: JWT

JWT

JSON Web Token(JSON Web令牌)

是一个开放标准(rfc7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以JSON对象安全地传输信息。此信息可以验证和信任,因为它是数字签名的。jwt可以使用秘密〈使用HNAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。

通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密、签名等相关处理。

1. JWT作用

授权:一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。它的开销很小并且可以在不同的域中使用。如:单点登录。
信息交换:在各方之间安全地传输信息。JWT可进行签名(如使用公钥/私钥对),因此可确保发件人。由于签名是使用标头和有效负载计算的,因此还可验证内容是否被篡改

(1)传统的session认证

这种模式最大的问题是,没有分布式架构,无法支持横向扩展。如果使用一个服务器,该模式完全没有问题。但是,如果它是服务器群集或面向服务的跨域体系结构的话,则需要一个统一的session数据库库来保存会话数据实现共享,这样负载均衡下的每个服务器才可以正确的验证用户身份。

例如,举一个实际中常见的单点登陆的需求:站点A和站点B提供同一公司的相关服务。现在要求用户只需要登录其中一个网站,然后它就会自动登录到另一个网站。怎么做?

一种解决方案是听过持久化session数据,写入数据库或文件持久层等。收到请求后,验证服务从持久层请求数据。该解决方案的优点在于架构清晰,而缺点是架构修改比较费劲,整个服务的验证逻辑层都需要重写,工作量相对较大。而且由于依赖于持久层的数据库或者问题系统,会有单点风险,如果持久层失败,整个认证体系都会挂掉。

基于session认证所显露的问题

Session: 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。

扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。

CSRF: 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

(2) 基于token的鉴权机制

基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。

流程上是这样的:

  • 用户使用用户名密码来请求服务器
  • 服务器进行验证用户的信息
  • 服务器通过验证发送给用户一个token
  • 客户端存储token,并在每次请求时附送上这个token值
  • 服务端验证token值,并返回数据

这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *

通过客户端保存数据,而服务器根本不保存会话数据,每个请求都被发送回服务器。 JWT是这种解决方案的代表

JWT官网有一张图描述了JWT的认证过程:

2. JWT的原则

JWT的原则是在服务器身份验证之后,将生成一个JSON对象并将其发送回用户,如下所示。

{
"UserName": "Chongchong",
"Role": "Admin",
"Expire": "2018-08-08 20:15:56"
}

之后,当用户与服务器通信时,客户在请求中发回JSON对象。服务器仅依赖于这个JSON对象来标识用户。为了防止用户篡改数据,服务器将在生成对象时添加签名(有关详细信息,请参阅下文)。

服务器不保存任何会话数据,即服务器变为无状态,使其更容易扩展。

3. JWT的数据结构

典型的,一个JWT看起来如下图。

改对象为一个很长的字符串,字符之间通过"."分隔符分为三个子串。注意JWT对象为一个长字串,各字串之间也没有换行符,此处为了演示需要,我们特意分行并用不同颜色表示了。每一个子串表示了一个功能块,总共有以下三个部分:

JWT的三个部分如下。JWT头、有效载荷和签名,将它们写成一行如下。

3.1 JWT头

JWT头部分是一个描述JWT元数据的JSON对象,通常如下所示。

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

在上面的代码中,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。

最后,使用Base64 URL算法将上述JSON对象转换为字符串保存。

3.2 有效载荷

有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择。

iss:发行人

exp:到期时间

sub:主题

aud:用户

nbf:在此之前不可用

iat:发布时间

jti:JWT ID用于标识该JWT

除以上默认字段外,我们还可以自定义私有字段,如下例:

{
"sub": "1234567890",
"name": "wanglihong",
"admin": true
}

请注意,默认情况下JWT是未加密的,任何人都可以解读其内容,因此不要构建隐私信息字段,存放保密信息,以防止信息泄露。

JSON对象也使用Base64 URL算法转换为字符串保存

3.3签名哈希

签名哈希部分是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改。

首先,需要指定一个密码(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用标头中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名。

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),

secret)

在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用"."分隔,就构成整个JWT对象。

3.4 Base64URL算法

如前所述,JWT头和有效载荷序列化的算法都用到了Base64URL。该算法和常见Base64算法类似,稍有差别。

作为令牌的JWT可以放在URL中(例如api.example/?token=xxx)。 Base64中用的三个字符是"+","/"和"=",由于在URL中有特殊含义,因此Base64URL中对他们做了替换:"="去掉,"+"用"-"替换,"/"用"_"替换,这就是Base64URL算法,很简单把。

4. JWT的用法

客户端接收服务器返回的JWT,将其存储在Cookie或localStorage中。

此后,客户端将在与服务器交互中都会带JWT。如果将它存储在Cookie中,就可以自动发送,但是不会跨域,因此一般是将它放入HTTP请求的Header Authorization字段中。

Authorization: Bearer

当跨域时,也可以将JWT被放置于POST请求的数据主体中。

5. JWT问题和趋势

1、JWT默认不加密,但可以加密。生成原始令牌后,可以使用改令牌再次对其进行加密。

2、当JWT未加密方法是,一些私密数据无法通过JWT传输。

3、JWT不仅可用于认证,还可用于信息交换。善用JWT有助于减少服务器请求数据库的次数。

4、JWT的最大缺点是服务器不保存会话状态,所以在使用期间不可能取消令牌或更改令牌的权限。也就是说,一旦JWT签发,在有效期内将会一直有效。

5、JWT本身包含认证信息,因此一旦信息泄露,任何人都可以获得令牌的所有权限。为了减少盗用,JWT的有效期不宜设置太长。对于某些重要操作,用户在使用时应该每次都进行进行身份验证。

6、为了减少盗用和窃取,JWT不建议使用HTTP协议来传输代码,而是使用加密的HTTPS协议进行传输。

6. JWT与SpringBoot整合使用

6.1 pom.xml
<dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.10.3</version>
</dependency>
6.2 TokenUtil : 封装了通过JWT 创建token, 签名, 获得数据信息
public class TokenUtil {
    //token到期时间60s
    private static final long EXPIRE_TIME= 600*1000;
    //密钥盐
    private static final String TOKEN_SECRET="123456qwertyuiop789";
    /**
     * 创建一个token
     * @param user
     * @return
     */
    public static String sign(Customer user){
        System.out.println("sign customer:" + user);
        String token=null;
        try {
            Date expireAt=new Date(System.currentTimeMillis()+EXPIRE_TIME);
            token = JWT.create()
                    //发行人
                    .withIssuer("auth0")
                    //存放数据
                    .withClaim("custId",user.getCustId())
                    .withClaim("custName",user.getCustName())
                    .withClaim("custPassword",user.getCustPassword())
                    //过期时间
                    .withExpiresAt(expireAt)
                    .sign(Algorithm.HMAC256(TOKEN_SECRET));
        } catch (IllegalArgumentException| JWTCreationException je) {
        }
        return token;
    }
    /**
     * 对token进行验证
     * @param token
     * @return
     */
    public static Boolean verify(String token){
        try {
            //创建token验证器
            JWTVerifier jwtVerifier=JWT.require(Algorithm.HMAC256(TOKEN_SECRET)).withIssuer("auth0").build();
            DecodedJWT decodedJWT=jwtVerifier.verify(token);
            System.out.println("认证通过:");
            System.out.println("custId: " + TokenUtil.getUserId(token));
            System.out.println("custName: " + TokenUtil.getUserName(token));
            System.out.println("过期时间: " + decodedJWT.getExpiresAt());
        } catch (IllegalArgumentException | JWTVerificationException e) {
            e.printStackTrace();
            //抛出错误即为验证不通过
            return false;
        }
        return true;
    }
    /**
     * 获取用户名
     */
    public static String getUserName(String token){
        try{
            DecodedJWT jwt= JWT.decode(token);
            String username =  jwt.getClaim("custName").asString();
            System.out.println("用户名:" + username);
            return username;
        }catch (JWTDecodeException e)
        {
            e.printStackTrace();
            return null;
        }
    }
    public static Integer getUserId(String token){
        try{
            DecodedJWT jwt= JWT.decode(token);
            Integer userId  =  jwt.getClaim("custId").asInt();
            System.out.println("用户id:" + userId);
            return  userId ;
        }catch (JWTDecodeException e)
        {
            e.printStackTrace();
            return null;
        }
    }
}
6.3 用户验证拦截器  TokenInterceptor
@Component
public class TokenInterceptor  implements HandlerInterceptor {
// /cart  -----[拦截验证]-------controller 
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            //跨域请求会首先发一个option请求,直接返回正常状态并通过拦截器
            if(request.getMethod().equals("OPTIONS")){
                response.setStatus(HttpServletResponse.SC_OK);
                return true;
            }
            //获取到token
            String token = request.getHeader("token");
            if (token!=null){
                boolean result= TokenUtil.verify(token);
                if (result){
                    System.out.println("通过拦截器");
                    return true;
                }
            }
            try {
                JSONObject json=new JSONObject();
                json.put("msg","token verify fail");
                json.put("code","500");
                response.getWriter().append(json.toString());
                System.out.println("认证失败,未通过拦截器");
            } catch (Exception e) {
                return false;
            }
            return false;
        }
    }
URL 拦截配置类
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new JwtInterceptor())
//                进行拦截,一般登录不拦截,企业都拦截
                    .addPathPatterns("/customer/**")
                    .excludePathPatterns("/customer/login")
            ;
        }
    }
6.4 登录请求 controller
@Controller
@RequestMapping("/customer")
public class CustomerController {
    @Autowired
    private ICustomerService customerService;
    //登录
    @PostMapping("login")
    @ResponseBody
    public String login(Customer customer){
        ServerResponse response = customerService.login(customer);
        System.out.println("controller response:" + response);
        if(response.getCode() ==200)
            return "login success";
        return "login fail";
    }
6.5 service  :  登录核心业务
@Service
public class CustomerServiceImpl extends ServiceImpl<CustomerMapper, Customer> implements ICustomerService {
    @Autowired
    private CustomerMapper customerMapper;
    @Override
    public ServerResponse login(Customer customer) {
        //1. DB 验证用户名与密码
        QueryWrapper<Customer> wrapper =  new QueryWrapper<>();
        wrapper.eq("cust_name",customer.getCustName());
        wrapper.eq("cust_password",customer.getCustPassword());
        Customer loginCustomer = customerMapper.selectOne(wrapper);
        System.out.println("查询到的登录账户:" + loginCustomer);
        if(loginCustomer !=null) {
            //2. JWT创建token
            String token = TokenUtil.sign(loginCustomer);
            System.out.println("service token:" + token);
            return ServerResponse.success("登录成功", loginCustomer);
        }else
            return ServerResponse.fail("登录失败",loginCustomer);
    }
6.6 如在下订单业务中,想获得登录用户的信息
@PostMapping("submitOrder")
    public String submitOrder(){
        //1. 获得页面的订单与商品信息
        //2. 获得登录用户信息  custId , custName 
        String token = null;// 请求头中获得token ??????
        if(TokenUtil.verify(token)){
             Integer custId =   TokenUtil.getUserId(token);
            String custName =   TokenUtil.getUserName(token);
            System.out.println("获得了 custId:" + custId);
            System.out.println("获得了 custName:" + custName);
        }else{
            System.out.println("验证token无效");
        }
        return  "ok";
    }
目录
相关文章
|
3月前
|
存储 JSON 安全
JWT 还能这样的去理解嘛??
JWT (JSON Web Token) 是目前最流行的跨域认证解决方案,是一种基于 Token 的认证授权机制。从 JWT 的全称可以看出,JWT 本身也是 Token,一种规范化之后的。JWT 自身包含了身份验证所需要的所有信息,因此,我们的服务器不需要存储 Session 信息。这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。可以看出,。并且, 使用 JWT 认证可以攻击,因为 JWT 一般是存在在中,使用 JWT 进行身份验证的过程中是的。
197 1
|
2月前
|
存储 JSON 安全
JWT令牌详解
JWT令牌详解
|
1月前
|
存储 JSON 算法
JWT
【7月更文挑战第5天】
14 0
|
2月前
|
存储 JSON 算法
12.Jwt
12.Jwt
18 0
|
3月前
|
JSON 算法 数据库
JWT 是什么
JWT 是什么
|
9月前
|
前端开发
什么是JWT?深入理解JWT从原理到应用(下)
什么是JWT?深入理解JWT从原理到应用(下)
66 0
|
3月前
JWT令牌的使用
JWT令牌的使用
71 0
|
3月前
|
存储 JSON 算法
什么是JWT?
什么是JWT?
53 0
|
3月前
|
存储 JSON 算法
快速了解什么是jwt及如何使用jwt
快速了解什么是jwt及如何使用jwt
127 0
|
8月前
|
存储 JSON 安全
了解什么是JWT
了解什么是JWT
34 0