JJWT使用笔记(一)—— JWT token的生成

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JJWT使用笔记(一)—— JWT token的生成相关的知识,希望对你有一定的参考价值。

参考技术A

一个JWT由三部分组成:
Header(头部) —— base64编码的Json字符串
Payload(载荷) —— base64编码的Json字符串
Signature(签名)—— 使用指定算法,通过Header和Payload加盐计算的字符串

各部分以" . "分割,如:

直接通过base64解码可获得

Header:

Payload:

本文基于:

Jwts.builder() 返回了一个 DefaultJwtBuilder()

DefaultJwtBuilder属性

DefaultJwtBuilder包含了一些Header和Payload的一些常用设置方法

如果即不设置签名,也不进行压缩,header是不是就应该没有了呢?
不是。即时不进行签名,alg也应该存在,不然对其进行解析会出错。
在生成jwt的时候,如果不设置签名,那么header中的alg应该为none。jjwt中compact()方法实现如下:

还提供了 JWT标准 7个保留声明(Reserved claims)的设置方法,7个声明都是可选的,也就是说可以不用设置。

Payload 举例:

当然也可以在Payload中添加一些自定义的属性claims键值对

jjwt实现的 DefaultJwtSigner 提供了一个带工厂参数的构造方法。并将jjwt实现的 DefaultSignerFactory
静态实例传入,根据不同的签名算法创建对应的签名器进行签名。

springboot集成jjwt

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。和?Cookie-Session 的模式不同,JSON Web Token(JWT)使用 Token 替换了 SessionId 的资源访问和状态的保持。

jwt是什么?

JWTs是JSON对象的编码表示。JSON对象由零或多个名称/值对组成,其中名称为字符串,值为任意JSON值。JWT有助于在clear(例如在URL中)发送这样的信息,可以被信任为不可读(即加密的)、不可修改的(即签名)和URL – safe(即Base64编码的)。

jwt的组成

  • Header: 标题包含了令牌的元数据,并且在最小包含签名和/或加密算法的类型
  • Claims: Claims包含您想要签署的任何信息
  • JSON Web Signature (JWS): 在header中指定的使用该算法的数字签名和声明

jwt 的认证过程

技术分享图片

 

  1. 用户登录系统;
  2. 服务端验证,将认证信息通过指定的算法(例如HS256)进行加密,例如对用户名和用户所属角色进行加密,加密私钥是保存在服务器端的,将加密后的结果发送给客户端,加密的字符串格式为三个”.” 分隔的字符串 Token,分别对应头部、载荷与签名,头部和载荷都可以通过 base64 解码出来,签名部分不可以;
  3. 客户端拿到返回的 Token,存储到 local storage 或本地数据库;
  4. 下次客户端再次发起请求,将 Token 附加到 header 中;
  5. 服务端获取 header 中的 Token ,通过相同的算法对 Token 中的用户名和所属角色进行相同的加密验证,如果验证结果相同,则说明这个请求是正常的,没有被篡改。

JWT 安全性

  • 有很多库可以帮助您创建和验证JWT,但是当使用JWT时,仍然可以做一些事情来限制您的安全风险。在您信任JWT中的任何信息之前,请始终验证签名。这应该是给定的。换句话说,如果您正在传递一个秘密签名密钥到验证签名的方法,并且签名算法被设置为“none”,那么它应该失败验证。
  • 确保签名的秘密签名,用于计算和验证签名。秘密签名密钥只能由发行者和消费者访问,不能在这两方之外访问。
  • 不要在JWT中包含任何敏感数据。这些令牌通常是用来防止操作(未加密)的,因此索赔中的数据可以很容易地解码和读取。
  • 如果您担心重播攻击,包括一个nonce(jti索赔)、过期时间(exp索赔)和创建时间(iat索赔)。这些在JWT规范中定义得很好。

jwt的优点

  • 使用 json 作为数据传输,有广泛的通用型,并且体积小,便于传输;
  • 不需要在服务器端保存相关信息;
  • jwt 载荷部分可以存储业务相关的信息(非敏感的),例如用户信息、角色等;

