25.<Spring博客系统②(实现JWT令牌登录接口+强制登录+获取用户信息+获取作者信息)>

PS:带删除线的方法

可以使用但是不建议使用(方法提供方说的)

加上@Deprecated注解。就代表这个方法可以使用,但是不建议使用。也就会带删除线了

前言 

对于用户登录。

我们之前的做法都是

1.用户登录,后端验证用户名和密码正确,则存储Session中。把SessionId存储在Cookie中

2.用户再次访问的时候,后端从Cookie中获取SessionId。根据SessionId获取Session。

存在问题:

1.Session丢失:Session存储在服务器内存中,如果服务器重启。那么Session就丢失了

如果用户刚登录成功。服务器进行重启Session丢失。客户端就需要重新登陆了。

2.多机部署的情况 :如果是单机,这台机器只要挂掉(①机器出现问题/②修改代码后服务器重启)。整个服务就挂掉。因此公司通常多机部署。

假如现在有一个客户端,和三个服务器。

①用户登录,用户请求到了服务器1。服务器1存储Session。

②用户访问。请求到了服务器2。服务器2根据用户的SessionId查找Session。但找不到。就会告诉用户未登录。这就出现了bug。

解决办法:

1.数据共享,把Session放在同一个地方。比如redis。

2.把数据放在客户端上。(类似身份证,由公安机关发放。用于每个人的身份校验。(我们把这个“身份证”就称作token令牌))

服务器具备生成令牌和验证令牌的能力

使用令牌技术后

1.用户登录,用户发起登录请求, 经过负载均衡, 把请求转给了第一台服务器, 第一台服务器进行账号密码验证, 验证成功后, 生成一个令牌, 并返回给客户端.
2.客户端收到令牌之后, 把令牌存储起来. 可以存储在Cookie中, 也可以存储在其他的存储空间(比如localStorage)
3.查询操作,用户登录成功之后, 携带令牌继续执行查询操作, 比如查询博客列表. 此时请求转发到了第二台机器, 第⼆台机器会先进行权限验证操作. 服务器验证令牌是否有效, 如果有效, 就说明用户已经执行了登录操作, 如果令牌是无效的, 就说明用户之前未执行登录操作. 

我们将token。也称作令牌。

令牌的优缺点

优点:

解决了集群环境下的认证问题。

减轻服务器的存储压力(无需在服务器存储) 

缺点:

需要自己实现,包括令牌的生成、令牌的传递、令牌的校验。

 一、JWT令牌(一种流行的公共令牌技术)

1.1JWT令牌简介

全称: JSON Web Token(官网)

token令牌其实就是一个字符串。用于校验用户身份。

对上⾯部分的信息, 使用Base64Url 进行编码, 合并在一起就是jwt令牌Base64是编码方式,而不是加密方式 。

 简介:

JWT由三部分组成、每部分中间使用(.)分隔。

①Header(头部):包括令牌类型(JWT)、以及使用的哈希算法(如HMAC、SHA256、RSA)

②Payload(负载):负载部分是存放有效信息的地方。里面是一些自定义内容比如。{"userId":"123","userName":"zhangsan"}。也可以存在jwt提供的现场字段, 比如exp(过期时间戳)等.此部分不建议存放敏感信息, 因为此部分可以解码还原原始内容.

③Signature(签名):此部分用于防止jwt内容被篡改, 确保安全性。

注:防止被篡改, 而不是防止被解析.

WT之所以安全, 就是因为最后的签名. jwt当中任何一个字符被篡改, 整个令牌都会校验失败.
好比我们的身份证, 之所以能标识一个⼈的⾝份, 是因为他不能被篡改, 二不是因为内容加密.(任何人都可以看到身份证的信息, jwt 也是)

1.2JWT令牌的使用

1.2.1引入依赖

        <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version></dependency><!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred--><version>0.11.5</version><scope>runtime</scope></dependency>

 1.2.2使用Jar包中提供的API来完成JWT令牌的生成和校验 

