JWT入门与实践

认识JWT

JSON Web Tokens - jwt.io

​ JWT(JSON Web Token)是一种用于在网络上安全传输信息的开放标准。它由三个部分组成:头部载荷签名。头部包含加密算法令牌类型等信息,载荷包含用户信息和其他元数据,签名则通过使用密钥对头部和载荷进行加密来验证令牌的真实性和完整性。JWT 可以被用于身份验证授权,因为它可以帮助验证请求是否来自可信的源,并且可以将用户信息权限信息嵌入到令牌中,从而避免了每次请求都需要进行数据库查询的情况。

​ 下面是一个JWT样例,头部、载荷、签名都用.进行分隔

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

下面是对于一个JWT的解析过程:

  1. 将JWT字符串按照点号(.)分成三个部分:头部载荷签名
  2. 解码头部,得到加密算法令牌类型等信息。
  3. 解码载荷,得到JWT中存储的信息
  4. 验证签名,确保JWT没有被篡改过。具体验证方式取决于使用的加密算法。
  5. 如果验证成功,则可以信任JWT中的信息。

需要注意的是,JWT只是一种基于文本的令牌,因此它不提供加密功能,只提供了签名功能。如果需要加密数据,可以将JWT作为一个整体进行加密

使用JWT

以下是一些JWT的具体使用例子:

  1. 身份验证:当用户成功登录时,服务器可以生成一个JWT并将其返回给客户端。客户端可以在后续请求中将该JWT作为身份验证凭据发送到服务器。服务器可以验证JWT的签名并确定用户是否有权访问所请求的资源。

  2. 单点登录:当用户成功登录到一个应用程序时,服务器可以生成一个JWT并将其返回给客户端。客户端可以在后续请求中将该JWT作为身份验证凭据发送到其他应用程序。其他应用程序可以验证JWT的签名并确定用户是否有权访问所请求的资源。

  3. 授权:当用户请求访问某个受保护的资源时,服务器可以检查JWT中包含的声明以确定用户是否有权访问该资源。例如,服务器可以检查JWT中是否包含特定的角色或权限声明。

  4. 信息交换:两个服务之间可以使用JWT来安全地交换信息。一个服务可以生成一个JWT并将其发送到另一个服务。接收方可以验证JWT的签名并提取其中包含的信息。

  5. 重置密码:当用户请求重置密码时,服务器可以生成一个包含重置令牌的JWT并将其发送到用户的电子邮件地址。用户可以使用该令牌来验证其身份并设置新密码。

以下是基于Vue+SpringBoot的一个简单例子:

// 导入所需的包
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.web.bind.annotation.*;

import java.util.Date;

@RestController
@RequestMapping("/api")
public class AuthController {

    // 用户登录接口
    @PostMapping("/login")
    public String login(@RequestBody User user) {
        // 验证用户身份
        if (authenticate(user)) {
            // 生成JWT令牌
            String token = Jwts.builder()
                    .setSubject(user.getUsername())
                    .setExpiration(new Date(System.currentTimeMillis() + 3600000))
                    .signWith(SignatureAlgorithm.HS512, "secret")
                    .compact();
            return token;
        } else {
            return "Invalid credentials";
        }
    }

    // 单点登录接口
    @PostMapping("/sso")
    public String sso(@RequestHeader("Authorization") String token) {
        // 验证JWT令牌
        if (validate(token)) {
            return "Success";
        } else {
            return "Unauthorized";
        }
    }

    // 授权接口
    @GetMapping("/protected")
    public String protectedResource(@RequestHeader("Authorization") String token) {
        // 验证JWT令牌中是否包含特定的角色或权限声明
        Claims claims = Jwts.parser()
                .setSigningKey("secret")
                .parseClaimsJws(token.replace("Bearer ", ""))
                .getBody();
        if (claims.get("role").equals("admin")) {
            return "Access granted";
        } else {
            return "Access denied";
        }
    }

    // 信息交换接口
    @PostMapping("/exchange")
    public String exchange(@RequestBody String data, @RequestHeader("Authorization") String token) {
        // 验证JWT令牌
        if (validate(token)) {
            // 处理数据并返回结果
            return "Processed data: " + data;
        } else {
            return "Unauthorized";
        }
    }