JJWT

 

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

  • JJWT的目标是最容易使用和理解用于在JVM上创建和验证JSON Web令牌(JWTs)的库。
  • JJWT是基于JWT、JWS、JWE、JWK和JWA RFC规范的Java实现。
  • JJWT还添加了一些不属于规范的便利扩展,比如JWT压缩和索赔强制。

简单介绍了一下,下面看一下怎么集成到Springboot中

第一步,引入pom依赖

<!-- jjwt -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

第二步,添加几个文件如下

RedisKey

package com.example.demo.filter;


//redis 前缀类
public interface RedisKey {
    String LOGIN_KEY_USER_ID = "login_key_user_id";//登陆key id前缀
    String LOGIN_KEY_USER_TOKEN = "login_key_user_token";//登陆key token前缀
    
    String LOGIN_KEY_PHONE = "login_key_user_phone";//登陆key 手机前缀
    String PRE_PHONE_NUM="pre_phone_num";//前一次验证码前缀
    
    
    String BACK_LOGIN_KEY_USER_ID = "back_login_key_user_id";//登陆key id前缀

}

MessageEnum

package com.example.demo.common.enums;

public enum MessageEnum {
    //令牌状态
    TOKEN_STATUS_ERROR(4023, "token有误"), TOKEN_NOT_EXIST(4024, "token有误"),
    TOKEN_IS_NULL(4025, "token为空"), TOKEN_IS_INVALID(4026, "token已经失效"),UID_IS_NULL(4027, "uid参数不能为空"),PLEASE_LOGIN(4028, "请登录后再操作"),
    YOU_CANT_DO_THIS(4030, "您无权操作#You have no right to operate"),
   ;

    private String message;
    private int code;

    MessageEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }
}

JwtAuthenticationFilter

package com.example.demo.filter;

import com.example.demo.common.enums.MessageEnum;
import com.example.demo.service.JedisService;
import org.springframework.stereotype.Service;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.Map;

@Service
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private PathMatcher PATH_MATCHER = new AntPathMatcher();
    private JwtUtil jwtUtil = null;
