• 学习登录接口过程中的踩坑记录


    学习视频地址: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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

    异常原因分析

    参考文章:解决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"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    🤨直到我翻视频下的评论看到
    评论区截图
    果然评论区出人才啊!确实是 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);
    
    • 1
    • 2
    • 3
    • 4

    🟥异常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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    很显然,我没有用 loombok 依赖,自己手动写了上述方法,然后就出事了 😵‍💫

    异常解决方案

    参考文章:有关Java实体类中get、set方法和有参无参构造方法的个人见解。

    总的来说,get和set方法就是为了能以得到和设置实体类中的私有属性值,而一个类中至少要有一个构造方法,
    当没有人为添加的时候,编译器会自动加入一个隐式的无参构造方法,
    当有人为添加时,编译器就不会自动添加了。
    无参构造方法的作用是为了比较方便的new出一个对象

    ✍️ 实体类中如果未使用 loombok 依赖或插件自己写了有参构造方法时:请务必写上无参构造方法!

    	 /**
         * 无参构造方法
         */
        public LoginUser() {
            super();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    至于为什么不想用 loombok 插件,原因请自行百度~

    综上,受益良多!😼

    ps:如有错误,欢迎批评指正,谢谢!

  • 相关阅读:
    geecg-uniapp 路由修改 页面创建 (2)
    web内容如何保护:如何有效地保护 HTML5 格式的视频内容?
    老司机必备的手机浏览器,比UC浏览器还好用
    【单片机毕业设计】【mcuclub-jj-054】基于单片机的豆浆机的设计
    【网络杂烩 ---> 网络安全】DLL 注入 --- c/c++ 代码实现(超 · 详细)
    在量化投资股票中如何正确使用MACD建立自己的交易策略?
    kmalloc、kzalloc、vmalloc、kmem_cache_alloc的区别
    quarkus(一) hello world
    HBase (六) --------- HBase API 操作
    ByteHouse MaterializedMySQL 增强优化
  • 原文地址:https://blog.csdn.net/qq_43006591/article/details/127786559