    // 重置密码接口
    @PostMapping("/reset-password")
    public String resetPassword(@RequestBody User user) {
        // 生成包含重置令牌的JWT令牌并发送到用户的电子邮件地址
        String token = Jwts.builder()
                .setSubject(user.getUsername())
                .setExpiration(new Date(System.currentTimeMillis() + 600000))
                .claim("reset", true)
                .signWith(SignatureAlgorithm.HS512, "secret")
                .compact();
        // 发送电子邮件
        sendEmail(user.getEmail(), token);
        return "Email sent";
    }

    // 验证用户身份
    private boolean authenticate(User user) {
        // TODO: 实现用户身份验证逻辑
        return true;
    }

    // 验证JWT令牌
    private boolean validate(String token) {
        try {
            Jwts.parser().setSigningKey("secret").parseClaimsJws(token.replace("Bearer ", ""));
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    // 发送电子邮件
    private void sendEmail(String email, String token) {
        // TODO: 实现发送电子邮件逻辑
    }

}

下面的代码是一个Vue.js组件,提供了各种身份验证和授权相关操作的用户界面。该组件包含一个用户登录表单,该表单使用用户的凭据向服务器发送POST请求。如果登录成功,服务器将响应一个JWT(JSON Web Token),该JWT存储在组件的token属性中。sso方法发送一个POST请求到服务器,以使用Authorization头中的JWT启动单点登录过程。protectedResource方法发送一个GET请求以访问受保护的资源,再次使用Authorization头中的JWT。exchangeData方法使用表单输入的数据和Authorization头中的JWT发送一个POST请求以与服务器交换数据。最后,resetPassword方法使用表单中输入的电子邮件发送一个POST请求以重置用户的密码。该组件使用Axios库向服务器发出HTTP请求。该库提供了一个简单和一致的API来发出HTTP请求,并支持拦截器来处理请求和响应。

<template>
  <div>
    <h2>Login</h2>
    <form @submit.prevent="login">
      <div>
        <label for="username">Username:</label>
        <input type="text" id="username" v-model="user.username" required>
      </div>
      <div>
        <label for="password">Password:</label>
        <input type="password" id="password" v-model="user.password" required>
      </div>
      <button type="submit">Login</button>
    </form>
    <hr>
    <h2>Single Sign-On</h2>
    <button @click="sso">SSO</button>
    <hr>
    <h2>Protected Resource</h2>
    <button @click="protectedResource">Access Protected Resource</button>
    <hr>
    <h2>Exchange Data</h2>
    <form @submit.prevent="exchangeData">
      <label for="data">Data:</label>
      <input type="text" id="data" v-model="data">
      <button type="submit">Exchange Data</button>
    </form>
    <hr>
    <h2>Reset Password</h2>
    <form @submit.prevent="resetPassword">
      <div>
        <label for="email">Email:</label>
        <input type="email" id="email" v-model="user.email" required>
      </div>
      <button type="submit">Reset Password</button>
    </form>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  name: 'App',
  data() {
    return {
      user: {
        username: '',
        password: '',
        email: '',
      },
      token: '',
      data: '',
    };
  },
  methods: {
    login() {
      axios.post('/api/login', this.user)
        .then(response => {
          this.token = response.data;
          console.log(this.token);
        })
        .catch(error => {
          console.error(error);
        });
    },
    sso() {
      axios.post('/api/sso', null, { headers: { Authorization: `Bearer ${this.token}` } })
        .then(response => {
          console.log(response.data);
        })
        .catch(error => {
          console.error(error);
        });
    },
    protectedResource() {
      axios.get('/api/protected', { headers: { Authorization: `Bearer ${this.token}` } })
        .then(response => {
          console.log(response.data);
        })
        .catch(error => {
          console.error(error);
        });
    },
    exchangeData() {
      axios.post('/api/exchange', this.data, { headers: { Authorization: `Bearer ${this.token}` } })
        .then(response => {
          console.log(response.data);
        })
        .catch(error => {
          console.error(error);
        });
    },
    resetPassword() {
      axios.post('/api/reset-password', this.user)
        .then(response => {
          console.log(response.data);
        })
        .catch(error => {
          console.error(error);
        });
    },
  },
};
</script>

注意

在项目中使用JWT时,需要注意以下几点:

