为了账号安全,请及时绑定邮箱和手机立即绑定

JWT解决方案学习:入门级教程

标签:
架构 安全
概述

本文将带你深入了解JWT的基础概念、工作原理及其组成部分,探讨其在用户身份验证、信息交换和授权管理中的应用场景,并详细介绍如何生成和验证JWT。

JWT基础概念介绍

什么是JWT

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在网络应用环境间安全地将信息作为JSON对象传输。JWT设计的目的是为了提供一种安全的方式来声明一系列有关载体的信息,并且可以在网络环境中进行传输。JWT通常用于身份验证和信息交换。在JWT中,所有的数据都以JSON形式存储,并且在传输过程中被加密和签名,以确保数据的安全性和完整性。

JWT的工作原理

JWT的工作原理可以分为以下几个步骤:

  1. 生成JWT:生成JWT时,会创建一个包含三部分信息的字符串,分别是Header、Payload和Signature。
  2. 验证JWT:接收JWT的一方会根据Header中的算法对Payload进行解密,并通过Signature验证数据是否被篡改。
  3. 解析JWT:验证成功后,接收方可以解析Payload中的数据,进行后续的处理。

JWT的组成部分

JWT由三部分组成:Header、Payload和Signature。

  • Header:包含令牌的类型(即JWT)和所使用的签名算法。通常使用HMAC SHA256算法,也可以使用RSA或ECDSA算法。
  • Payload:包含声明(Claims)。这些声明可以分为三类:注册声明(Registered Claims)、公共声明(Public Claims)和私有声明(Private Claims)。注册声明是一组预定义的声明,如iss(签发者)和exp(过期时间)。
  • Signature:用于验证消息的完整性和身份验证。签名通过Header和Payload中的数据以及一个密钥,按照Header中指定的算法生成。接收方可以使用相同的算法和密钥来验证签名是否正确。
JWT的应用场景

用户身份验证

JWT常用于用户身份验证。当用户成功登录后,服务器会生成一个JWT,并将其返回给客户端。客户端在每次请求时,都会将JWT附加到HTTP请求头中,服务器通过验证JWT来确认用户的身份。

信息交换

JWT可以用于不同系统之间的信息交换。JWT中可以包含一些元数据,如用户信息、权限等,接收方可以通过解析JWT来获取所需的信息。

授权管理

JWT可以用于管理用户的权限。通过在Payload中添加权限声明,接收方可以根据JWT中的声明来限制对某些资源的访问。

如何生成JWT

Header的生成

Header是一个JSON对象,通常包含两个属性:typalgtyp表示令牌的类型,固定值为JWTalg表示使用的签名算法,常见的算法有HS256(使用HMAC SHA256算法)、RS256(使用RSA算法)和ES256(使用ECDSA算法)。

{
    "typ": "JWT",
    "alg": "HS256"
}

生成Base64编码后的Header字符串:

import base64
import json

header = {
    "typ": "JWT",
    "alg": "HS256"
}
header_encoded = base64.urlsafe_b64encode(json.dumps(header).encode()).decode().rstrip('=')

Payload的生成

Payload是一个JSON对象,包含声明(Claims)。常见的注册声明如下:

  • iss:签发者
  • sub:主题
  • aud:受众
  • exp:过期时间
  • nbf:生效时间
  • iat:签发时间
  • jti:JWT ID
{
    "sub": "1234567890",
    "name": "John Doe",
    "admin": true,
    "exp": 1516239022
}

生成Base64编码后的Payload字符串:

payload = {
    "sub": "1234567890",
    "name": "John Doe",
    "admin": True,
    "exp": 1516239022
}
payload_encoded = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip('=')

Signature的生成

Signature通过Header、Payload和一个密钥生成。根据Header中的算法,使用Base64解码后的Header和Payload生成签名。

import hashlib
import hmac

def generate_signature(header_encoded, payload_encoded, secret):
    signature_input = f"{header_encoded}.{payload_encoded}".encode()
    signature = hmac.new(secret.encode(), signature_input, hashlib.sha256).digest()
    return base64.urlsafe_b64encode(signature).decode().rstrip('=')

secret = "supersecretkey"
signature = generate_signature(header_encoded, payload_encoded, secret)
如何验证JWT

验证Token的合法性

验证Token的合法性需要解析JWT的Header和Payload,并使用相同的算法和密钥生成签名。然后,将生成的签名与JWT中的Signature进行比较。

def verify_signature(header_encoded, payload_encoded, jwt_signature, secret):
    expected_signature = generate_signature(header_encoded, payload_encoded, secret)
    return hmac.compare_digest(expected_signature, jwt_signature)

jwt_signature = "eyJhbGciOiAiSFMyNTYifQ.eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogIkpvaG4gRG9lIiwgImFkbWluIjogdHJ1ZSwgImV4cCI6IDE1MTYyMzkwMjJ9.r1sT7e9XqkGJl4DkX0JL5N9zP7rGf3FtG4BfQrGZ"
is_valid = verify_signature(header_encoded, payload_encoded, jwt_signature, secret)

验证Token的有效性

验证Token的有效性需要检查Payload中的声明。例如,检查exp(过期时间)是否已过期。

import time

def is_token_expired(exp):
    return time.time() > exp

