分布式环境认证和授权-基于springboot+JWT+拦截器实现-实操+源码下载

news2024/9/20 0:57:32

1、功能概述?

1、当用户登录的时候,将用户的信息通过JWT进行加密和签名,并将JWT产生了token信息保存到当前浏览器的localStoragee中,即本地存储中。

2、当用户登录成功后,访问其他资源的时候,程序从localStorage中获取token的信息,并将token的信息存入到请求头中。

3、拦截器拦截当前的请求,并从请求中获取到token的值,使用JWT进行验证,如果验证表示当前用户合法就放行,如果验证不通过表示不合法,不放行。

2、JWT概述

1、JSON WebToken(JWT)是一种紧凑、自包含方式的、遵循RFC 7519开放标准,是一种协议。

2、JWT中的声明(如何用户名/编号等信息)被编译成JSON对象,并且这些信息会经过数字签名,信息可以进行验证和信任。

3、JWT支持以下签名和验证算法: HMAC、RSA 或 ECDSA。

4、JSON WebToken是常用的跨域身份验证方案。

5、JWT生成的token内容可以被解析,因为采用的是base64算法,但是使用了签名所以数据不能被篡改,敏感信息不能放入其中如密码,放置信息泄露。

2.1、JWT优点

1、以json行书传输,数据量小,传输速度快且JWT是跨语言的。

2、适用于分布式和微服务架构,因为信息的保存不依赖于session或者cookie.

3、使用cookie不适合移动端、但是JWT既不依赖session也不依赖cookie,单点登录实现容易。

4、因此无论是单体结构还是分布式都可以使用JWT进行身份认证。

2.2、JWT的组成结构

我们可以使用官网:https://github.com/auth0/java-jwt,学习JWT相关的结构

JWT主要有三部分组成:标头(Header)+有效载荷(Payload)+签名(Signature)

格式通常为:Header.Payload.Signature

【第一部分:Header

由令牌类型和签名算法组成

【第二部分:Payload

有效负载,通常定义用户自定义信息,这部分构成JWT的第二部分数据,是推荐使用的,而非强制。主要包括

iss:发行人或签发者

exp:到期时间

sub:主题

aud:用户/jwt接收方

nbf:在此之前不可用

iat:发布时间/签发时间

jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击   

【第三部分:Signature

由于Header和Payload都是使用Base64进行编码,是可逆的,因此信息可以被解析出来,为了放置信息被篡改,加入了签名。同时如果提供的签名不正确,jwt生成的token不能被正确解析。

通过jwt.io这个网站观看jwt生成的token样式

2.3、JWT使用的大体流程

1、在登录验证通过后,给用户生成一个对应的随机token(注意这个token不是指jwt,可以用uuid等算法生成),然后将这个token作为key的一部分,用户信息作为value存入Redis,并设置过期时间,这个过期时间就是登录失效的时间;

将第1步中生成的随机token作为JWT的payload生成JWT字符串返回给前端;

前端之后每次请求都在请求头中的Authorization字段中携带JWT字符串;

后端定义一个拦截器,每次收到前端请求时,都先从请求头中的Authorization字段中取出JWT字符串并进行验证,验证通过后解析出payload中的随机token,然后再用这个随机token得到key,从Redis中获取用户信息,如果能获取到就说明用户已经登录。

3、使用JWT实现签发token/校验token/获取token信息

3.1、项目结构

3.2、创建springboot工程映入jwt的包信息

我们使用java-jwt实现

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.txc</groupId>
    <artifactId>distribute-session-jwt</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>distribute-session-jwt</name>
    <description>distribute-session-jwt</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>4.4.0</version>
        </dependency>

    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <image>
                        <builder>paketobuildpacks/builder-jammy-base:latest</builder>
                    </image>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

3.3、创建login测试类签发token

注意:理论上,签名可以通过穷举法进行破除,所以一般签名不要使用的过于简单或者定期更换签名,增强系统的安全性。

程序中通过jwt.create进行签发,并设置了签名信息,过期时间,及相关的用户信息。