  1. 安全性:JWT令牌是基于密钥签名的,因此确保在使用时使用强大的加密算法和安全的密钥管理。

    以下是一些强大的加密算法:

    • AES (Advanced Encryption Standard) - 对称加密算法,用于加密数据传输和存储。

    • RSA (Rivest–Shamir–Adleman) - 非对称加密算法,用于数字签名和密钥交换。

    • HMAC (Hash-based Message Authentication Code) - 基于哈希函数的消息认证码,用于验证数据完整性和真实性。

    • SHA-256 (Secure Hash Algorithm 256-bit) - 哈希函数,用于生成固定长度的摘要,常用于密码学应用中。

    • ECDH (Elliptic Curve Diffie-Hellman) - 椭圆曲线密钥交换协议,用于在两个参与者之间安全地共享密钥。

  2. 过期时间:为了防止令牌被滥用,应该设置适当的过期时间,并定期更新令牌。

    要为Spring Boot中的JWT令牌设置过期时间,我们可以使用以下代码:

    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import io.jsonwebtoken.security.Keys;
    import java.security.Key;
    import java.util.Date;
    
    public class JwtUtil {
        private static final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
        private static final long expirationTimeInMs = 3600000; // 1 hour
    		// 生成JWT令牌
        public static String generateToken(String subject) {
            Date now = new Date();
            Date expiration = new Date(now.getTime() + expirationTimeInMs);
    
            return Jwts.builder()
                    .setSubject(subject)
                    .setIssuedAt(now)
                    .setExpiration(expiration)
                    .signWith(key)
                    .compact();
        }
    		// 验证JWT令牌
        public static boolean validateToken(String token) {
            try {
                Jwts.parser().setSigningKey(key).parseClaimsJws(token);
                return true;
            } catch (Exception e) {
                return false;
            }
        }
    
        public static String getSubjectFromToken(String token) {
            Claims claims = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
            return claims.getSubject();
        }
    }
    

    ​ 要定期更新JWT令牌,我们可以实现一个定时任务,在当前令牌过期之前生成一个新令牌。我们可以使用Spring的@Scheduled注释来安排任务。这是一个例子:

    import org.springframework.scheduling.annotation.Scheduled;
    
    public class TokenScheduler {
        private final JwtUtil jwtUtil;
        private String currentToken;
    
        public TokenScheduler(JwtUtil jwtUtil) {
            this.jwtUtil = jwtUtil;
            this.currentToken = jwtUtil.generateToken("user123");
        }
    
        public String getCurrentToken() {
            return currentToken;
        }
    		// 使用Spring的@Scheduled注释来安排任务
        @Scheduled(fixedRate = 1800000) // 30 minutes
        public void updateToken() {
            currentToken = jwtUtil.generateToken("user123");
        }
    }
    
    
  3. 数据隐私:不要将敏感数据存储在JWT令牌中,因为它们可以通过解码令牌来访问。

  4. 令牌刷新:在某些情况下,可能需要刷新JWT令牌,例如用户更改密码权限等。在这种情况下,需要重新颁发新的令牌。

  5. 跨站点请求伪造(CSRF)攻击:为了防止CSRF攻击,应该在JWT令牌中包含CSRF令牌,并在每个请求中验证它。

CSRF攻击是一种利用用户已经登录的身份来进行恶意操作的攻击方式。攻击者会在第三方网站上放置一个恶意代码,当用户访问该网站时,代码会自动向目标网站发送请求,利用用户的登录状态进行操作。为了防止CSRF攻击,可以在JWT令牌中包含CSRF令牌,并在每个请求中验证它。在每个请求中,服务器会验证请求头请求参数中的CSRF令牌是否与JWT令牌中的CSRF令牌一致,如果不一致则拒绝该请求。

  1. 滥用检测:监控系统以检测任何异常活动,如频繁的登录尝试或使用同一JWT令牌进行多个请求。该系统跟踪用户行为并标记任何偏离正常模式的活动,有助于防止未经授权的访问并保护系统免受攻击
// 导入必要的包
${INSERT_HERE}

// 为滥用检测系统定义一个类
public class AbuseDetectionSystem {

