jwt,token,cookie-session总结,JwtUtil.java编写,jwt应用

一.cookie-session总结,与jwt区别

1:cookie-session技术需要浏览器支持,response返回set-cookie属性后。http请求request 会自动携带cookie 

而,jwt和toekn一样 每次ajax都需要手动携带,一般是设置的request的header头中

2.cookie-session技术在web应用方便,对于集群,sessio存在容器(tomcat)并发大了占用内存的说法都有解决方案,

比如spring-redis   把session存在redis中,集群环境每个节点也是连接redis,tomcat不存储session,这样就可以实现集群了。

cookie-session自带刷新功能,而且浏览器会自动携带

传统的web应用还是使用cookie-session技术方便

二.jwt和token技术的区别

JWT是json web token缩写,JWT包含三个部分: Header头部,Payload负载和Signature签名

jwt的思想为去中心化。生成的令牌携带了加密的用户信息 直接后台验证 ,一旦生成后只要没到设置的失效时间就一直有效,这样如果泄露后无法让该令牌立即失效,除非

在加一层拦截验证,这样就失去了使用jwt的意义。

token就是一个用户标识(key),每次传入后台使用token查询redis或者数据库等查询用户信息验证,这样token泄露后可以及时在redis或数据库中剔除该toekn

使用jwt和toekn都没类似cookie-session刷新过期时间的机制(比如session超时时间30分钟,在29分钟时使用session后,超时时间又会重置为30分钟),因为jwt的过期时间是生成jwt令牌时指定的 ,需要自己采取相应方案,防止用户使用一段时间到期了,需要重新登录生成jwt或toekn。

jwt 口令如果快过期了,是没有设置延长过期时间的方式,因为过期时间本身就包含在了jwt口令中,只能重新生成新的jwt返回给用户使用,依此循环。

普通token的方式,因为token只是一个唯一标识,用户信息,过期时间在后端,直接延长就行。

我在开发移动app中就是使用的token技术   ,原生app都有提供获取机器唯一标识 UUID的函数,登录后UUID当做key,存入登录信息,

每次ajax请求携带UUID,登录后每个需要登录才能访问的请求都需要UUID查询redis或者数据库 验证用户。

jwt和token技术适合非web应用环境,比如移动端,或者发布服务给第三方调用,用来做用户验证

总结jwt和token最大的区别就是jwt令牌自带用户信息,过期时间等,token只是一个key,需要这个key去对应的存储中查找用户信息,项目上看情况使用

3.jwt demo

我使用的spring-boot项目写的

1.新建spring-boot项目

2.项目创建好pom引入jwt jar,我这边引入的JJWT最新版本

jwt的类库蛮多,我这边使用的jjwt测试

  <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
   </dependency>

3.创建JwtUtil工具类

JwtUtil.java

package com.wying.utils;


import com.fasterxml.jackson.core.JsonProcessingException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.tomcat.util.codec.binary.Base64;

