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

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 复制代码
<?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进行签发,并设置了签名信息,过期时间,及相关的用户信息。

java 复制代码
@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 **:**注意获取值的返回值结果

java 复制代码
@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

java 复制代码
@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地址会出现死循环的情况。

java 复制代码
@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、创建自定义异常类

java 复制代码
public class TokenIsNullException extends RuntimeException{

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

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

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

java 复制代码
@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,如果验证成功允许访问,否则不允许访问。

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

4.6、创建登录页实现登录

html 复制代码
说明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, 如果验证失败拒绝访问。

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 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没有成功访问,拦截器没有放行。

相关推荐
李洋-蛟龙腾飞公司2 小时前
HarmonyOS Next 应用元服务开发-分布式数据对象迁移数据文件资产迁移
分布式·华为·harmonyos
技术路上的苦行僧4 小时前
分布式专题(10)之ShardingSphere分库分表实战指南
分布式·shardingsphere·分库分表
GitCode官方5 小时前
GitCode 光引计划投稿 | GoIoT:开源分布式物联网开发平台
分布式·开源·gitcode
小扳7 小时前
微服务篇-深入了解 MinIO 文件服务器(你还在使用阿里云 0SS 对象存储图片服务?教你使用 MinIO 文件服务器:实现从部署到具体使用)
java·服务器·分布式·微服务·云原生·架构
zquwei16 小时前
SpringCloudGateway+Nacos注册与转发Netty+WebSocket
java·网络·分布式·后端·websocket·网络协议·spring
道一云黑板报19 小时前
Flink集群批作业实践:七析BI批作业执行
大数据·分布式·数据分析·flink·kubernetes
飞来又飞去21 小时前
kafka sasl和acl之间的关系
分布式·kafka
MZWeiei1 天前
Zookeeper的监听机制
分布式·zookeeper
莹雨潇潇1 天前
Hadoop完全分布式环境部署
大数据·hadoop·分布式
浩哲Zhe1 天前
RabbitMQ
java·分布式·rabbitmq