04 动力云客之登录后获取用户信息+JWT存进Redis+Filter验证Token + token续期

news2024/12/22 23:20:51

在这里插入图片描述

1. 登录后获取用户信息

非常好实现. 只要新建一个controller, 并调用SS提供的Authentication对象即可

package com.sunsplanter.controller;

@RestController
public class UserController {

    @GetMapping(value = "api/login/info")
    public R loginInfo(Authentication authentication) {
        TUser tUser = (TUser)authentication.getPrincipal();

        return R.OK(tUser);
    }

}

未登录状态下可以直接访问 api/login/info吗?

不可以. 因为在安全配置类已经写明了, 仅登陆界面允许任何人访问, 其他所有界面都需要认证

<由于未写JWT, 默认使用Session 保存会话,> ???好像不对
因此只要我们先通过登录接口登录, 然后再直接访问获取用户信息接口即可
在这里插入图片描述

2. 使用JWT打通登录后各个页面的认证

前后端分离的项目一般会使用token(jwt)实现登录状态的保持;(java web : session)

token其实就是一个随机字符串(字符串要求是唯一的,不同人的token都不能相同),当用户在登录页面输入账号和密码后,前端将账号密码发送给后端,后端检验完账号和密码后,会生成一个随机不重复的字符串即(token),并将其响应给前端,前端拿到token后,需要在客户端进行持久化存储(一般会写在localStorage或者sessionStorage中),那么下次在向后端数据接口发送请求的时候,一般需要将token一并发送给后端数据接口,后端数据接口会对token进行校验,如果合法则正常响应请求,如果不合法,则提示未登录。

2.1 sessionStorage与localStorage

它们是javascript对象,浏览器支持这两个对象,可以直接使用. 属于前端范畴
localStorage和sessionStorage都是用来在浏览器客户端存储临时信息的对象;

sessionStorage、localStorage区别?

sessionStorage只在一个浏览器页面有效,比如你打开一个新的tab浏览器页会失效,你关闭浏览器后,再打开浏览器也会失效;
localStorage在整个浏览器中都有效,重启浏览器也有效;除非你手动删除了localStorage,才会失效;

2.2 修改登录成功拦截器

根据流程图, 在查询到对象并返回给SS后, SS会调用成功拦截器,
就在这个拦截器中, 根据查询到的对象生成JWT并同时存进Redis和返回前端.

//登录成功会自动执行这个类中的onAuthenticationSuccess方法, 该方法返回自定义的Json给前端
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Resource
    private RedisService redisService;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        //登录成功,执行该方法,在该方法中返回json给前端,就行了
        TUser tUser = (TUser) authentication.getPrincipal();

        //1.生成JWT
        //由于createJWT方法定义的参数是序列化后的对象, 因此先调用JSONUtils序列化对象
        String userJSON = JSONUtils.toJSON(tUser);
        String jwt = JWTUtils.createJWT(userJSON);

        //2.写入Redis
        redisService.setValue(Constants.REDIS_JWT_KEY + tUser.getId(), jwt);

        //3. 设置JWT的过期时间(如果选择记住我, 过期时间是7天, 否则30分钟)
        String rememberMe = request.getParameter("rememberMe");
        //勾选了记住我就设置为7天, 其中EXPIRE_TIME就是7天,DEFAULT_EXPIRE_TIME是半小时
        if (Boolean.parseBoolean(rememberMe)){
            redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId() , Constants.EXPIRE_TIME, TimeUnit.SECONDS);
        } else {
            redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId() , Constants.DEFAULT_EXPIRE_TIME, TimeUnit.SECONDS);
        }

        //登录成功的统一结果
        R result = R.OK(jwt);

        //把R对象转成json
        String resultJSON = JSONUtils.toJSON(result);

        //把R以json返回给前端
        ResponseUtils.write(response, resultJSON);
    }
}

常量类为

package com.sunsplanter.constant;

/**
 * 常量类
 */
public class Constants {

    public static final String LOGIN_URI = "/api/login";

    //redis的key的命名规范: 项目名:模块名:功能名:唯一业务参数(比如用户id)
    public static final String REDIS_JWT_KEY = "dlyk:user:login:";

    //redis中负责人的key
    public static final String REDIS_OWNER_KEY = "dlyk:user:owner";

    //jwt过期时间7天
    public static final Long EXPIRE_TIME = 7 * 24 * 60 * 60L;

    //jwt过期时间30分钟
    public static final Long DEFAULT_EXPIRE_TIME = 30 * 60L;
}

redis类 为

package com.sunsplanter.service

public interface RedisService {

    void setValue(String key, Object value);

    Object getValue(String key);

    Boolean removeValue(String key);

    //给jwt设置过期时间
    Boolean expire(String key, Long timeOut, TimeUnit timeUnit);
}
package com.sunsplanter.service.impl;