1.在Test中创建JWTUtilsTest类

如果需要从Spring容器中获取一些信息。则加上SpringBootTest注解。

而我们这里不用。

定义常量

    //过期时间:设置为一小时后过期private final static long EXPIRATION_DATE = 60 * 1000;private final static String secretString = "sqmbcvcBPjfvJ4ilRLqbGmHeUaCEwdpv10jSRbCNtH4=";private final static Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString));//生成key

生成token令牌

    @Testpublic void gentToken(){Map<String,Object> claim = new HashMap<>();claim.put("id",5);claim.put("name","王五");String token = Jwts.builder().setClaims(claim) //设置头部和荷负载.setExpiration(new Date(System.currentTimeMillis()+EXPIRATION_DATE)).signWith(key) //设置签名.compact();System.out.println(token);}

 随机生成key(标签)

    /*** 随机生成Key.(标签)*/@Testpublic void genKey(){SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);String encode = Encoders.BASE64.encode(secretKey.getEncoded());System.out.println(encode);}

解析Token 

    /*** 解析Token*/@Testpublic void parseToken(){String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi546L5LqUIiwiaWQiOjUsImV4cCI6MTczMTQ3MDYxMX0.s-6fv04cAt_8IY-BDScfzzqq-XtuEZ4THuqj_ekw824";JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();Claims body = build.parseClaimsJws(token).getBody();System.out.println(body);}

二、登录接口的实现(使用JWT令牌技术)

2.1在utils包创建JWTUtils类

@Slf4j
public class JWTUtils {//过期时间:设置为一小时后过期private final static long EXPIRATION_DATE = 60 * 60 * 1000;private final static String secretString = "sqmbcvcBPjfvJ4ilRLqbGmHeUaCEwdpv10jSRbCNtH4=";private final static Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString));//生成key/*** 生成token令牌*/public static String gentToken(Map<String,Object> claim){return Jwts.builder().setClaims(claim) //设置头部和荷负载.setExpiration(new Date(System.currentTimeMillis()+EXPIRATION_DATE)).signWith(key) //设置签名.compact();}/*** 随机生成Key.(标签)* 由于我们已经生成了因此不需要这个代码了*/
//    public void genKey(){
//        SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
//        String encode = Encoders.BASE64.encode(secretKey.getEncoded());
//        System.out.println(encode);
//    }/*** 解析Token*/public static Claims parseToken(String token){Claims body = null;JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();try {body = build.parseClaimsJws(token).getBody();} catch (ExpiredJwtException e) {log.info("token过期,校验失败,token:{}",token);} catch (Exception e) {log.info("token校验失败,token:{}",token);}return body;}public static boolean checkToken(String token){Claims body = parseToken(token);if(body == null){return false;}return true;}

 2.2实现后端接口

    @Autowiredprivate UserService userService;@RequestMapping("/login")public Result login(String userName, String password){//1.参数校验//2.对密码进行校验//3.如果校验成功,生成tokenif(!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)){return Result.fail("用户名或密码不能为空!");}UserInfo userInfo = userService.queryUserByName(userName);if(userInfo == null || userInfo.getId() <= 0){return Result.fail("用户不存在");}if(!password.equals(userInfo.getPassword())){return Result.fail("密码错误!");}//密码正确Map<String,Object> claim = new HashMap<>();claim.put("id",userInfo.getId());claim.put("name",userInfo.getUserName());return Result.success(JWTUtils.gentToken(claim));}

2.3实现前端接口

客户端可以把Token放在哪里呢?

1.Cookie(推荐,但实现较复杂)

2.本地存储(推荐,实现简单)(下面代码使用这个)

3.url中(一般不这样用) 

使用

localStorage.setItem("user_token",result.data)

存储token。登录后按f12。点击应用程序,找到本地存储。就能看到我们存储的token了

        function login() {$.ajax({type: "post",url: "user/login",data: {"userName": $("#username").val(),"password": $("#password").val()},success:function(result){if(result.code == 200 && result.code != null){//存储TokenlocalStorage.setItem("user_token",result.data)location.href = "blog_list.html"}else if(result.errMsg == "用户名或密码不能为空!"){alert("用户名或密码不能为空!");}else if(result.errMsg == "用户不存在"){alert("用户不存在");}else if(result.errMsg == "密码错误!"){alert("密码错误!");}//不为200如何处理.....}});}

成功登录! 

三、强制登录(拦截器)

1.客户端访问时,携带token(token通常放在Header中)

2.服务器获取token,验证token,如果token校验成功,放行。否则跳转到登录页面。

客户端返回token 

在定义拦截器之前。我们需要从客户端获得token。若token存在且校验正确。那么放行。不然进行拦截

$(document).ajaxSend(function(e,xhr,opt){var user_token = localStorage.getItem("user_token")xhr.setRequestHeader("user_token_header",user_token)
});
//放在common.js中,这时候所有引入common.js的页面都会执行这个代码。

 //放在common.js中,这时候所有引入common.js的页面都会执行这个代码。

每当发起ajax请求。就会执行这个方法。ajaxSend

这样我们向后端发送token。将这个变量名命名为user_token_header。

登录状态失效(提示后跳转到登录状态) 

放在common.js中,这时候所有引入common.js的页面都会执行这个代码。

每当发起ajax请求。如果请求发生错误。就会执行这个方法。ajaxError

$(document).ajaxError(function(event, jqxhr, settings, thrownError) {// 检查是否是未授权错误if (jqxhr.status === 401) {alert("登录已失效,请重新登录!");window.location.href = "/blog_login.html";} else {// 处理其他错误console.error("AJAX 请求出错,状态码:", jqxhr.status);alert("请求出错,请稍后再试!");}
});

3.1自定义拦截器

1.创建interceptor包。创建LoginInterceptor类,加上@Component注解

2.实现HandlerInterceptor接口,

3.重写preHandle方法{

//1.从handler中获取请求 //2.校验token //3.成功放行

}

/*** 用户登录拦截器*/
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.从handler中获取请求//2.校验token//3.成功放行String userToken = request.getHeader("user_token_header");log.info("获得token,token:{}",userToken);boolean result = JWTUtils.checkToken(userToken);if(result){return true;}response.setStatus(401);return false;}
}

3.2注册拦截器配置

1.创建config包。创建WebConfig implements WebMvcConfigurer。

2.注册拦截器

3.放入拦截内容以及不拦截的内容

/*** 注册拦截器并配置拦截路径*/
@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/**/*.html","/blog-editormd/**","/css/**","/js/**","/pic/**","/user/login");}
}

1.注意对应关系,这两个名称可以一样。

我们完全可以写成一样的。

2.common.js的引用。必须放在jQuery.min.js的下面

ajaxSend是基于JQuery实现的。因此common.js的引用。必须放在

jQuery.min.js的下面。如果调整顺序。会导致运行不成功。

    <script src="js/jquery.min.js"></script><script src="js/common.js"></script>

四、获得当前用户信息接口

根据token,获得用户信息

后端代码

controller

    @RequestMapping("getUserInfo")public UserInfo getUserInfo(HttpServletRequest request){//1.获取token,从token中获取Id//2.根据Id,获得用户信息String user_token = request.getHeader(Constant.USER_TOKEN_HEADER);Integer userId = JWTUtils.getUserIdFromToken(user_token);if(userId == null || userId<=0){return null;}return userService.queryUserById(userId);}

Service 

    public UserInfo queryUserById(Integer userId) {return userMapper.selectById(userId);}

Mapper

    @Select("select *from user where id = #{userId} and delete_flag = 0")UserInfo selectById(Integer userId);

测试的时候。注意加上Header信息。也就是token。 

我们发现password返回不太合适。因此可以进行处理。

    @RequestMapping("getUserInfo")public UserInfo getUserInfo(HttpServletRequest request){//1.获取token,从token中获取Id//2.根据Id,获得用户信息String user_token = request.getHeader(Constant.USER_TOKEN_HEADER);Integer userId = JWTUtils.getUserIdFromToken(user_token);if(userId == null || userId<=0){return null;}UserInfo userInfo = userService.queryUserById(userId);userInfo.setPassword("");return userInfo;}

 企业开发中并不会出现这种情况。

企业中。接口返回的实体类是单独定义的。并不是我们项目中使用的那个,

通过解耦的思想。我们返回的接口数据。并不是Userinfozhong

比如接口中返回用户信息。会重新定义一个比如UserInfoApi。

这个实体类。和UserInfo是对应的。UserInfoApi 是接口需要什么。设置什么

而UserInfo是与数据库对应的。

前端代码

    <div class="container"><div class="left"><div class="card"><img src="pic/doge.jpg" alt=""><h3></h3><a href="#">GitHub 地址</a>
        getUserInfo();function getUserInfo(){$.ajax({type: "get",url: "/user/getUserInfo",success:function(result){if(result.code == 200 && result.data!=null){$(".left .card h3").text(result.data.userName);$(".left .card a").attr("href",result.data.githubUrl);}}});}
 ".left .card a"

.card前面的空格一定要加上

 成功显示

五、 获取作者信息接口

根据博客Id。获取作者Id

根据作者Id。获取作者信息。

后端代码 

Controller

    @RequestMapping("/getAuthorInfo")public UserInfo getAuthorInfo(Integer blogId){//1.根据博客Id,获取作者Id//2.根据作者Id,获取作者信息if(blogId != null && blogId< 1){return null;}UserInfo authorInfoByBlogId = userService.getAuthorInfoByBlogId(blogId);authorInfoByBlogId.setPassword("");return authorInfoByBlogId;}

Service

    public UserInfo getAuthorInfoByBlogId(Integer blogId) {//1.根据博客Id,获取作者Id//2.根据作者Id,获取作者信息BlogInfo blogInfo = blogMapper.selectById(blogId);if(blogInfo ==null || blogInfo.getUserId()<1){return null;}return userMapper.selectById(blogInfo.getUserId());}

Mapper

    @Select("select *from user where id = #{userId} and delete_flag = 0")UserInfo selectById(Integer userId);

使用Postman测试一下

 

前端代码 

    <div class="container"><div class="left"><div class="card"><img src="pic/doge.jpg" alt=""><h3></h3><a href="#">GitHub 地址</a>
        //显示博客作者信息
/*         var userUrl = "/user/getAuthorInfo" + location.search;getUserInfo(userUrl); */getUserInfo();function getUserInfo(){$.ajax({type: "get",url: "/user/getAuthorInfo"+location.search,success:function(result){if(result.code == 200 && result.data!=null){$(".left .card h3").text(result.data.userName);$(".left .card a").attr("href",result.data.githubUrl);}}});}

最终成功显示 

代码整合 

我们发现四、五这两个接口的前端代码几乎一样。

因此我们可以把这个代码放在common.js文件中。

function getUserInfo(url){$.ajax({type: "get",url: url,success:function(result){if(result.code == 200 && result.data!=null){$(".left .card h3").text(result.data.userName);$(".left .card a").attr("href",result.data.githubUrl);}}});
}

接着在前端响应的接口处调用这个方法。传入参数就行了。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《Python编程实训快速上手》第七天--文件与文件路径

该章节将使用Python在硬盘上创建、读取和保存文件 一、文件与文件路径 1、Windows中使用\以及macOS和Linux中使用/ 使用pathlib模块中的Path()函数进行文件名和目录的拼接,返回文件路径字符串 from pathlib import Path print(Path("spam","bacon",&qu…

Oracle RAC仲裁交换机的小科普

一、Oracle RAC仲裁交换机的主要功能 Oracle RAC仲裁交换机是用于Oracle Real Application Clusters&#xff08;真实应用集群&#xff0c;简称RAC&#xff09;环境中的一种网络设备。它主要用于在集群节点之间进行通信&#xff0c;以确保集群的高可用性和故障切换能力。以下是…

Leetcode 快乐数

算法思想&#xff1a; 这段代码的目的是判断一个正整数是否是 快乐数&#xff08;Happy Number&#xff09;。根据题目要求&#xff0c;快乐数定义如下&#xff1a; 对于一个正整数&#xff0c;不断将它每个位上的数字替换为这些数字平方和。重复这个过程&#xff0c;如果最终…

使用 Vue 和 Create-Vue 构建工程化前端项目

目录 前言1. 工程化的意义与 Vue 的生态支持2. 搭建 Vue 工程化项目2.1 环境准备2.2 使用 create-vue 创建项目2.2.1 初始化项目2.2.2 安装依赖2.2.3 本地运行 3. Vue 项目的目录结构解析4. Vue 开发流程详解4.1 项目入口与根组件4.1.1 main.js 的作用4.1.2 App.vue 的结构 4.2…

【MySQL实战45讲笔记】基础篇——redo log 和 binlog

系列文章 基础篇——MySQL 的基础架构 目录 系列文章1. 重要的日志模块&#xff1a;redo log 和 binlog1.1 redo log1.2 binlog1.3 执行器和 InnoDB 引擎内部如何执行更新语句 1. 重要的日志模块&#xff1a;redo log 和 binlog 前面系统的了解了一个查询语句的执行流程&…

C++ lambda(匿名函数)捕获自己

今天写算法题时无意间遇到一种情况,我的深度优先遍历函数要在函数内调用自身,如果是普通函数没什么问题,但如果是 匿名函数 的话会有一些问题,甚至问ai,ai也没打上来,上网搜了半天,才找到这个的解答,故作此文 以费契那波数列为例 // 普通函数式 int fun(int pos) {if (pos …

DAO模式

前言 DAO&#xff08;Data Access Object&#xff09;模式 是一种常用的设计模式&#xff0c;主要用于将数据访问逻辑与业务逻辑分离。它提供了一种抽象层&#xff0c;使得应用程序可以与不同的数据源&#xff08;如数据库、文件系统等&#xff09;进行交互&#xff0c;而无需…

mysql日志写满出现The table ‘xxxx_amazon_order’ is full

数仓发现写数据出现 SQL 错误 [1114] [HY000]: The table ‘xxxx_amazon_order’ is full 1.第一时间查看系统磁盘, 发现空间写满了 df -h因为mysql是使用docker部署的, Docker 的默认存储位置在 /var/lib/docker /var 目录默认是在根分区 (/dev/mapper/centos-root) 下的 …

【读书笔记-《网络是怎样连接的》- 7】Chapter3_2 路由器

本篇继续介绍路由器及其转发过程。 1 路由器内部结构 路由器内部结构图如图所示。 即主要包含左侧的包转发模块和右侧的端口模块。转发模块负责查找包的发送目的地&#xff0c;端口模块完成包的发送。通过安装不同的硬件&#xff0c;转发模块不仅可以支持以太网&#xff0c;也…

P5099 [USACO04OPEN] Cave Cows 4

P5099 [USACO04OPEN] Cave Cows 4https://www.luogu.com.cn/problem/P5099 思路&#xff1a; 这里的垫蹄石之间很明显是有后效性的 所以不能用dp来做 考虑宽搜 我们每次都枚举和这个垫蹄石之间x方向和z方向的距离均不超过2的垫脚石 因为都很大 我们可以使用 代码&#xf…

高阶C语言之六:程序环境和预处理

本文介绍程序的环境&#xff0c;在Linux下对编译链接理解&#xff0c;较为简短&#xff0c;着重在于编译的步骤。 C的环境 在ANSI C&#xff08;标准C语言&#xff09;的任何一种实现中&#xff0c;存在两个不同的环境。 翻译环境&#xff1a;在这个环境中&#xff0c;源代码…

【Python数据可视化分析实战】数据爬取—京东手机品牌信息数据爬取和数据分析与可视化

大数据分析设计方案 1.数据集来源&#xff1a;https://search.jd.com 2.实现思路&#xff1a; &#xff08;1&#xff09;数据爬取 首先&#xff0c;我们需要从京东平台上采集手机品牌的相关数据。可以通过网络爬虫或API接口等方式获取数据。为了保证数据的完整性和准确性&…

【MySQL-4】表的基本查询

目录 1. 整体学习的思维导图 2. 表的创建 2.1 Create(创建) 2.1.1 插入规则 2.1.2 更新插入 2.2 Retrieve(读取) 2.2.1 创建一个实例表 2.3 select使用 2.3.1 全表查询 2.3.2 指定序列查询 2.3.3 查询表达式 2.3.3.1 为查询表达式改名字 2.3.4 查询去重 2.…

无人机航测技术算法概述!

一、核心技术 传感器技术&#xff1a; GPS/GLONASS&#xff1a;无人机通过卫星定位系统实现高精度的飞行控制和数据采集。 高清相机&#xff1a;用于拍摄地面图像&#xff0c;通过后续图像处理生成三维模型。 激光雷达&#xff08;LiDAR&#xff09;&#xff1a;通过激光扫…

uniapp 自定义加载组件,全屏加载,局部加载 (微信小程序)

效果图 全屏加载 页面加载使用 局部加载 列表加载里面使用 使用gif html <template><view><view class"" v-if"typeFullScreen"><view class"loading" v-if"show"><view class""><i…

【D3.js in Action 3 精译_040】4.4 D3 弧形图的绘制方法

当前内容所在位置&#xff1a; 第四章 直线、曲线与弧线的绘制 ✔️ 4.1 坐标轴的创建&#xff08;上篇&#xff09; 4.1.1 D3 中的边距约定&#xff08;中篇&#xff09;4.1.2 坐标轴的生成&#xff08;中篇&#xff09; 4.1.2.1 比例尺的声明&#xff08;中篇&#xff09;4.1…

element ui 走马灯一页展示多个数据实现

element ui 走马灯一页展示多个数据实现 element ui 走马灯一页展示多个数据实现 element ui 走马灯一页展示多个数据实现 主要是对走马灯的数据的操作&#xff0c;先看js处理 let list [{ i: 1, name: 1 },{ i: 2, name: 2 },{ i: 3, name: 3 },{ i: 4, name: 4 },]let newL…

使用MaxKB搭建知识库问答系统并接入个人网站(halo)

首发地址&#xff08;欢迎大家访问&#xff09;&#xff1a;使用MaxKB搭建知识库问答系统并接入个人网站 前言 从OpenAI推出ChatGPT到现在&#xff0c;大模型已经渗透到各行各业&#xff0c;大模型也逐渐趋于平民化&#xff1b;从最开始对其理解、生成、强大的知识积累的惊叹&…

Linux进阶:软件安装、网络操作、端口、进程等

软件安装 yum 和 apt 均需要root权限 CentOS系统使用&#xff1a; yum [install remove search] [-y] 软件名称 install 安装remove 卸载search 搜索-y&#xff0c;自动确认 Ubuntu系统使用 apt [install remove search] [-y] 软件名称 install 安装remove 卸载search 搜索-y&…

如何解决飞书网页文字无法复制的问题

如何解决网页文字无法复制的问题&#xff1f;特别推荐提词宝防复制文案功能&#xff01; 在日常工作和学习中&#xff0c;我们经常遇到一些网页文字无法复制的情况&#xff0c;无论是因为权限限制还是其他原因&#xff0c;手动输入内容不仅耗时费力&#xff0c;还容易出错。那…