@RestController
public class JWTController {
    @RequestMapping("/login")
public String login(@RequestParam String username, HttpServletResponse response, HttpServletRequest request){
    //创建map存放用户信息
    Map<String,Object> map=new HashMap<>();
    map.put("userid","1001");
    map.put("username",username);
    String token=null;
    try {
        //!@#$%^&123~:是我们使用的签名
        Algorithm algorithm = Algorithm.HMAC256("!@#$%^&123~");
        token = JWT.create()
                .withIssuer("auth0")//角色权限
                .withClaim("userinfo",map)
                //设置token的过期时间为一个小时
                .withExpiresAt(new Date(System.currentTimeMillis()+3600000))
                .sign(algorithm);
        //将token信息添加到token中
    } catch (JWTCreationException exception){
        System.out.println("=====程序异常=======");
    }
    return token;
}
}

3.4、访问login后结果如下

3.5、解析之后效果如下

将浏览器生成的token的信息,拷贝到JSON Web Tokens - jwt.io地址中解析如下:

3.6、创建getLoginToken实现校验和获取token信息

此处为了测试方便,我们是直接手动的将token的值拷贝过来,放在地址栏中传递到该方法,实际的项目中,会将token的信息存放到请求的header中,用户通过header获取。

由于JWT是在请求头中传递的,所以为了避免网络劫持,推荐使用HTTPS来传输,更加安全

请求地址:localhost:8080/getLoginToken?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCIsInVzZXJpbmZvIjp7InVzZXJpZCI6MTAwMSwidXNlcm5hbWUiOiJ4aWFvY2h1biJ9LCJleHAiOjE3MDE4ODMwNjd9.ceGcf1Q_TG26HFCBf20E0TIoSXlJYK7gudAvfKOjUlw

说明1校验的时候签名千万不能写错了,否则会提示校验失败。

说明2如果抛出异常说明校验失败

说明3注意获取值的返回值结果

@RequestMapping("/getLoginToken")
public String getLoginToken(@RequestParam String token){
    //验证一个token
    try{
        Algorithm algorithm=Algorithm.HMAC256("!@#$%^&123~");
        JWTVerifier verifier=JWT.require(algorithm)
                .withIssuer("auth0")
                .build();
        //jwt通过verify进行校验
        DecodedJWT jwt=verifier.verify(token);
        //如果校验成功就获取userinfo的信息
        Map<String,Object> userinfo=jwt.getClaim("userinfo").asMap();
        //获取userinfo中的username值并转化成string
        String username=userinfo.get("username").toString();
        int userid=Integer.valueOf(userinfo.get("userid").toString());
        //获取token信息的有效期
        Date exp=jwt.getExpiresAt();
        return username+"==="+userid+"==="+exp;
    }catch(Exception e){
        e.printStackTrace();
        return "校验失败";
    }
}

返回结果

4、拦截器统一处理token

案例实现当用户登录成功后,将当前token信息保存到浏览器本地存储位置。当再次在项目中发起请求的时候,从本地不去到token,将token信息保存到请求头中,拦截器获取到请求头中的token信息,如果验证成功就允许访问资源,如果验证失败重定向到登录页中。

4.1、在springboot中创建拦截器

1、下面的拦截器主要实现,如果没有从请求头中获取到token就抛出异常,程序不放行。如果获取了token的值,但是验证没有通过不放行。

2、DecodedJWT jwt=verifier.verify(token);就是验证方法,如果验证不通过会抛出异常。
3、hasText判断字符串是否为null用法:

   https://blog.csdn.net/tangshiyilang/article/details/134926299

@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println(request.getMethod()+"============拦截器执行===============");

       String token=request.getHeader("token");
       if(!StringUtils.hasText(token)){
           throw new TokenIsNullException();
       }
        try{
            //验证一个token
            Algorithm algorithm=Algorithm.HMAC256("!@#$%^&123~");
            JWTVerifier verifier= JWT.require(algorithm)
                    .withIssuer("auth0")
                    .build();
            DecodedJWT jwt=verifier.verify(token);
            String username=jwt.getClaim("username").asString();
            request.setAttribute("username",username);
        }catch(Exception e){
            return false;
        }
        return true;
    }
}