@Service
public class RedisServiceImpl implements RedisService {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public void setValue(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    @Override
    public Object getValue(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    @Override
    public Boolean removeValue(String key) {
        return redisTemplate.delete(key);
    }

    //给JWT设置过期时间
    @Override
    public Boolean expire(String key, Long timeOut, TimeUnit timeUnit) {
        return redisTemplate.expire(key, timeOut, timeUnit);
    }
}

在网页登录后可以看到已经存到了localstorage
在这里插入图片描述

3 Filter验证Token

如流程图所示, 第一次登录成功过后, 往后每次登录会携带token登录,

因此, 在config.filter文件夹下新建一个token过滤器类, 该类实现OncePerRequestFilter
并在SS的安全配置类中中添加进去

package com.sunsplanter.config.filter;

@Component
public class TokenVerifyFilter extends OncePerRequestFilter{

    @Resource
    private RedisService redisService;

    //spring boot框架的ioc容器中已经创建好了该线程池,可以注入直接使用
    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (request.getRequestURI().equals(Constants.LOGIN_URI)) { //如果是登录请求,此时还没有生成jwt,那不需要对登录请求进行jwt验证
            //验证jwt通过了 ,让Filter链继续执行,也就是继续执行下一个Filter
            filterChain.doFilter(request, response);

        } else {
            String token = null;
            if (request.getRequestURI().equals(Constants.EXPORT_EXCEL_URI)) {
                //从请求路径的参数中获取token
                token = request.getParameter("Authorization");
            } else {
                //其他请求都是从请求头中获取token
                token = request.getHeader("Authorization");
            }

            if (!StringUtils.hasText(token)) {
                //token验证未通过的统一结果类
                R result = R.FAIL(CodeEnum.TOKEN_IS_EMPTY);
                //把R对象转成json
                String resultJSON = JSONUtils.toJSON(result);
                //把R以json返回给前端
                ResponseUtils.write(response, resultJSON);
                return;
            }

            //验证token有没有被篡改过
            if (!JWTUtils.verifyJWT(token)) {
                //token验证未通过统一结果类
                R result = R.FAIL(CodeEnum.TOKEN_IS_ERROR);

                //把R对象转成json
                String resultJSON = JSONUtils.toJSON(result);

                //把R以json返回给前端
                ResponseUtils.write(response, resultJSON);

                return;
            }

            TUser tUser = JWTUtils.parseUserFromJWT(token);
            String redisToken = (String) redisService.getValue(Constants.REDIS_JWT_KEY + tUser.getId());

            //验证token非空
            if (!StringUtils.hasText(redisToken)) {
                //token验证未通过统一结果类
                R result = R.FAIL(CodeEnum.TOKEN_IS_EXPIRED);

                //把R对象转成json
                String resultJSON = JSONUtils.toJSON(result);

                //把R以json返回给前端
                ResponseUtils.write(response, resultJSON);

                return;
            }

            //验证token是否与redis中的一致
            if (!token.equals(redisToken)) {
                //token验证未通过的统一结果类
                R result = R.FAIL(CodeEnum.TOKEN_IS_NONE_MATCH);

                //把R对象转成json
                String resultJSON = JSONUtils.toJSON(result);

                //把R以json返回给前端
                ResponseUtils.write(response, resultJSON);
                return;
            }

            //jwt验证通过了,那么在spring security的上下文环境中要设置一下,设置当前这个人是登录过的,你后续不要再拦截他了
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(tUser, tUser.getLoginPwd(), tUser.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);

            //刷新一下token(异步处理,new一个线程去执行)
            /*new Thread(() -> {
                //刷新token
                String rememberMe = request.getHeader("rememberMe");
                if (Boolean.parseBoolean(rememberMe)) {
                    redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId(), Constants.EXPIRE_TIME, TimeUnit.SECONDS);
                } else {
                    redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId(), Constants.DEFAULT_EXPIRE_TIME, TimeUnit.SECONDS);
                }
            }).start();*/

            //异步处理(更好的方式,使用线程池去执行)
            threadPoolTaskExecutor.execute(() -> {
                //刷新token
                String rememberMe = request.getHeader("rememberMe");
                if (Boolean.parseBoolean(rememberMe)) {
                    redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId(), Constants.EXPIRE_TIME, TimeUnit.SECONDS);
                } else {
                    redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId(), Constants.DEFAULT_EXPIRE_TIME, TimeUnit.SECONDS);
                }
            });

            //验证jwt通过了 ,让Filter链继续执行,也就是继续执行下一个Filter
            filterChain.doFilter(request, response);
        }
    }
}

4. token续期

现在一个问题是

token一旦发布, 是7天/30分钟 .

原来没有续期时 , token只能获得与提前删除.