exp = payload['exp']
is_expired = is_token_expired(exp)
使用JWT的常见问题及解决方法

JWT的签名问题

JWT的签名问题主要包括签名算法的选择和密钥的安全管理。建议使用HMAC SHA256算法,因为它相对安全且易于实现。密钥需要妥善保管,不要泄露给未经授权的第三方。以下是一个具体的代码示例来展示如何使用HMAC SHA256算法生成签名,以及如何存储和管理密钥:

import hashlib
import hmac

def generate_signature(header_encoded, payload_encoded, secret):
    signature_input = f"{header_encoded}.{payload_encoded}".encode()
    signature = hmac.new(secret.encode(), signature_input, hashlib.sha256).digest()
    return base64.urlsafe_b64encode(signature).decode().rstrip('=')

secret = "supersecretkey"
signature = generate_signature(header_encoded, payload_encoded, secret)

JWT的存储问题

JWT可以通过多种方式存储,如存储在Cookie中或本地存储中。Cookie可以设置为HttpOnly,以防止通过JavaScript访问。本地存储相对容易受到跨站脚本攻击(XSS)的影响,但可以通过一些措施(如使用JWT中间件)来减轻风险。以下是具体的代码示例来展示如何将JWT存储在Cookie中,并设置HttpOnly属性,以及如何使用本地存储来存储JWT,并采取措施减轻XSS风险:

将JWT存储在Cookie中并设置HttpOnly属性

from flask import Flask, make_response, request

app = Flask(__name__)

@app.route('/login', methods=['POST'])
def login():
    token = generate_jwt()  # 假设有一个生成JWT的函数
    response = make_response("Login successful")
    response.set_cookie('jwt', token, httponly=True, secure=True)
    return response

使用本地存储存储JWT

// 存储JWT到本地存储
localStorage.setItem('jwt', token);

// 从本地存储中获取JWT
const token = localStorage.getItem('jwt');

JWT的安全问题

JWT的安全问题主要包括中间人攻击、令牌泄露和令牌固定攻击。为了避免这些风险,可以采取以下措施:

  • 使用HTTPS:确保所有通信都是加密的。
  • 密钥安全性:使用强密钥,并定期更换密钥。
  • 限制令牌有效期:设置较短的过期时间,并在必要时刷新令牌。
  • 防止令牌泄漏:不要将JWT存储在本地存储中,尤其是不要在前端代码中泄露密钥。

以下是一个具体的代码示例来展示如何使用HTTPS加密通信,以及如何在JWT中设置较短的过期时间,并在必要时刷新令牌:

import jwt
import time

def generate_jwt():
    payload = {
        'sub': 'user_id',
        'exp': int(time.time()) + 3600,  # 设置过期时间为1小时
    }
    token = jwt.encode(payload, 'supersecretkey', algorithm='HS256')
    return token

def refresh_jwt(token):
    decoded_token = jwt.decode(token, 'supersecretkey', algorithms=['HS256'])
    if decoded_token['exp'] - time.time() < 1800:  # 如果剩余时间小于30分钟
        payload = {
            'sub': decoded_token['sub'],
            'exp': int(time.time()) + 3600,  # 刷新过期时间
        }
        return jwt.encode(payload, 'supersecretkey', algorithm='HS256')
    return token
实践案例

使用Node.js生成JWT

在Node.js中,可以使用jsonwebtoken库来生成JWT。

const jwt = require('jsonwebtoken');

const secret = 'supersecretkey';
const payload = {
    sub: '1234567890',
    name: 'John Doe',
    admin: true,
    exp: Math.floor(Date.now() / 1000) + (60 * 60)  // 过期时间设置为1小时后
};

const token = jwt.sign(payload, secret, {
    algorithm: 'HS256'
});

console.log(token);  // 输出生成的JWT

使用Spring Boot验证JWT

在Spring Boot中,可以使用Spring Securityjwt-token库来验证JWT。

首先,在pom.xml中添加依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

然后,创建一个JWT认证过滤器:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;

@Component
public class JwtRequestFilter extends OncePerRequestFilter {

    private final UserDetailsService userDetailsService;
    private final String SECRET = "supersecretkey";

    public JwtRequestFilter(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        final String requestTokenHeader = request.getHeader("Authorization");

        String username = null;
        String jwtToken = null;
        // JWT Token 在 Authorization 头中,格式为 "Bearer Token"
        if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
            jwtToken = requestTokenHeader.substring(7);
            try {
                Claims claims = Jwts.parser()
                        .setSigningKey(SECRET)
                        .parseClaimsJws(jwtToken)
                        .getBody();

                username = claims.getSubject();
            } catch (Exception e) {
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid token");
            }
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);

            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                    userDetails, null, Collections.emptyList()
            );

            usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        }
        chain.doFilter(request, response);
    }
}

最后,在Spring Boot应用中启用过滤器:


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtRequestFilter jwtRequestFilter;

    @Autowired
    private UserDetailsService jwtInMemoryUserDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(jwtInMemoryUserDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests().antMatchers("/api/authenticate").permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
``

以上代码展示了如何在Spring Boot中使用JWT进行身份验证,包括生成过滤器、设置安全配置等。

通过以上步骤,可以在实际项目中实现JWT的身份验证功能。
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消