解析JWT时遇到Java反射异常ClassCastExcepti
问题描述:
将一个复杂类(类成员变量有链表类)的实例化对象,转为符合JWT的Json串之后,再通过该json解析出该复杂类的对象。
场景:
我在使用JWT的时候,为了将用户身份在Token中进行记录,以减轻服务器查询数据库带来的消耗,定义了一个AuthoritiesVo类:
成员变量:
访问修饰符 | 变量类型 | 变量名 |
---|---|---|
private | int | userid |
private | ArrayList<String> | privil |
private | ArrayList<Integer> | privilValue |
其中,privil与privilValue的取值范围是AuthoritiesVo定义的一系列常量:
访问修饰符 | 静态 | 最终值 | 类型 | 变量名 | 值 |
---|---|---|---|---|---|
public | static | final | String | ADMIN | "ADMIN" |
public | static | final | String | COMMON | "COMMON" |
public | static | final | int | ADMIN_VALUE | 1 |
public | static | final | int | COMMON_VALUE | 7 |
成员方法:
上述成员变量的setter和getter方法
JWT的背景说明:
JWT是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范。JWT作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。
提炼主要信息:JWT是一种规范,用来描述一种一Json对象来传递信息的规范。
我们采用的JWT规范的一个实现的Jar包:jsonwebtoken
在jsonwebtoken提供的方法中,可以通过Claims来将自定义的信息写入负载(Payload),Claims实际上是一个Map,通过put(key, value)的形式就可以将数据以键值对的形式写入,比如:
使用Claims将自定义的信息写入JWT负载
Claims claims = Jwts.claims().setSubject(user.getName()); //Claims是一个Map,可以向其中写入自定义的key-value值 claims.put("userId", user.getUserid()); claims.put("Authorities", user.getAuthorities());
解析的时候,要获取负载(Payload)中的信息,仍然可以像使用Map一样,通过get(key)的形式将值取出,比如:
使用Claims从负载中取出需要的数据
Claims claims = Jwts.parser() .setSigningKey(DatatypeConverter.parseBase64Binary(PRIVATE_KEY)) .parseClaimsJws(token).getBody(); int userId = (Integer) claims.get("userId");
但是,我们将上文提到的AuthoritiesVo类的实例化对象写入到负载中,并置key为Authorities,并不能通过get("Authorities")直接强转为AuthoritiesVo对象:
下述代码会报错:
解析token串的函数方法
authorities = (AuthoritiesVo) claims.get("Authorities");
报错的主要异常信息如下:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to imp4sep.vo.AuthoritiesVo at imp4sep.utils.TicketUtils.parseToken(TicketUtils.java:118) at imp4sep.utils.TicketUtilsTest.testParseToken(TicketUtilsTest.java:89) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source)
我们首先判断,claims.get("Authorities")语句得到的内容是什么:
用输出:
{userid=10001, privil=[COM_ADMIN, LEADER, PM], privilValue=[2, 3, 4]}
鼠标悬浮(或者添加watch),可以看到,claims是一个LinkedHashMap: claims是一个LinkedHashMap
展开可以看到,get方法返回的也是一个LinkedHashMap:
get方法返回的也是一个LinkedHashMap我们之所以不能将返回值强制类型转换为AuthoritiesVo问题就在此。
继续展看层层嵌套的HashMap,在table中找到我们需要的privil和privilValue
(因为HashMap采用的是散列算法,所以table中有些单元是空的):
既然反射并不能直接得到最终的类对象,现在我们知道了返回值类型为链表哈希映射表,就不直接强制类型,采用“手动”的方式解决:
LinkedHashMap hashmap = (LinkedHashMap) claims.get("Authorities"); ArrayList<String> privil = (ArrayList<String>) hashmap.get("privil"); ArrayList<Integer> privilValue = (ArrayList<Integer>) obj.get("privilValue"); authorities.setPrivil(privil); authorities.setPrivilValue(privilValue);
至此,问题解决。