分布式项目登录——JWT技术

1.JWT介绍

1.1什么是JWT

      JWT是json web token缩写。它将用户信息加密到token里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证token的正确性,只要正确即通过验证。

1.2JWT的组成

       一个JWT其实就是一个字符串,由三部分组成:头部(header)、载荷(payload)、签名(signature),以“.”拼接

1.2.1 header

      头部用于描述关于该JWT的最基本的信息,header典型的由两部分组成:token的类型(“JWT”)和算法名称(比如:HMAC SHA256或者RSA等等)。

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

  头部指明了签名算法是hs256,用base64对这个json进行编码后就得到了JWT的第一部分

1.2.2 payload

  载荷是存放有效信息的地方,包含了声明(要求),声明有三种类型:

 (1)标准中注册的声明:(建议但是不强制使用)

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

(2)公共声明

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

(3)私有声明

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

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

1.2.3signature

签名是一个字符串,是由" header(Base64编码后)+‘.’+payload(Base64编码后) " 拼接的字符串通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

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

2.java的JJWT实现JWT

   JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(ApacheLicense,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。

2.1JJWT的快速入门

2.1.1导入依赖


io.jsonwebtoken
jjwt
0.6.0

2.1.2生成token

public class CreateJwt {
  public static void main(String[] args) {
    JwtBuilder builder =
        Jwts.builder() // 生成jwt,下面是载荷的内容
            .setId("111") // 当前登录用户的id
            .setSubject("皮卡丘") // 用户名
            .setIssuedAt(new Date()) // 签发时间
            .signWith(SignatureAlgorithm.HS256, "pikapika"); // 加密方式是hs256,盐是pikapika
    // 输出token
    System.out.println(builder.compact());
  }
}

执行完成后输出的结果如下:

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMTEiLCJzdWIiOiLnmq7ljaHkuJgiLCJpYXQiOjE1NDY0ODU4ODd9.v64lCobI5odEHY2Vl4h4kQh1cThzsU8MAs3gGM3nrms

2.1.3解析token

public class ParseJwt {
  public static void main(String[] args) {
    String token =
        "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMTEiLCJzdWIiOiLnmq7ljaHkuJgiLCJpYXQiOjE1NDY0ODU4ODd9."
            + "v64lCobI5odEHY2Vl4h4kQh1cThzsU8MAs3gGM3nrms";
    Claims claims = Jwts.parser().setSigningKey("pikapika").parseClaimsJws(token).getBody();
    // 获取具体的参数
    System.out.println("用户id:" + claims.getId());
    System.out.println("登录时间:" + claims.getIssuedAt());
    System.out.println(
        "登录时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(claims.getIssuedAt()));
    System.out.println("用户名:" + claims.getSubject());
  }
}

输出结果如下:

分布式项目登录——JWT技术_第1张图片

 把盐或者token写错,会出现相同的报错,所以在解析token的时候就会验证token

分布式项目登录——JWT技术_第2张图片

分布式项目登录——JWT技术_第3张图片

2.1.4 在载荷里添加自定义属性

取值

分布式项目登录——JWT技术_第4张图片

记住修改自定义添加的属性后,要修改token值

2.1.5设置过期时间

分布式项目登录——JWT技术_第5张图片

没过期前运行输出的结果:

分布式项目登录——JWT技术_第6张图片

时间超过过期时间再运行就报错

Exception in thread "main" io.jsonwebtoken.ExpiredJwtException: JWT expired at 2019-01-03T14:20:54+0800. Current time: 2019-01-03T14:21:12+0800
	at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:365)
	at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:458)
	at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:518)
	at com.tensquare.jjwt.ParseJwt.main(ParseJwt.java:26)

3.JWT在项目中的运用

在项目中加入依赖

3.1书写生成和解析JWT的工具类

package util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;

/**
 * Created by Administrator on 2018/4/11.
 */
@ConfigurationProperties("jwt.config") //要从配置文件jwt.config获取属性的值
public class JwtUtil {

    private String key ;//盐

    private long ttl ;//过期时间

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public long getTtl() {
        return ttl;
    }

    public void setTtl(long ttl) {
        this.ttl = ttl;
    }

    /**
     * 生成JWT
     *
     * @param id
     * @param subject
     * @return
     */
    public String createJWT(String id, String subject, String roles) {
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        JwtBuilder builder = Jwts.builder().setId(id)
                .setSubject(subject)
                .setIssuedAt(now)
                .signWith(SignatureAlgorithm.HS256, key).claim("roles", roles);
        if (ttl > 0) {
            builder.setExpiration( new Date( nowMillis + ttl));
        }
        return builder.compact();
    }
    /**
     * 解析JWT
     * @param jwtStr
     * @return
     */
    public Claims parseJWT(String jwtStr){
        return  Jwts.parser()
                .setSigningKey(key)
                .parseClaimsJws(jwtStr)
                .getBody();
    }
}