import com.fasterxml.jackson.databind.ObjectMapper;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class JwtUtil {
    //JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
   // public static final String JWT_ID = UUID.randomUUID().toString();
     public static final String JWT_ID = "111111111222222222222222223333333333333";

    /**
     * 附加码加密密文  我这边用的base64编码的 使用时在解码 也可以直接定义不加密的普通密码 使用时也不用解码了
     */
    public static final String JWT_SECRET = "YnNvZnRwZXNoaGhoaGho";
    public static final int JWT_TTL = 60*60*1000;  //单位毫秒  这里设置一小时
    public static ObjectMapper mapper = new ObjectMapper();
    /**
     * 由附加码 JWT_SECRET 生成加密key
     *
     * @return
     */
    public SecretKey generalKey() {
        String stringKey = JWT_SECRET;

        // base64 密码 解密
        byte[] encodedKey = Base64.decodeBase64(stringKey);

        // 根据给定的字节数组使用AES加密算法构造一个密钥
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");

        return key;
    }

    /**
     * 创建jwt
     * @param id jwtid
     * @param issuer jwt签发人
     * @param subject jwt 主题 通常存放用户信息
     * @param ttlMillis  jtw过期时间
     * @return
     * @throws Exception
     */
    public String createJWT(String id, String issuer, String subject, long ttlMillis) throws Exception {

        // 指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        // 生成JWT的时间
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        // 创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
        Map<String, Object> claims = new HashMap<>();
        claims.put("uid", "1111111111");



        // 生成签名的时候使用的秘钥secret,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。
        // 一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
        SecretKey key = generalKey();

        // 下面就是在为payload添加各种标准声明和私有声明了
        JwtBuilder builder = Jwts.builder() // 这里其实就是new一个JwtBuilder,设置jwt的body
                .setClaims(claims)          // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setId(id)                  // 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
                .setIssuedAt(now)           // iat: jwt的签发时间
                .setIssuer(issuer)          // issuer:jwt签发人
                .setSubject(subject)        // sub(Subject):代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。
                .signWith(signatureAlgorithm, key); // 设置签名使用的签名算法和签名使用的秘钥

        // 设置过期时间
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date exp = new Date(expMillis);
            builder.setExpiration(exp);
        }
        return builder.compact();
    }

    /**
     * 解密jwt
     *
     * @param jwt 令牌
     * @return
     * @throws Exception
     */
    public Claims parseJWT(String jwt) throws Exception {
        SecretKey key = generalKey();  //签名秘钥,和生成的签名的秘钥一模一样
        Claims claims = Jwts.parser()  //得到DefaultJwtParser
                .setSigningKey(key)                 //设置签名的秘钥
                .parseClaimsJws(jwt).getBody();     //设置需要解析的jwt
        return claims;
    }

    public static void main(String[] args) throws Exception {


        Map<String,String> userMeesgaeMap=new HashMap<>();
        userMeesgaeMap.put("userId","CS001");
        userMeesgaeMap.put("userName","张三");
        userMeesgaeMap.put("idCard","330110202001010101");
        userMeesgaeMap.put("releId","100");


        String subject =mapper.writeValueAsString(userMeesgaeMap);
        try {
            JwtUtil util = new JwtUtil();
            String jwt = util.createJWT(JWT_ID, "gaom", subject,JWT_TTL);
            System.out.println("util.createJWT 生成的JWT :" + jwt);

            System.out.println("==============解密=====================");
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
            Claims c = util.parseJWT(jwt);
            System.out.println("jwt id:"+c.getId());//jwt id
            System.out.println("jwt签发时间:"+sdf.format(c.getIssuedAt()));
            System.out.println("jwt过期时间:"+sdf.format(c.getExpiration()));
            System.out.println("jwt用户信息:"+c.getSubject());
            System.out.println("jwt签发人:"+c.getIssuer());
            System.out.println("jwt 额外附加的属性uid :"+c.get("uid", String.class));

        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

4.运行,测试效果

5.jwt应用

业务场景:微信H5中无法下载文件,用户下载体检报告时我是先判断访问来源,如果是微信访问的话提示用户右上角在浏览器打开链接下载,在浏览器访问pdf报告下载链接由于不需要二次登录,直接传递体检编号的话存在遍历风险,传递加密的体检编号,下载时在解密可以做到遍历风险,但是这个加密字符串是一直有效的,用户保存起来可以随时下载,所以想到了jwt,用体检编号生成一个有下载期限的令牌,同时在jwt 主题subject属性中放入体检编号,jwt验证通过后解析subject在得到体检编号,同时实现体检编号加密传输和下载期限设置;

4.1 用户下载报告前生成wjt令牌
 /**
     * 创建报告jwt口令
     * @param httpServletRequest
     * @param tjTjdjbDto
     * @return
     */
    @GetMapping(value = "/createRporToken")
    public WrapperRusult createRporToken(HttpServletRequest httpServletRequest, TjTjdjbDto tjTjdjbDto) {
        log.info("入参tjTjdjbDto:" + tjTjdjbDto);
        try {
            //验证权限 防止水平越权
            Object tjDwdmbObjDto = httpServletRequest.getSession().getAttribute(ConstantUtil.DW_USER_MESSAGE);
            if (tjDwdmbObjDto == null) {
                return WrapperRusult.fail("查看PDF报告失败 提示:"+(ConstantUtil.SESSION_TIME_OUT_TIPS),"");
            }
            TjDwdmbDto tjDwdmbDto = (TjDwdmbDto) tjDwdmbObjDto;

            /**
             *  设置查询条件 只能查询本单位的报告
             */
            tjTjdjbDto.setDwbh(tjDwdmbDto.getDwbh());
            tjTjdjbDto.setJgid(tjDwdmbDto.getJgid());

            List<TjTjdjbDto> tjTjdjbDtoList=   companyReportService.selectReportByTjbh(tjTjdjbDto);
            if(tjTjdjbDtoList.size()==0){
               return WrapperRusult.fail("查看PDF报告失败 提示: tjbh:"+tjTjdjbDto.getTjbh()+" 未检索到报告301","");
            }
            if(!tjTjdjbDtoList.get(0).getDwbh().equals(tjDwdmbDto.getDwbh())){
                return WrapperRusult.fail("查看PDF报告失败 提示: tjbh:"+tjTjdjbDto.getTjbh()+" 不属于当前登录单位: "+tjDwdmbDto.getMc());
            }

            //生成令牌
            JwtUtil util = new JwtUtil();
            String jwt = util.createJWT(JWT_ID, "gaom", "{\"tjbh\":\""+tjTjdjbDto.getTjbh()+"\"}",JWT_TTL);
            String urlEncoderJwt=java.net.URLEncoder.encode(jwt);
            log.info("util.createJWT 生成的JWT :{}  url编码后:{}",jwt,urlEncoderJwt);
            return WrapperRusult.success(urlEncoderJwt);
        } catch (Exception e) {
            //返回异常信息 方便前端排查错误
            return WrapperRusult.fail(e.getMessage());
        }


    }

4.2  外部浏览器下载报告先验证jwt
/**
     * 下载报告 返回pdf 文件流
     * @param httpServletRequest
     * @param httpServletResponse
     * @param tjTjdjbDto
     */
    @GetMapping(value = "/downReport")
    public void downReport(HttpServletRequest httpServletRequest,
                              HttpServletResponse httpServletResponse, TjTjdjbDto tjTjdjbDto) {
        ByteArrayInputStream byteArrayInputStream = null;
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        log.info(" 入参tjTjdjbDto:" + tjTjdjbDto);
        try {
            String tjbhToken= tjTjdjbDto.getTjbhToken();
            String urlDecoderTjbhToken=java.net.URLDecoder.decode(tjbhToken);
            log.info("tjbhToken:{} ",urlDecoderTjbhToken);
            JwtUtil util = new JwtUtil();
            /**
             * 如果令牌过期会直接抛出异常,不用我们手动判断是否过期 只能能执行下去得到Claims对象 就在有效期
             */
            Claims c = util.parseJWT(urlDecoderTjbhToken);
            String subject=c.getSubject();


            String tjbh= JSONUtil.toBean(subject, TjTjdjbDto.class).getTjbh();
            log.info("tjbhToken:{}  解析到 subject:{}, 体检编号:{} ",urlDecoderTjbhToken,subject,tjbh);
            tjTjdjbDto.setTjbh(tjbh);
            String pdfBase64= companyReportService.getPdfReportBase64(tjTjdjbDto);

            byte[] bytes =  Base64.getDecoder().decode(pdfBase64);
            //创建一个将byte作为缓冲区的ByteArrayInputStream对象
            byteArrayInputStream = new ByteArrayInputStream(bytes);

            //创建从底层输入流读取的读取数据的缓冲输入流对象
            bis = new BufferedInputStream(byteArrayInputStream);


            //创建一个缓冲区字节对象
            byte[] buffer = new byte[bis.available()];

            //读取缓冲区的数据
            bis.read(buffer);

            //清空response
            httpServletResponse.reset();
           // httpServletResponse.setHeader("Content-type", "application/pdf");
            httpServletResponse.setHeader("Content-Disposition", "attachment;filename=" + new String
                    (tjbh.getBytes("gb2312"), "iso-8859-1") + ".pdf");
            //创建从底层输出流输出页面数据的缓冲流输出对象
            bos = new BufferedOutputStream(httpServletResponse.getOutputStream());
            bos.write(buffer);
            bos.flush();


        } catch (Exception e) {
            e.printStackTrace();
            BaseUtil.outputString(httpServletResponse,"查看PDF报告失败 异常:e"+e.getMessage());

        } finally {

            try {
                if (bos != null) {
                    bos.close();
                }
                if (bis != null) {
                    bis.close();
                }
                if (byteArrayInputStream != null) {
                    byteArrayInputStream.close();
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();

            }
        }

    }

如果jw令牌过期会抛出异常

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值