//    private UserLogMapper userLogMapper = null;
    private JedisService jedisService = null;
    /** 需要token认证的数据的前台路径 */
    private String[] protectUrlPatterns = {
            //地址相关
            "/address/addAddress","/address/editAddress","/address/getAddressDetail","/address/getUserDefaultAddress","/address/getAddressListByPage",
            "/address/setAddressToDefault","/address/deleteAddress","/address/wxinsert",
            //购物车模块
            "/cart/addToCart","/cart/updateCartGoodsNum","/cart/getCartByPage","/cart/deleteCartBatch",
            //用户模块
            "/user/logOut","/user/getUserInfo","/user/getPersonalCenterInfo","/user/getHistoryInviteList","/user/getOrderDetailList","/user/getMyIncomeList",
            //用户反馈模块
            "/userQuestion/addQuestion","/userQuestionManage/getQuestionListByPage",
            // 订单模块
            "/orders/orderList","/orders/orderView","/orders/confirmOrder","/orders/createOrder","/orders/queryBankInfo","/orders/cancelOrder","/orders/cancelRefund",
            "/orders/viewOrderLogistics","/orders/confirmReceive",
    };
    /** 需要token认证的后台管理数据的路径 */
    private String[] sellerProtectUrlPatterns = {
            //运费模版户模块
            "/freightTemplate/addTemplate","/freightTemplate/deleteTemplate","/freightTemplate/updateTemplate","/freightTemplate/getTemplateListByPage","/freightTemplate/getTemplateList",
            //后台运营账户相关
            "/seller/addSeller","/seller/deleteSeller","/seller/updateSeller","/seller/getSellerListByPage",
            // 订单模块
            "/manage/orders_manage/orderList","/manage/orders_manage/orderView","/manage/orders_manage/selledList",
            "/manage/orders_manage/finishRefund","/manage/orders_manage/refundFailure","/manage/orders_manage/orderDetail","/manage/orders_manage/sendGoods","/manage/orders_manage/viewExpress",
            //bannner 模块
            "/banner_manage/listBanner","/banner_manage/listSplashScreen","/banner_manage/insertBanner","/banner_manage/insertSplashScreen","/banner_manage/viewBannerByid",
            "/banner_manage/updateBanner","/banner_manage/changeBannerSort","/banner_manage/deleteBanner","/banner_manage/batchDeleteBanner",
            //商品模块
            "/product_manage/listCategoryManger","/product_manage/insertProduct","/product_manage/updateProduct","/product_manage/deleteProduct","/product_manage/listCommission",
            "/product_manage/updateCommission","/product_manage/viewProductforManager","/product_manage/changeProductsStatus","/product_manage/ImageUpload",
            //分类模块
            "/productsCategory_manage/listCategoryManger","/productsCategory_manage/insertCategory","/productsCategory_manage/viewCategery","/productsCategory_manage/updateCategory","/productsCategory_manage/deleteCategory",
            "/productsCategory_manage/changSort","/productsCategory_manage/changProductsSort","/productsCategory_manage/listProduct","/productsCategory_manage/listAllCategory","/productsCategory_manage/listAllProducts",
            "/productsCategory_manage/changAllProductsSort","/productsCategory_manage/deleteProductsForCategory",
            //运营分类模块
            "/productsOperate_manage/listOperate","/productsOperate_manage/viewOperate","/productsOperate_manage/updateOperate",
            //运营分类商品模块
            "/productsOperateSort_manage/productsList","/productsOperateSort_manage/deleteProducts","/productsOperateSort_manage/productsSortChange","/productsOperateSort_manage/listProductSelect",
            "/productsOperateSort_manage/insetProducts",
    };
    

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        if(isProtectedUrl(request)||isSellerProtectedUrl(request)) {
            jedisService = (JedisService) SpringContextUtil.getBean("jedisServerImpl");
            jwtUtil = (JwtUtil) SpringContextUtil.getBean("jwtUtil");
//            userLogMapper = (UserLogMapper) SpringContextUtil.getBean("userLogMapper");
            Map<String, Object> claims = jwtUtil.validateTokenAndGetClaims(request);
            String type = (String) claims.get("type");
            String uid = (String) claims.get("uid");
            if(uid==null) {
                response.sendError(MessageEnum.TOKEN_NOT_EXIST.getCode(), MessageEnum.TOKEN_NOT_EXIST.getMessage());
                return;
            }
            if(type==null||"".equals(type)||(!"fingo_client".equals(type)&&!"fingo_server".equals(type))) {//token 无效,或者类型不对。让用户去登录
                response.sendError(MessageEnum.PLEASE_LOGIN.getCode(), MessageEnum.PLEASE_LOGIN.getMessage());
                return;
            }else if(type.equals("fingo_client")){//前台类型token
                if (isProtectedUrl(request)) {
                    Date cuurrentTime = new Date();
                    String gentTime = jedisService.get(RedisKey.LOGIN_KEY_USER_ID+uid);
                    if (gentTime == null||"".equals(gentTime)) {
                        response.sendError(MessageEnum.PLEASE_LOGIN.getCode(), MessageEnum.PLEASE_LOGIN.getMessage());
                        return;
                    }else {
                        long expireTime = new Long(gentTime)+jwtUtil.EXPIRATION_TIME;
                        if( expireTime<cuurrentTime.getTime()) {
                            response.sendError(MessageEnum.TOKEN_IS_INVALID.getCode(), MessageEnum.TOKEN_IS_INVALID.getMessage());
                            return;
                        }
                        jedisService.set(RedisKey.LOGIN_KEY_USER_ID+uid,  new Date().getTime()+"");
                    }
                }
            }else if(type.equals("fingo_server")) {//后台类型token
                if(isSellerProtectedUrl(request)) {
                    Date cuurrentTime = new Date();
                    String gentTime = jedisService.get(RedisKey.BACK_LOGIN_KEY_USER_ID+uid);
                    if (gentTime == null||"".equals(gentTime)) {
                        response.sendError(MessageEnum.PLEASE_LOGIN.getCode(), MessageEnum.PLEASE_LOGIN.getMessage());
                        return;
                    }else {
                        long expireTime = new Long(gentTime)+jwtUtil.EXPIRATION_TIME;
                        if( expireTime<cuurrentTime.getTime()) {
                            response.sendError(MessageEnum.TOKEN_IS_INVALID.getCode(), MessageEnum.TOKEN_IS_INVALID.getMessage());
                            return;
                        }
                    }
//                    String resource = (String) claims.get(UsersEnum.REDIS_KEY.getMessage()+uid);
//                    List<String> list = (List<String>) JSON.parse(resource);
//                    JsonResultEntity JsonResultEntity = new JsonResultEntity();
//                    JsonResultEntity.setCode(UsersEnum.NO_PERMISSION.getCode());??
//                    JsonResultEntity.setMessage(UsersEnum.NO_PERMISSION.getMessage());
//                    if(list!=null && list.size()>0) {
//                        if (!list.contains(url)) {
//                            response.setCharacterEncoding("UTF-8");
//                            response.getWriter().print(JSON.toJSONString(JsonResultEntity));
//                            return;
//                        }
//                        //验证通过 则记录操作
//                        UserLogEntity entity = new UserLogEntity();
//                        entity.setGmtCreate(cuurrentTime);
//                        entity.setOperation(url);
//                        entity.setStatus(0);
//                        entity.setUserId(Integer.parseInt(uid));
//                        userLogMapper.insert(entity);
//                    }else if(list==null){
//                        response.setCharacterEncoding("UTF-8");
//                        response.getWriter().print(JSON.toJSONString(JsonResultEntity));
//                        return;
//                    }
                    jedisService.set(RedisKey.BACK_LOGIN_KEY_USER_ID+uid,  new Date().getTime()+"");
                }
            }
        }
        filterChain.doFilter(request, response);
    }

    private boolean isProtectedUrl(HttpServletRequest request) {
        for (String str : protectUrlPatterns) {
            if (PATH_MATCHER.match(str, request.getServletPath())) {
                return true;
            }
        }
        return false;
    }
    private boolean isSellerProtectedUrl(HttpServletRequest request) {
        for (String str : sellerProtectUrlPatterns) {
            if (PATH_MATCHER.match(str, request.getServletPath())) {
                return true;
            }
        }
        return false;
    }

}