    // 定义必要的变量和数据结构
    ${INSERT_HERE}

    // 监视用户活动并检测任何可疑行为的方法
    public void monitorUserActivity(User user, Request request) {
        // 检查用户活动是否在正常模式内
        if (!isActivityWithinNormalPatterns(user, request)) {
            // 如果不是,则标记为可疑活动
            flagSuspiciousActivity(user, request);
            // 处理滥用或可疑行为
            handleAbuse(user, request);
        }
        // 更新用户活动历史记录
        updateActivityHistory(user, request);
    }

    // 标记任何偏离正常模式的活动的方法
    private void flagSuspiciousActivity(User user, Request request) {
        // 标记用户的活动为可疑
        ${INSERT_HERE}
    }

    // 更新用户活动历史记录的方法
    private void updateActivityHistory(User user, Request request) {
        // 将用户的活动添加到历史记录中
        ${INSERT_HERE}
    }

    // 检查用户的活动是否在正常模式内的方法
    private boolean isActivityWithinNormalPatterns(User user, Request request) {
        // 检查用户的活动是否在正常模式内
        ${INSERT_HERE}
    }

    // 处理任何检测到的滥用或可疑行为的方法
    private void handleAbuse(User user, Request request) {
        // 处理滥用或可疑行为
        ${INSERT_HERE}
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 JWT(JSON Web Token)的公钥和私钥,主要涉及生成签名(签发令牌)和验证签名(验证令牌)两个过程。 1. 生成签名(签发令牌): - 使用私钥对 JWT 的头部和载荷进行数字签名,以确保令牌的完整性和真实性。 - 将签名后的结果添加到 JWT 的头部或载荷中,形成最终的 JWT。 2. 验证签名(验证令牌): - 获取 JWT 中的头部和载荷,并提取签名部分。 - 使用公钥对头部和载荷进行验证,以确认令牌是由合法的签发者签名的。 - 如果验证成功,则说明令牌是有效的。 在实际应用中,生成签名和验证签名的具体实现细节会根据编程语言和库的不同而有所差异。以下是一个示例,使用 C# 和 System.IdentityModel.Tokens.Jwt 库来生成和验证 JWT 的过程: ```csharp using System; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using Microsoft.IdentityModel.Tokens; public class JwtHelper { public static string GenerateToken(string privateKey) { var securityKey = new SymmetricSecurityKey(Convert.FromBase64String(privateKey)); var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); var tokenHandler = new JwtSecurityTokenHandler(); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new Claim[] { new Claim("userId", "123") }), Expires = DateTime.UtcNow.AddDays(1), SigningCredentials = credentials }; var token = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(token); } public static bool ValidateToken(string token, string publicKey) { var tokenHandler = new JwtSecurityTokenHandler(); var validationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Convert.FromBase64String(publicKey)), ValidateIssuer = false, // 可选,如果需要验证签发者,请将其设置为 true,并提供有效的 Issuer ValidateAudience = false // 可选,如果需要验证受众,请将其设置为 true,并提供有效的 Audience }; try { // 验证令牌 tokenHandler.ValidateToken(token, validationParameters, out _); return true; } catch (Exception) { // 令牌验证失败 return false; } } } ``` 上述代码中,`GenerateToken` 方法用于生成 JWT,其中传入私钥 `privateKey` 用于生成签名。在 `tokenDescriptor` 中,我们设置了 JWT 的主题(Subject)、过期时间(Expires)等信息,并使用私钥进行签名。 `ValidateToken` 方法用于验证 JWT,其中传入公钥 `publicKey` 用于验证签名。在 `validationParameters` 中,我们设置了验证签名的密钥(IssuerSigningKey)和其他可选的验证参数(如验证签发者和受众)。 请注意,上述示例中使用的是对称加密算法(HMAC),密钥是以 Base64 编码的字符串。如果使用非对称加密算法(如 RSA),则需要使用公钥和私钥对,并且相应的密钥格式和库的使用可能会有所不同。 总之,通过使用 JWT 的公钥和私钥,你可以生成签名并签发令牌,也可以验证令牌的签名的真实性和完整性。具体的实现方法会根据你所使用的编程语言和库而有所差异。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值