JWT使用

JWT简介

什么是JWT?

官网:JSON 网络令牌介绍
(JWT)Json Web Tokne 是一个开放标准(RFC 7519),它定义了一种紧凑且自成一体的方式,以便将各方之间的信息安全地传输为 JSON 对象。此信息可以通过数字签名进行验证和信任。JWT 可以使用秘密(使用HMAC算法)或使用RSAECDSA的公共/私人密钥对进行签名。

通俗解释: JWT简称JSON Web Token,也就是通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密、签名等相关处理。

JWT可以做什么?

# 1.授权
- 这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。单点登录是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。

# 2.信息交换
- JSON Web Token是在各方之间安全地传输信息的好方法。因为可以对JWT进行签名《例如,使用公钥/私钥对),所以您可以确保发件人是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否遭到篡改。

token和session认证对比

基于session认证

# 1.认证方式
- http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。

# 2.认证流程

image-20210802153847866

# 3.存在问题
- Session: 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。

- 扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。

- CSRF: 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

- 在前后端分离系统中就更加痛苦:(如下图所示)
	也就是说前后端分离在应用解耦后增加了部署的复杂性。通常用户一次请求就要转发多次。如果用session 每次携带sessionid到服务器,服务器还要查询用户信息。同时如果用户很多。这些信息存储在服务器内存中,给服务器增加负担。还有就是CSRF(跨站伪造请求攻击)攻击,session是基于cookie进行用户识别的,cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。还有就是sessionid就是一个特征值,表达的信息不够丰富。不容易扩展。而且如果你后端应用是多节点部署。那么就需要实现session共享机制。不方便集群应用。

image-20210802154633022

基于JWT认证

image-20210802154733688

# 1.认证流程
- 用户使用用户名密码来请求服务器

- 后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个token。形成的token就是一个形同111.zzz.x×x的字符串。

- 后端将token字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage上,退出登录时前端删除保存的JWT即可。

- 前端在每次请求时将token放入HTTP Header中的Authorization位。(解决XSS和XSRF问题)

- 后端检查是否存在,如存在验证token的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。

- 验证通过后后端使用token中包含的用户信息进行其他逻辑操作,返回相应结果。

# 2.jwt优势
- 简洁:可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快

- 自包含∶负载中包含了所有用户所需要的信息,避免了多次查询数据库

- 因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。

- 不需要在服务端保存会话信息,特别适用于分布式微服务。

Tips:

这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *

JWT的结构

header (标头)
playload (有效载荷)
signature (签名)

JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

header

  • 标头通常由两部分组成︰令牌的类型(即JWT))和所使用的签名算法,例如HMAC SHA256或RSA。它会使用Base64编码组成JWT结构的第一部分。
  • 注意:Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。
{
  'typ': 'JWT',
  'alg': 'HS256'
}
  • 然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分

playload

  • 令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户)和其他数据的声明。同样的,它会使用Base64编码组成JWT结构的第二部分
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

  • 这些有效信息包含三个部分
标准中注册的声明
公共的声明
私有的声明
标准中注册的声明 (建议但不强制使用) :

iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
公共的声明 :
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明 :
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

signature

  • 这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret'); 

Tip:

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。

image-20210802163218572

image-20210802163351793

应用

一般是在请求头里加入Authorization,并加上Bearer标注:

fetch('api/user/1', {
  headers: {
    'Authorization': 'Bearer ' + token
  }
})

快速入门

  1. 导入依赖
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.4.0</version>
</dependency>
  1. 生成token 获取令牌
        //生成令牌
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.MINUTE,30); //设置过期时间 30分钟
        String token= JWT.create()
                .withClaim("userName","前度")
                .withClaim("id","123456")  //设置自定义内容
                .withExpiresAt(instance.getTime()) //设置过期时间
                .sign(Algorithm.HMAC256("!4424&%*%#FESFTSE"));//设置签名,保密
        //输出令牌
        System.out.println(token);
  1. 验证令牌 根据令牌和签名解析数据
//创建验证对象
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("!4424&%*%#FESFTSE")).build();
DecodedJWT verify = jwtVerifier.verify(setToken());//token
//获取值
System.out.println(verify.getClaim("userName").asString());
System.out.println(verify.getClaim("id").asInt());
System.out.println("过期时间"+verify.getExpiresAt());
  1. 常见的类型异常
异常类型描述
SignatureVerificationException签名不一致异常
TokenExpiredException令牌过期异常
AlgorithmMismatchException算法不匹配异常
InvalidclaimException失效的payload异常

封装工具类

public class JWTUtil {
    //密钥
    private static final String SECRET = "!Q464rwr(&*)6%$#^%&JNJ46da";