JwtUtil

package com.example.demo.filter;


import com.example.demo.service.JedisService;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Service
public class JwtUtil {
    private  Logger logger= LoggerFactory.getLogger(JwtUtil.class);
    public   long EXPIRATION_TIME =1000*60*60*24*10; //token 失效时间
    public   String SECRET = "[email protected]";// 秘钥
    public   String TOKEN_PREFIX = "fingo"; //秘钥前缀
    public   String HEADER_STRING = "token";// token 名
    @Autowired
    private JedisService jedisService;
 // 清除token
    public  String removeToken(String uid) {
        jedisService.del(RedisKey.LOGIN_KEY_USER_ID+uid);
        return "true";
    }

    // 客户端生成token
    public  String generateToken(String uid,Date gentTime) {
        HashMap<String, Object> map = new HashMap<>();
        jedisService.set(RedisKey.LOGIN_KEY_USER_ID+uid, gentTime.getTime()+"");
        map.put("uid",uid);
        map.put("type","fingo_client");
        String jwt = Jwts.builder()
                .setClaims(map)
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .compact();
        return TOKEN_PREFIX + jwt;
    }
    // 为后台生成token并将权限放入token内
    public  String generateTokenForSeller(String uid,Date gentTime) {
        HashMap<String, Object> map = new HashMap<>();
        jedisService.set(RedisKey.BACK_LOGIN_KEY_USER_ID+uid, gentTime.getTime()+"");
        map.put("uid",uid);
        map.put("type","fingo_server");
        String jwt = Jwts.builder()
                .setClaims(map)
//                .setExpiration(new Date(gentTime.getTime() + EXPIRATION_TIME))
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .compact();
        return TOKEN_PREFIX + jwt;
    }
    // 验证token
    public  Map<String, Object> validateTokenAndGetClaims(HttpServletRequest request) {
        String token = request.getHeader(HEADER_STRING);
        if (token == null){
            throw new TokenValidationException(400,"请重新登录");
        }
        // parse the token. exception when token is invalid
        Map<String, Object> body = Jwts.parser()
                .setSigningKey(SECRET)
                .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                .getBody();
        return body;
    }
    