4.2、在工程中加载拦截器

实现WebMvcConfigurer接口,重写addInterceptors方法。

addInterceptor:表示需要加载的拦截器

addPathPatterns:需要拦截的地址

excludePathPatterns:不需要拦截的地址,如果拦截login地址会出现死循环的情况。

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/test.do")
                .excludePathPatterns("/login");
    }

}

4.3、创建自定义异常类

public class TokenIsNullException extends RuntimeException{

    public TokenIsNullException(){
        super("token为空!");
    }
}

4.4、创建登录使用的接口login1

说明1:为了便于测试,之前的login方法我们保留了下来,单独又写了一个login1用于当前的测试。

@RequestMapping("/login1")
@ResponseBody
public String login1(@RequestParam String username, HttpServletResponse response, HttpServletRequest request){
    System.out.println("=======login==========="+username);
    //创建map存放用户信息
    Map<String,Object> map=new HashMap<>();
    map.put("userid",1001);
    map.put("username",username);
    String token=null;
    try {
        //!@#$%^&123~:是我们使用的签名
        Algorithm algorithm = Algorithm.HMAC256("!@#$%^&123~");
        token = JWT.create()
                .withIssuer("auth0")//角色权限
                .withClaim("userinfo",map)
                //设置token的过期时间为一个小时
                .withExpiresAt(new Date(System.currentTimeMillis()+3600000))
                .sign(algorithm);
    } catch (JWTCreationException exception){
        System.out.println("=====程序异常=======");
        return "0";//如果返回值为0,表示失败。
    }
    return "1";//如果返回值为1,表示成功。
}

4.5、创建test.do

这个方法主要用于模拟登录成功后,我们需要访问的资源,拦截器会获取请求中的token,如果验证成功允许访问,否则不允许访问。

@RequestMapping("/test.do")
@ResponseBody
public String test(){
    System.out.println("=====test.do========");
    return "恭喜你资源访问成功。";
}

4.6、创建登录页实现登录

说明1:localStorage.setItem("token",msg2);使用jquery向本地存储数据
说明2:window.location.href="show.html";//登录成功后,重定向到show.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="js/jquery-2.1.1.js"></script>
    <script type="text/javascript">
        function login(){
            $.ajax({
                type:"get",
                url:"/login",
                cache:false,
                dataType:"text",
                data: {"username":"xiaochun"},
                success:function(msg2,request){
                    alert("==msg2==="+msg2);
                    //存储字段
                    localStorage.setItem("token",msg2);
                    //重定向到主页
                    window.location.href="show.html";
                }
            });
        }
    </script>
</head>
<body>
      <input type="text" name="username"> <br>
      <button onclick="login()">登录</button>
</body>
</html>

登录页样式:

4.7、创建show.html页面

如果登录成功后,页面会重定向到show.html中。这个时候点击a标签,触发getUserToken函数访问test.do资源,在访问资源的同时会从本地获取token信息,放入到请求头中。这个时候请求会被拦截器拦截,拦截器获取请求头中的token信息,如果验证成功允许访问test.do,如果验证失败拒绝访问。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
  <script src="js/jquery-2.1.1.js"></script>
  <script type="text/javascript">
    function getUserToken(){
      //获取保存在localStore中的数据
      var mytoken=localStorage.getItem("token");
      alert("===token==="+token);
      $.ajax({
        type:"post",
        url:"/test.do",
        headers: {'token':mytoken},
        success:function(msg2){
          //存储字段
          localStorage.setItem("token",msg2);
          //重定向到主页
          window.location.href="show.html";
        }
      });
    }
  </script>
</head>
<body>
   我是主页 <br>
   <a href=" javaScript:void(0)"  onclick="getUserToken()">发起请求访问test.do资源</a>
</body>
</html>

4.8、登录成功后的信息

登录成功后重定向到show.html.