然而一个现实需求是

假如用户获得是30分钟的token. 在这三十分钟内, 只要用户有任何操作, 我们应当自动刷新token到30分钟 , 否则假如不管用户怎么操作, token都固定30分钟刷新 , 这显然不符合逻辑

目标为, 任何除登出的用户登录都会重置token过期时间

然后是代码放在哪里的问题

我们知道, 即使登录过后 , 我们在每一次操作时仍会用TokenVerifyFilter检查token的有效期.

因此直接把续期代码放到该类中即可, 每次有操作->执行TokenVerifyFilter检查token有效期->有效则续期token

在TokenVerifyFilter中新增刷新代码

//刷新一下token(异步处理,new一个线程去执行)
//没必要因为续期token而阻塞整个进程, 毕竟此时已经校验过了不影响本次执行
            new Thread(() -> {
                //刷新token
                String rememberMe = request.getHeader("rememberMe");
                if (Boolean.parseBoolean(rememberMe)) {
                    redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId(), Constants.EXPIRE_TIME, TimeUnit.SECONDS);
                } else {
                    redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId(), Constants.DEFAULT_EXPIRE_TIME, TimeUnit.SECONDS);
                }
            }).start();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1460848.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ipad作为扩展屏的最简单方式(无需数据线)

ipad和win都下载安装toDesk&#xff0c;并且都处于同一局域网下 连接ipad&#xff0c;在ipad中输入win设备的设备密码和临时密码&#xff0c;连接上后可以看到ipad会是win屏幕的镜像&#xff0c;此时退出连接&#xff0c;准备以扩展模式再次连接。 注意&#xff0c;如果直接从…

Devvortex

目标靶机 攻击机IP地址为10.10.16.2 信息收集 # nmap -sT --min-rate 10000 -p- 10.10.11.242 -oN port.nmap Starting Nmap 7.94 ( https://nmap.org ) at 2024-02-21 10:32 CST Warning: 10.10.11.242 giving up on port because retransmission cap hit (10). Nma…

FPGA高端项目:FPGA实现SDI视频编解码工程解决方案,提供3套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本方案的SDI图像缩放应用本方案的SDI图像缩放视频拼接应用本方案的SDI图像缩放UDP网络视频发送应用本方案的SDI视频编码输出应用本方案的SDI视频编码图像缩放视频拼接输出应用本方案的SDI视频编码SFP光口收发应用…

如何在Ubuntu部署Emlog,并将本地博客发布至公网可远程访问

文章目录 前言1. 网站搭建1.1 Emolog网页下载和安装1.2 网页测试1.3 cpolar的安装和注册 2. 本地网页发布2.1 Cpolar临时数据隧道2.2.Cpolar稳定隧道&#xff08;云端设置&#xff09;2.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 3. 公网访问测试总结 前言 博客作为使…

Flask数据库操作-Flask-SQLAlchemy

Flask中一般使用flask-sqlalchemy来操作数据库。flask-sqlalchemy的使用介绍如下&#xff1a; 一、SQLAlchemy SQLALchemy 实际上是对数据库的抽象&#xff0c;让开发者不用直接和 SQL 语句打交道&#xff0c;而是通过 Python 对象来操作数据库&#xff0c;在舍弃一些性能开销…

ASO专家绝不会分享的高级应用商店优化秘密!(2)

上回我们讨论了“推广应用内购买、实施应用内事件、如果使用特殊字符&#xff0c;请勿使用全部 30 个字符、新的评级是应用商店优化的货币、 在 Google Play 上&#xff0c;评论中的关键词很重要”等五个方面的内容&#xff0c;接下来小柚将继续和大家分享一些更细节的部分。 …

护眼台灯哪家品牌好?盘点全网五大爆款

护眼台灯能比较好的改善学习环境光线&#xff0c;尤其是现在孩子的近视率比较高&#xff0c;所以特别多家长都会给孩子准备上一盏台灯。但是也有一些消费者反馈护眼台灯购买后的体验不尽人意&#xff0c;甚至使用中还会出现很多负面影响&#xff0c;特别是新闻报道关于护眼台灯…

K8S临时小结

k8s是什么&#xff1f;能解决什么问题&#xff1f; k8s是容器管理平台&#xff0c;一套复杂的开源系统 如何更好的维护pod&#xff0c;k8s第二大要素&#xff08;pod控制器&#xff09; k8s的很多对容器&#xff08;pod&#xff09;管理的高级特性&#xff0c;都是基于控制器…

day51 java面向对象OOP

一、类和对象 1 对象、类 对象&#xff1a;一个实体 对象包含静态特征和动态特征 静态特征&#xff1a;属性&#xff08;实例变量&#xff09; 动态特征&#xff1a;行为&#xff08;实例方法&#xff09; 把具有相同属性和方法的对象分为一类 2 现实对象转化程序的对象…