    public  Map<String, Object> validateByToken(String token) {
        if(StringUtils.isBlank(token)) {
            return null;
        }
        Map<String, Object> body = Jwts.parser()
                .setSigningKey(SECRET)
                .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                .getBody();
        return body;
    }
}

SpringContextUtil

package com.example.demo.filter;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringContextUtil implements ApplicationContextAware {
    private static ApplicationContext applicationContext;
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextUtil.applicationContext = applicationContext;
    }
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
    
    //通过名字获取上下文中的bean
    public static Object getBean(String name){
        return applicationContext.getBean(name);
    }
    
    //通过类型获取上下文中的bean
    public static Object getBean(Class<?> requiredType){
        return applicationContext.getBean(requiredType);
    }
}

TokenValidationException

package com.example.demo.filter;

public class TokenValidationException extends RuntimeException{

    private Integer code;


    public TokenValidationException(Integer code, String message) {
        super(message);
        this.code = code;
    }
}

第三步,测试

DemoController

 @ApiOperation(value = "登陆返回token", notes = "测试jwt")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "mobile", value = "手机号", dataType = "string", paramType = "query"),
            @ApiImplicitParam(name = "password", value = "密码", dataType = "string", paramType = "query")
    })
    @RequestMapping(value = "login", method = RequestMethod.POST)
    public ResponseEntity<JsonResultEntity> login(String mobile, String password) {
        if (StringUtils.isBlank(mobile) || StringUtils.isBlank(password)) {
            return ResponseEntity.ok(JsonResultUtils.error(10001, "用户名或密码不正确"));
        }
        //只做演示,不做数据查询校验
        return userService.login(mobile, password);
    }

UserService

/**
     * 登陆返回token
     * @param mobile
     * @param password
     * @return
     */
    ResponseEntity<JsonResultEntity> login(String mobile, String password);

UserServiceImpl

@Override
public ResponseEntity<JsonResultEntity> login(String mobile, String password) {
    //此处省去校验
    String token = jwtUtil.generateToken("1", new Date());
    Map<String, Object> map = new HashMap<>();
    map.put("uid", 1);
    map.put("token", token);
    return ResponseEntity.ok(JsonResultUtils.success(map));
}

测试token生成

技术分享图片

在DemoController中增加一个方法,测试token验证

@ApiOperation(value = "测试jwt", notes = "测试jwt")
@ApiImplicitParam(name = "uid", value = "用户id", dataType = "int", paramType = "query")
@RequestMapping(value = "testJwt", method = RequestMethod.POST)
public ResponseEntity<JsonResultEntity> testJwt(Integer uid, HttpServletRequest request) {
    Map<String, Object> claims = jwtUtil.validateTokenAndGetClaims(request);
    String userId = (String) claims.get("uid");
    if (userId == null || !uid.equals(Integer.parseInt(userId))) {
        return ResponseEntity.ok(JsonResultUtils.error(MessageEnum.YOU_CANT_DO_THIS.getCode(), MessageEnum.YOU_CANT_DO_THIS.getMessage()));
    }
    return ResponseEntity.ok(JsonResultUtils.success());
}

测试成功如图

技术分享图片

 

以上是关于JJWT使用笔记(一)—— JWT token的生成的主要内容,如果未能解决你的问题,请参考以下文章

springboot集成jjwt

Java 的 JJWT 实现 JWT

JWT学习

Spring Security + JJWT 实现 JWT 认证和授权

Cookie,Session,Token来进行身份认证

SpringBoot集成JWT实现token验证