4.9、通过show.html访问test.do资源

当我们点击”发起请求访问test.do资源”,经过拦截器校验token成功,返回成功访问,且从请求头中可以看到token的信息。

4.10、手动清除token信息,再次访问

从下面的地址可以看出,test.do没有成功访问,拦截器没有放行。

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

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

相关文章

linux(4):linux基础命令第三弹

在linux基础命令第二弹中http://t.csdnimg.cn/JPNYY我们讲了有关路径&#xff0c;创建目录和文件、文件夹&#xff0c;以及如何查看文件内容的问题&#xff0c;第三弹我们将学习有关文件操作和查找以及过滤关键字、展示文件字节&#xff0c;行数的命令&#xff0c;还有一个很重…

【程序员的自我修养04】目标文件生成可执行文件过程

绪论 大家好&#xff0c;欢迎来到【程序员的自我修养】专栏。正如其专栏名&#xff0c;本专栏主要分享学习《程序员的自我修养——链接、装载与库》的知识点以及结合自己的工作经验以及思考。编译原理相关知识本身就比较有难度&#xff0c;我会尽自己最大的努力&#xff0c;争…

.Net中的集合

所有的集合都是继承自IEnumerable。集合总体可以分为以下几类&#xff1a;关联/非关联型集合&#xff0c;顺序/随机访问集合&#xff0c;顺序/无序集合&#xff0c;泛型/非泛型集合&#xff0c;线程集合。 各集合类底层接口关系图 泛型与非泛型集合类的分析 泛型集合是类型安…

自动化测试基础知识:什么是自动化测试?需要学习哪些知识与工具!

1、自动化测试概念 自动化测试是把以人为驱动的测试行为转化为机器执行的一种过程。通常&#xff0c; 在设计了测试用例并通过评审之后&#xff0c;由测 试人员根据测试用例中描述的规程一步步执行测试&#xff0c;得到实际结果与期望结果的比较。简言之&#xff0c;自动化测试…

【操作系统导论】比例份额调度

本文介绍一种 比例份额&#xff08;proportional-share&#xff09; 调度程序&#xff0c;也称为 公平份额&#xff08;fair-share&#xff09;。 彩票调度 简介 彩票调度 的基本思想&#xff1a; 每隔一段时间&#xff0c;都会举行一次彩票抽奖&#xff0c;以确定接下来应该…

【上海大学数字逻辑实验报告】六、时序电路

一、 实验目的 掌握同步二进制计数器和移位寄存器的原理。学会用分立元件构成2位同步二进制加计数器。学会在Quartus II上设计单向移位寄存器。学会在Quartus II上设计环形计数器。 二、 实验原理 同步计数器是指计数器中的各触发器的时钟脉冲输入端连接在一起&#xff0c;接…

做题总结 707. 设计链表

做题总结 707. 设计链表 leetcode中单链表节点的默认定义我的尝试正确运行的代码&#xff08;java&#xff09; leetcode中单链表节点的默认定义 class ListNode {int val;ListNode next;//无参public ListNode() {}//有参:1public ListNode(int val) {this.val val;}//有参:…

【项目小结】优点分析

一、 个人博客系统 一&#xff09;限制强制登录 问题&#xff1a;限制用户登录后才能进行相关操作解决&#xff1a; 1&#xff09;前端&#xff1a; ① 写一个函数用于判断登录状态&#xff0c;如果返回的状态码是200就不进行任何操作&#xff0c;否则Ajax实现页面的跳转操作…

Apollo配置发布原理解析

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;2022年度博客之星全国TOP3&#xff0c;专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化&#xff0c;文章内容兼具广度、深度、大厂技术方案&#xff0c;对待技术喜欢推理加验证&#xff0c;就职于…

Windows下查看删除某一个端口号

背景&#xff1a;Java项目运行时&#xff0c;提示端口号被占用&#xff0c;然后就忘记之前是怎么处理的了&#xff0c;感觉还是像Linux中杀掉端口号就命令行的方式比较简单一些&#xff0c;然后就是各种搜索&#xff0c;记录一下 第一步&#xff1a;在cmd中查看该端口号是否被…