Project_Euler-04 题解

Project_Euler-04 题解与优化 今天带来欧拉计划的第4题的程序分析与优化。 题目 如何判断回文数 int is_reverse(int n){int x n, sum 0;while (x){sum sum * 10 x % 10;x / 10;}return sum n; }如果为回文数&#xff0c;返回1&#xff0c;否则返回0. 暴力破解 两个循…

07 Redis之持久化(RDB+AOF)

4 Redis持久化 Redis 是一个内存数据库&#xff0c;然而内存中的数据是不持久的&#xff0c;若主机宕机或 Redis 关机重启&#xff0c;则内存中的数据全部丢失。 当然&#xff0c;这是不允许的。Redis 具有持久化功能&#xff0c;其会按照设置以快照或操作日志的形式将数据持…

Nginx缓存相关配置解析

文章目录 前言配置示例proxy_cacheproxy_cache_pathproxy_cache_keyproxy_cache_validproxy_cache_lockproxy_cache_methodsproxy_cache_bypassproxy_no_cacheproxy_cache_min_usesadd_header 可选项 使用示例通过响应头判断是否走缓存 缓存手动删除原博客 前言 客户端需要访问…

第五篇【传奇开心果系列】Python文本和语音相互转换库技术点案例示例:详细解读pyttsx3的`preprocess_text`函数文本预处理。

传奇开心果短博文系列 系列短博文目录Python文本和语音相互转换库技术点案例示例系列 短博文目录前言一、pyttsx3的preprocess_text函数文本预处理基本用法示例代码二、实现更复杂的文本预处理逻辑示例代码三、去除停用词、词干提取示例代码四、词形还原、拼写纠正示例代码五、…

力扣面试150 验证回文串 双指针 Character API

Problem: 125. 验证回文串 文章目录 思路复杂度Code 思路 &#x1f468;‍&#x1f3eb; 参考题解 Character.isLetterorDigit(char c)&#xff1a;判读字符 c 是否是字母或者数字 Character.toLowerCase(char c)&#xff1a;将字符 c 转换为小写字母 复杂度 时间复杂度: …

基于vue的个性化推荐餐饮系统Springboot

项目&#xff1a;基于vue的个性化推荐餐饮系统Springboot 摘要 现代信息化社会下的数据管理对活动的重要性越来越为明显&#xff0c;人们出门可以通过网络进行交流、信息咨询、查询等操作。网络化生活对人们通过网上购物也有了非常大的考验&#xff0c;通过网上进行点餐的人也…

「Java开发指南」MyEclipse如何支持Spring Scaffolding?(三)

在上文中&#xff08;点击这里回顾>>&#xff09;&#xff0c;主要为大家介绍了CRUD Scaffolding&#xff0c;本文将继续介绍应用程序的分层、代码助手等。 MyEclipse v2023.1.2离线版下载 3. 应用程序的分层 应用程序分层是应用程序开发领域中非常常见的体系结构方法…

欲速则不达,慢就是快!

引言 随着生活水平的提高&#xff0c;不少人的目标从原先的解决温饱转变为追求内心充实&#xff0c;但由于现在的时间过得越来越快以及其他外部因素&#xff0c;我们对很多东西的获取越来越没耐心&#xff0c;例如书店经常会看到《7天精通Java》、《3天掌握XXX》等等之类的书籍…

算法沉淀——二叉树中的深搜(leetcode真题剖析)

算法沉淀——二叉树中的深搜 01.计算布尔二叉树的值02.求根节点到叶节点数字之和03.二叉树剪枝04.验证二叉搜索树05.二叉搜索树中第K小的元素06.二叉树的所有路径 二叉树的深度优先搜索是一种遍历二叉树的方法&#xff0c;它通过深度递归的方式探索树的结构。有两种主要形式&am…

【洛谷 P8780】[蓝桥杯 2022 省 B] 刷题统计 题解(贪心算法+模拟+四则运算)

[蓝桥杯 2022 省 B] 刷题统计 题目描述 小明决定从下周一开始努力刷题准备蓝桥杯竞赛。他计划周一至周五每天做 a a a 道题目&#xff0c;周六和周日每天做 b b b 道题目。请你帮小明计算&#xff0c;按照计划他将在第几天实现做题数大于等于 n n n 题? 输入格式 输入一…

机器人内部传感器阅读笔记及心得-位置传感器-光电编码器

目前&#xff0c;机器人系统中应用的位置传感器一般为光电编码器。光电编码器是一种应用广泛的位置传感器&#xff0c;其分辨率完全能满足机器人的技术要求&#xff0c;这种非接触型位置传感器可分为绝对型光电编码器和相对型光电编码器。前者只要将电源加到用这种传感器的机电…