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 的认证过程
- 用户登录系统;
- 服务端验证,将认证信息通过指定的算法(例如HS256)进行加密,例如对用户名和用户所属角色进行加密,加密私钥是保存在服务器端的,将加密后的结果发送给客户端,加密的字符串格式为三个”.” 分隔的字符串 Token,分别对应头部、载荷与签名,头部和载荷都可以通过 base64 解码出来,签名部分不可以;
- 客户端拿到返回的 Token,存储到 local storage 或本地数据库;
- 下次客户端再次发起请求,将 Token 附加到 header 中;
- 服务端获取 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的生成的主要内容,如果未能解决你的问题,请参考以下文章