JWT入门,登录案例的实现,踩雷与解析.

8 篇文章 0 订阅
2 篇文章 0 订阅
一个晚上就搞一个JWT,各种采坑要自闭了.

就怕自己继续自闭,写一个笔记,记录一下JWT入门用法:

1.环境:

pom.xml中写入坐标:
<!--jwt依赖-->
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.3</version>
        </dependency>

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

        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.9.7</version>
        </dependency>
JWT所需工具类:

在这里插入图片描述

JwtUtils:
public class JwtUtils {
    /**
     *  私钥加密token
     * @param data 需要加密的数据(载荷内容)
     * @param expireMinutes 过期时间,单位:分钟
     * @param privateKey 私钥
     * @return
     */
    public static String generateToken(Object data, int expireMinutes, PrivateKey privateKey) throws Exception {
        //1 获得jwt构建对象
        JwtBuilder jwtBuilder = Jwts.builder();
        //2 设置数据
        if( data == null ) {
            throw new RuntimeException("数据不能为空");
        }
        BeanInfo beanInfo = Introspector.getBeanInfo(data.getClass());
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            // 获得属性名
            String name = propertyDescriptor.getName();
            // 获得属性值
            Object value = propertyDescriptor.getReadMethod().invoke(data);
            if(value != null) {
                jwtBuilder.claim(name,value);
            }
        }
        //3 设置过期时间
        jwtBuilder.setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate());
        //4 设置加密
        jwtBuilder.signWith(SignatureAlgorithm.RS256, privateKey);
        //5 构建
        return jwtBuilder.compact();
    }

    /**
     * 通过公钥解析token
     * @param token 需要解析的数据
     * @param publicKey 公钥
     * @param beanClass 封装的JavaBean
     * @return
     * @throws Exception
     */
    public static <T> T  getObjectFromToken(String token, PublicKey publicKey,Class<T> beanClass) throws Exception {
        //1 获得解析后内容
        Claims body = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token).getBody();
        //2 将内容封装到对象JavaBean
        T bean = beanClass.newInstance();
        BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            // 获得属性名
            String name = propertyDescriptor.getName();
            // 通过属性名,获得对应解析的数据
            Object value = body.get(name);
            if(value != null) {
                // 将获得的数据封装到对应的JavaBean中
                BeanUtils.setProperty(bean,name,value);
            }
        }
        return bean;
    }
}

RasUtils:
public class RasUtils {

    /**
     * 从文件中读取公钥
     *
     * @param filename 公钥保存路径,相对于classpath
     * @return 公钥对象
     * @throws Exception
     */
    public static PublicKey getPublicKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPublicKey(bytes);
    }

    /**
     * 从文件中读取密钥
     *
     * @param filename 私钥保存路径,相对于classpath
     * @return 私钥对象
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPrivateKey(bytes);
    }

    /**
     * 获取公钥
     *
     * @param bytes 公钥的字节形式
     * @return
     * @throws Exception
     */
    public static PublicKey getPublicKey(byte[] bytes) throws Exception {
        X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePublic(spec);
    }

    /**
     * 获取密钥
     *
     * @param bytes 私钥的字节形式
     * @return
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(byte[] bytes) throws Exception {
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePrivate(spec);
    }

    /**
     * 根据密文,生存rsa公钥和私钥,并写入指定文件
     *
     * @param publicKeyFilename  公钥文件路径
     * @param privateKeyFilename 私钥文件路径
     * @param secret             生成密钥的密文
     * @throws Exception
     */
    public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        SecureRandom secureRandom = new SecureRandom(secret.getBytes());
        keyPairGenerator.initialize(1024, secureRandom);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        // 获取公钥并写出
        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        writeFile(publicKeyFilename, publicKeyBytes);
        // 获取私钥并写出
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        writeFile(privateKeyFilename, privateKeyBytes);
    }

    private static byte[] readFile(String fileName) throws Exception {
        return Files.readAllBytes(new File(fileName).toPath());
    }

    private static void writeFile(String destPath, byte[] bytes) throws IOException {
        File dest = new File(destPath);

        //创建父文件夹
        if(!dest.getParentFile().exists()){
            dest.getParentFile().mkdirs();
        }
        //创建需要的文件
        if (!dest.exists()) {
            dest.createNewFile();
        }

        Files.write(dest.toPath(), bytes);
    }


}

2.写入测试数据,生成文件:

随便写一个测试类直接运行:
public class TestJwt {
    private static final String pubKeyPath = "D:\\temp\\password.pub";
    private static final String priKeyPath = "D:\\temp\\password.pri";
    @Test
    public void testDemo01() throws Exception {
        //生成公钥和私钥
        RasUtils.generateKey(pubKeyPath,priKeyPath,"123");
    }
    @Test
    public void testGetRas() throws Exception {
        //获得公钥和私钥
        PublicKey publicKey = RasUtils.getPublicKey(pubKeyPath);
        PrivateKey privateKey = RasUtils.getPrivateKey(priKeyPath);

        System.out.println(publicKey);
        System.out.println(privateKey);
    }
}
运行第一个方法即可自动生成数据文件,那个"123"随便写,就是加密差不多意思.

在这里插入图片描述

3.登录的过滤器:

在这里插入图片描述

@Component
public class LoginFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        //获得工具类(请求上下文对象)
        RequestContext requestContext = RequestContext.getCurrentContext();
        //通过工具类获得request对象
        HttpServletRequest request = requestContext.getRequest();
        //通过请求对象判断是否在登录页面(在登录页面才能点击登录使用登录方法)
        //如果不是就放行,进行登录判断,如果是,就不需要登录判断
      
        String requestURI = request.getRequestURI();
		
		//-------------------------------------------------------------------------------------------
		//StringBuffer requestURL = request.getRequestURL();注意了,这里非常重要,必须获取的是URI而不是URL,因为我们下面if中
		//写的就是URI,如果获取URL下面就需要改成http://localhost:端口号/网关前缀/服务名/一级路径/二级路径.....
		//-------------------------------------------------------------------------------------------


//        System.out.println(requestURI);
        if ("/api/userservice/user/login".equals(requestURI)){
            return false;
        }
        return true;
    }

    //设置一个存放密码的地址
    private static final String pubKeyPath = "D:\\temp\\password.pub";

    @Override
    public Object run() throws ZuulException {
        //获得工具类(请求上下文对象)
        RequestContext requestContext = RequestContext.getCurrentContext();
        //通过工具类获得request对象
        HttpServletRequest request = requestContext.getRequest();
        //根据请求头获取token
        String token = request.getHeader("authorization");
        try {
            JwtUtils.getObjectFromToken(token, RasUtils.getPublicKey(pubKeyPath), User.class);
        } catch (Exception e) {
            e.printStackTrace();
            //如果没有找到匹配的数据
            //不允许放行
            requestContext.setSendZuulResponse(false);
            requestContext.setResponseStatusCode(403);	//这里是你希望在前台响应的错误响应码
        }
        //放行
        return null;
    }
}

上面有横杠隔开的地方一定要读!!!

4.登录方法:

@RestController
@RequestMapping("/user")
public class UserController {
    @Resource
    private UserService userServiceImpl;
    //设置一个存放密码的地址
    private static final String priKeyPath = "D:\\temp\\password.pri";
    @PostMapping("/login")
    public BaseResult login(@RequestBody User user){
        User u = userServiceImpl.login(user);
        if (u == null){
            return BaseResult.error("用户名或者密码错误");
        }else{
            //正确登录之后就需要产生token
            String token = null;
            try {
                token = JwtUtils.generateToken(u,30, RasUtils.getPrivateKey(priKeyPath));
            } catch (Exception e) {
                e.printStackTrace();
            }
            return BaseResult.ok("登录成功").append("token",token);
        }
    }
}

5.前端我用的Vue,大概有两个地方需要改:

1.登录方法:
async login(){
            let {data} = await login(this.user)
            if (data.code == 1) {
                this.$message.success(data.message)
                //获得token,保存到sessionStorage
                sessionStorage.setItem('token',data.other.token)
                this.$router.push('/home')
            }else{
                this.$message.error(data.message)
            }
        }
2.api.js拦截器
// 3 配置拦截器
axios.interceptors.request.use(request=>{
    let token = sessionStorage.getItem('token')
    if(token){
        request.headers.authorization = token
    }
    return request
},error=>{})
axios.interceptors.response.use(response => {
    
    // 放行
    return response
}, error => {
    // 错误提示
    console.info(error)
    Message.error(error.message)
    return Promise.reject(error)
})

如上,如果一开始没有登录想进其他页面,没有权限所有的操作都会报403,只有在登录成功过一次之后才能正常使用页面内的操作.

异常&处理:

java.nio.file.NoSuchFileException: D:\temp\password.pub

系统没有找到该文件

java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: Short read of DER length

我出这个异常的时候因为我的token没有被取出来,token为null

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值