在要用到jwt的工程的配置文件里面添加

jwt:
  config:
    key: tensquare
    ttl: 3600000   #过期时间一小时

登录的方法: 

登录成功后要添加生成token令牌的方法

@PostMapping("/login")
  public Result login(@RequestBody Map map) {
    String loginname = map.get("loginname");
    String password = map.get("password");
    Admin admin = adminService.login(loginname, password);
    if (admin == null) {
      return new Result(false, StatusCode.LOGINERROR, "用户名或密码错误");
    }
    //如果登录成功就生成token令牌
    String jwt = jwtUtil.createJWT(admin.getId(), admin.getLoginname(), "admin");
    Map rmap=new HashMap<>();//前端需要什么信息给它返回什么信息
    rmap.put("token",jwt);
    rmap.put("role","admin");
    rmap.put("loginname",admin.getLoginname());
    return new Result(true, StatusCode.OK, "登录成功",rmap);
  }

 //假设一个业务场景,只有当前用户是admin角色时才能删除user

首先要注入两个类

分布式项目登录——JWT技术_第7张图片

另外要与前端商定好,token怎么携带,这里假设的是将其放在请求头设置的 Authorization属性里面,内容为Bearer+空格+token

分布式项目登录——JWT技术_第8张图片

删除用户user 

  public void deleteById(String id) {
    String authHeader = request.getHeader("Authorization");
    //首先需要判断该请求头是否存在
    if(authHeader==null|| "".equals(authHeader)){
      throw new RuntimeException("权限不足");
    }
    if(!authHeader.startsWith("Bearer ")){
      throw new RuntimeException("权限不足");
    }
    //获取token,然后解析
    String token  = authHeader.substring(7);
    try {
      Claims claims = jwtUtil.parseJWT(token);
      String role = (String) claims.get("roles");
      if(role==null || !role.equals("admin")){
        throw new RuntimeException("权限不足");
      }
    } catch (Exception e) {
      throw new RuntimeException("权限不足");
    }
    userDao.deleteById(id);
  }

只有按照要求的格式在header里面填写参数时,才能删除成功。

分布式项目登录——JWT技术_第9张图片

4.使用拦截器方式实现token 鉴权

如果每一个方法都去验证该用户是否登录是否有权限,代码就会很冗余,不利于维护,所以我们用拦截器来实现。

下面是自定义拦截器类,我们要在请求处理之前进行拦截


package com.tensquare.user;

import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import util.JwtUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/** ClassName: JwtInterceptor Description: Author:yqwang103 CreateDime:2019/1/3 16:45 */
// 自定义拦截器实现HandlerInterceptor接口或者继承HandlerInterceptorAdapter均可
@Component
public class JwtInterceptor implements HandlerInterceptor {
  @Autowired private JwtUtil jwtUtil;

  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {
    System.out.println("经过了拦截器。。。");
    // 不管怎样结果都是放行,具体能不能操作还是要在业务中去判断 ,现在做的只是解析token
    String header = request.getHeader("Authorization");
    if (header != null && header.startsWith("Bearer ")) {
      String token = header.substring(7);
      // 解析token
      try {
        Claims claims = jwtUtil.parseJWT(token);
        if (claims != null) {
          String role = (String) claims.get("roles");
          if ("admin".equals(role)) {
            request.setAttribute("admin_claims", claims); // 此时是管理员
          }
          if ("user".equals(role)) {
            request.setAttribute("user_claims", claims); // 此时是普通用户
          }
        }
      } catch (Exception e) {
        throw new RuntimeException("令牌有误");
      }
    }
    return true; // 返回true就是放行,返回false就是拦截
  }
}

 

分布式项目登录——JWT技术_第10张图片

 以前用spring的时候要在配置文件里面设置拦截路径,因为小鱼这个项目使用的springboot,所以要书写自定义配置类

@Configuration // 表明它是一个配置类
public class ApplicationConfig extends WebMvcConfigurationSupport {
  @Autowired private JwtInterceptor jwtInterceptor;

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry
        .addInterceptor(jwtInterceptor)
        .addPathPatterns("/**")    //所有的路径都拦截
        .excludePathPatterns("/login/**");//除了登录的路径不拦截
  }
}

对上面的删除代码进行修改:

分布式项目登录——JWT技术_第11张图片

没有header 

 分布式项目登录——JWT技术_第12张图片

有header并且是正确的header 

分布式项目登录——JWT技术_第13张图片 

分布式项目登录——JWT技术_第14张图片

你可能感兴趣的:(tec)