学习视频地址:SpringSecurity框架教程-Spring Security+JWT实现项目级前端分离认证授权-B站最通俗易懂的Spring Security课程
🟥异常1️⃣: com.alibaba.fastjson.JSONObject and com.demo.entity.LoginUser are in unnamed module of loader 'app'
异常位置
JwtAuthenticationTokenFilter
类 -> doFilterInternal
方法 -> LoginUser loginUser = redisCache.getCacheObject(redisKey);
package com.demo.filter;
import com.alibaba.fastjson.JSONObject;
import com.demo.constant.JwtConstant;
import com.demo.entity.LoginUser;
import com.demo.util.JwtUtil;
import com.demo.util.RedisCache;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
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.Objects;
/**
* Token 认证过滤器
*
* @author action
* @date 2022/11/8 22:23
*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisCache redisCache;
/**
* 内部过滤器
*
* @param request 请求
* @param response 响应
* @param filterChain 过滤链
* @throws ServletException Servlet异常
* @throws IOException IO异常
* @date 2022/11/8 22:36
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 获取Token
String token = request.getHeader("token");
if (!StringUtils.hasText(token)) {
// token不存在, 接口放行并返回
filterChain.doFilter(request, response);
return;
}
String userId;
// 解析Token
try {
Claims claims = JwtUtil.parseJwt(token);
userId = claims.getSubject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(JwtConstant.ILLEGAL_TOKEN);
}
// 从Redis缓存中获取用户信息
String redisKey = "login:" + userId;
LoginUser loginUser = redisCache.getCacheObject(redisKey);
if (Objects.isNull(loginUser)) {
throw new RuntimeException(JwtConstant.NOT_SIGN_IN);
}
// 存入 SecurityContextHolder
// TODO 封装权限信息到 authentication
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
// 全部处理完成后放行
filterChain.doFilter(request, response);
}
}
异常原因分析
参考文章:解决FastJson com.alibaba.fastjson.JSONObject cannot be cast to的问题
找到原因:此处从redis中获取的 redisCache 对象无法通过 fastJson 强转为 LoginUser 对象
起初我以为存入 Redis 的 loginUser 对象格式有问题,但是不管怎么存都是这样的格式:
{
"@type": "com.demo.entity.LoginUser",
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true,
"password": "$2a$10$TXWXQbE7TFmUuV",
"user": {
"avatar": "",
"createTime": 1651143735000,
"email": "xxxxx@qq.com",
"enable": 1,
"expired": 0,
"id": 1,
"locked": 1,
"nickName": "某某",
"password": "$2a$10$TXWXQbE7TFmUuV",
"phone": "123",
"sex": 1,
"updateTime": 1667905073651,
"username": "lee"
},
"username": "lee"
}
🤨直到我翻视频下的评论看到
果然评论区出人才啊!确实是 fastjson 版本高
视频中Up主用的是:1.2.33
而我用的是: 2.0.14
新版本不支持强转
参考文章:阿里巴巴fastjson版本从1.2.72升级到2.0.14。开启autoType,不支持Object对象接收强转成Java对象的解决方案
异常解决方案
将 LoginUser loginUser = redisCache.getCacheObject(redisKey)
替换为如下:
// 先转成JSON对象
JSONObject jsonObject = redisCache.getCacheObject(redisKey);
// JSON对象转换成Java对象
LoginUser loginUser = jsonObject.toJavaObject(LoginUser.class);
🟥异常2️⃣com.alibaba.fastjson2.JSONException: invoke constructor error, public com.demo.entity.LoginUser(com.demo.entity.User)
异常位置
JwtAuthenticationTokenFilter
类 -> doFilterInternal
方法 -> LoginUser loginUser = jsonObject.toJavaObject(LoginUser.class);
异常原因分析
调用LoginUser 实体类的构造函数错误
UP主视频中的 LoginUser 类用的是 loombok 依赖代替写某些方法
- @NoArgsConstructor 代替写无参构造方法
- @AllArgsConstructor 代替写有参构造方法
- @Data 代替写 get 和 set 方法
我自己写的实体类 LoginUser 如下:
package com.demo.entity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
/**
* 登录用户类
*
* @author Actio
* @date 2022/10/13 1:58
*/
public class LoginUser implements UserDetails {
private User user;
/**
* 有参构造方法
*/
public LoginUser(User user) {
this.user = user;
}
public String toString() {
return "LoginUser(user=" + getUser() + ")";
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
很显然,我没有用 loombok 依赖,自己手动写了上述方法,然后就出事了 😵💫
异常解决方案
参考文章:有关Java实体类中get、set方法和有参无参构造方法的个人见解。
总的来说,get和set方法就是为了能以得到和设置实体类中的私有属性值,而一个类中至少要有一个构造方法,
当没有人为添加的时候,编译器会自动加入一个隐式的无参构造方法,
当有人为添加时,编译器就不会自动添加了。
无参构造方法的作用是为了比较方便的new出一个对象
。
✍️ 实体类中如果未使用 loombok 依赖或插件且
自己写了有参构造方法时:请务必写上无参构造方法!
/**
* 无参构造方法
*/
public LoginUser() {
super();
}
至于为什么不想用 loombok 插件,原因请自行百度~
综上,受益良多!😼
ps:如有错误,欢迎批评指正,谢谢!