    /**
     * 生成token
     * @param map:要加密的信息
     * @return 令牌
     */
    public static String sign(Map<String, String> map) {
        //设置过期时间 默认3天
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.DATE, 3);
        JWTCreator.Builder builder = JWT.create();
        //payload
        map.forEach((k, v) -> {
            builder.withClaim(k, v);
        });
        //指定令牌过期时间与加密
        String token = builder.withExpiresAt(instance.getTime())
                .sign(Algorithm.HMAC256(SECRET));
        return token;
    }

    /**
     * 验证token合法性/获取token全部信息
     * @param token
     * @return verify
     */
    public static DecodedJWT verify(String token) {
        return JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);
    }

    /**
     * 获取token单个信息
     * @param token
     * @param s:key
     * @return value
     */
    public static String getInfo(String token, String s) {
        DecodedJWT verify = verify(token);
        return verify.getClaim(s).asString();
    }
}

使用Demo

形式一

登录得到token

验证token

实际上不这么写 开发中会使用拦截器拦截所有请求 将token放入请求头中
image-20210802192412672

代码优化

  • SpringBoot : 使用拦截器
  • SpringCloud :使用网关

这里演示使用拦截器优化

  1. 拦截请求
public class JWTInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Map<String, Object> map = new HashMap<>();
        //获取请求头中的令牌
        String token = request.getHeader("token");
        //验证令牌
        try {
            JwtUtil.verify(token);
            return true;
        } catch (SignatureVerificationException e) {
            e.printStackTrace();
            map.put("msg", "无效签名!");
        } catch (TokenExpiredException e) {
            e.printStackTrace();
            map.put("msg", "token过期!");
        } catch (AlgorithmMismatchException e) {
            e.printStackTrace();
            map.put("msg", "token算法不一致!");
        } catch (Exception e) {
            e.printStackTrace();
            map.put("msg", "token失效!!");
        }
        map.put("state", false);  //设置状态
        //将map转为json
        String json = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json; charset=UTF-8");
        response.getWriter().println(json);
        response.getWriter().flush();
        response.getWriter().close();
        return false;
    }
}
  1. 配置拦截器
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        WebMvcConfigurer.super.addInterceptors(registry);
        registry.addInterceptor(new JWTInterceptor())
                .addPathPatterns("/**")  //所有接口都要token验证
                .excludePathPatterns("/*/login"); //所有关于用户接口放行  根据场景配置
    }
}
  1. 测试接口

image-20210802200611400

​ 获取token中的信息

image-20210802200929281


The End~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Gin框架中使用JWT(JSON Web Token)可以实现份验证和授权功能。JWT是一种用于在网络应用间传递信息的安全方法,它由三部分组成:header、payload和signature[^1]。 下面是在Gin框架中使用JWT的示例代码[^2]: 1. 导入所需的包: ```go import ( "github.com/gin-gonic/gin" "github.com/dgrijalva/jwt-go" ) ``` 2. 定义JWT的密钥: ```go var jwtKey = []byte("your_secret_key") ``` 3. 创建一个JWT的Claims结构体,用于存储用户的信息: ```go type Claims struct { Username string `json:"username"` jwt.StandardClaims } ``` 4. 创建一个处理登录请求的路由: ```go func login(c *gin.Context) { var loginData LoginData if err := c.ShouldBindJSON(&loginData); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"}) return } // 验证用户名和密码 if loginData.Username == "admin" && loginData.Password == "password" { // 创建JWT的Claims claims := &Claims{ Username: loginData.Username, StandardClaims: jwt.StandardClaims{ ExpiresAt: time.Now().Add(time.Hour * 24).Unix(), // 设置过期时间 }, } // 创建JWT token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString(jwtKey) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"}) return } // 返回JWT给客户端 c.JSON(http.StatusOK, gin.H{"token": tokenString}) } else { c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"}) } } ``` 5. 创建一个需要身份验证的路由: ```go func protectedRoute(c *gin.Context) { // 从请求头中获取JWT authHeader := c.GetHeader("Authorization") if authHeader == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "Missing authorization header"}) return } // 解析JWT tokenString := authHeader[7:] // 去除Bearer前缀 claims := &Claims{} token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { return jwtKey, nil }) if err != nil { if err == jwt.ErrSignatureInvalid { c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token signature"}) return } c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid token"}) return } if !token.Valid { c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"}) return } // 验证通过,继续处理请求 c.JSON(http.StatusOK, gin.H{"message": "Protected route"}) } ``` 6. 在路由中注册处理函数: ```go func main() { r := gin.Default() r.POST("/login", login) r.GET("/protected", protectedRoute) r.Run(":8080") } ``` 以上代码演示了在Gin框架中使用JWT进行身份验证和授权的基本流程。用户可以通过发送登录请求获取JWT,然后在需要身份验证的路由中将JWT放入请求头中进行验证。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值