最强文生图跨模态大模型:Stable Diffusion

文章目录 一、概述二、Stable Diffusion v1 & v22.1 简介2.2 LAION-5B数据集2.3 CLIP条件控制模型2.4 模型训练 三、Stable Diffusion 发展3.1 图形界面3.1.1 Web UI3.1.2 Comfy UI 3.2 微调方法3.1 Lora 3.3 控制模型3.3.1 ControlNet 四、其他文生图模型4.1 DALL-E24.2 I…

Nginx的location匹配和rewrite重写

一、location匹配 常用的正则表达式 ^ &#xff1a;匹配输入字符串的起始位置 $ &#xff1a;匹配输入字符串的结束位置 * &#xff1a;匹配前面的字符零次或多次。如“ol*”能匹配“o”及“ol”、“oll”&#xff1a;匹配前面的字符一次或多次。如“ol”能匹配“ol”及“oll…

MySQL笔记-第14章_视图

视频链接&#xff1a;【MySQL数据库入门到大牛&#xff0c;mysql安装到优化&#xff0c;百科全书级&#xff0c;全网天花板】 文章目录 第14章_视图1. 常见的数据库对象2. 视图概述2.1 为什么使用视图&#xff1f;2.2 视图的理解 3. 创建视图3.1 创建单表视图3.2 创建多表联合视…

C++ exception类:C++标准异常的基类

C语言本身或者标准库抛出的异常都是 exception 的子类&#xff0c;称为标准异常&#xff08;Standard Exception&#xff09;。你可以通过下面的语句来捕获所有的标准异常&#xff1a; try{//可能抛出异常的语句}catch(exception &e){//处理异常的语句} 之所以使用引用&a…

GeoPandas实操:读取数据

GeoPandas 支持读取和写入多种地理空间数据格式&#xff0c;如 ESRI Shapefile、GeoJSON、GeoPackage 等&#xff0c;以及与其他 GIS 软件兼容的格式。 1. 读取数据 1.1. 读取ESRI Shapefile数据 ESRI Shapefile&#xff08;简称 Shapefile 或 .shp 文件&#xff09;是一种常…

Pandas中的Series(第1讲)

Pandas中的Series(第1讲)         🍹博主 侯小啾 感谢您的支持与信赖。☀️ 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔…

PHP操作ZIP之ZipArchive类以及如何避免生成压缩文件带有目录层级的问题

常用的方法 php ZipArchive可以说是php自带的一个函数了&#xff0c;他可对对文件进行压缩与解压缩处理&#xff0c;但是使用此类之前我们必须在php.ini中把extensionphp_zip.dll前面的分号有没有去掉&#xff0c;然后再重启Apache这样才能使用这个类库。 ziparchive 可选参数…

解决:Component name “index“ should always be multi-word

原因 要求组件名称以驼峰格式命名&#xff0c;自定义组件名称应该由多单纯组成&#xff0c;防止和html标签冲突&#xff0c;所以index.vue 会报错 解决 1、按照规则驼峰格式&#xff0c;如&#xff1a;appIndex.vue 2、若有.eslintrc.js文件&#xff0c;并在规则中(rules)关…

排序算法4:【快速排序】、查看每趟归并后的结果,定义一个全局变量,用来计数作为总趟数

一、快速排序——时间复杂度&#xff1a;、 最坏的情况 1、原理&#xff1a; 快速排序是通过多次比较和交换来实现排序&#xff0c;首先&#xff0c;先从数列中&#xff0c;任意选择一个数作为基准&#xff08;或叫分界值&#xff09;&#xff0c;比如&#xff0c;第一个数&a…

MySQL的事务以及springboot中如何使用事务

事务的四大特性&#xff1a; 概念&#xff1a; 事务 是一组操作的集合&#xff0c;它是不可分割的工作单元。事务会把所有操作作为一个整体&#xff0c;一起向系统提交或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失败。 注意&#xff1a; 默认MySQ…