亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定

JWT單點登錄資料詳解與實戰教程

標簽:
API
概述

本文介绍了JWT的构成和用途,解释了单点登录的基本概念,并详细阐述了JWT单点登录的优势和实现步骤。文章还提供了跨域认证、跨应用认证和跨平台认证的实际应用场景,以及相关的常见问题和解决方案。

什么是JWT单点登录

JWT简介

JSON Web Token(JWT)是一种开放标准(RFC 7519),用于在双方之间安全地传输信息。JWT可用来构建安全的无状态认证机制,它通常用于身份验证和授权。JWT的核心特性包括:无状态性、加密签名和自包含。无状态性意味着服务端不需要存储和追踪会话状态,加密签名确保了数据的完整性和不可篡改性,自包含则意味着所有必要的信息都包含在单个令牌中。

JWT由三部分组成,分别用.分割:

  1. 头部(Header):包含令牌的类型(即JWT)和所使用的签名算法(如HMAC SHA256或RSA)。
  2. 载荷(Payload):包含声明(claims),是JWT的主体部分,通常用于传输用户身份信息、权限信息等。
  3. 签名(Signature):通过联合头部、载荷和一个密钥(只有服务端知道),用HMAC算法、SHA256算法或其他算法生成一个签名,用于验证消息的完整性。

单点登录的基本概念

单点登录(Single Sign-On,简称 SSO)是指用户只需一次登录,即可访问多个系统或应用。这种机制提高了用户的便利性,减少了输入密码的次数,同时也简化了身份验证过程。传统的单点登录通常依赖于服务端会话机制,而JWT单点登录则采用无状态的JWT令牌来实现。

JWT单点登录的意义

JWT单点登录的意义在于:

  1. 无状态认证:省去了服务端存储会话状态的需求,简化了系统架构。
  2. 高可扩展性:由于JWT是无状态的,服务端不需要维护用户的登录状态,因此可以轻松地扩展到多个应用或服务。
  3. 安全性:JWT通过加密签名确保了数据的安全性和完整性,同时令牌在客户端本地存储,减少了网络传输中的安全风险。
  4. 跨域访问:支持跨域认证,适合微服务架构和分布式系统。
JWT单点登录的工作原理

搭建JWT环境

在开始使用JWT之前,首先需要搭建JWT环境。主要工具包括:

  • Node.js(后端)
  • Express(框架)
  • jsonwebtoken(JWT处理库)

安装所需依赖:

npm install express jsonwebtoken

生成JWT令牌的过程

生成JWT令牌需要以下几个步骤:

  1. 创建JWT头部,指定类型和算法。
  2. 构建JWT载荷,包含必要的声明。
  3. 使用密钥和头部中的算法生成签名。
  4. 合并头部、载荷和签名,形成完整的JWT令牌。

示例代码:

const jwt = require('jsonwebtoken');
const secret = 'your256bitsecret'; // 秘钥

function generateToken(user) {
    const payload = {
        id: user.id,
        username: user.username,
        email: user.email
    };
    const token = jwt.sign(payload, secret, {
        expiresIn: '1h'
    });
    return token;
}

验证JWT令牌的流程

验证JWT令牌需要以下几个步骤:

  1. 从请求中获取JWT令牌。
  2. 使用相同的秘钥和算法解码JWT。
  3. 检查令牌的有效性和签名。
  4. 验证令牌的过期时间。

示例代码:

function verifyToken(req, res, next) {
    const token = req.headers.authorization && req.headers.authorization.split(' ')[1];
    if (token) {
        jwt.verify(token, secret, (err, decoded) => {
            if (err) {
                return res.status(403).send({ message: '无效的Token' });
            } else {
                req.userId = decoded.id;
                next();
            }
        });
    } else {
        return res.status(403).send({ message: '未提供Token' });
    }
}
JWT单点登录的实现步骤

前端与后端的交互过程

前端向后端发起认证请求,通过验证后,后端生成JWT令牌并返回给前端,前端则保存这个令牌,并在后续请求中携带这个令牌进行身份验证。

前端代码示例(使用fetch):

fetch('/auth/login', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        username: 'user1',
        password: 'pass1'
    })
})
.then(response => response.json())
.then(data => {
    console.log('Token:', data.token);
    localStorage.setItem('token', data.token);
});

后端实现登录接口:

app.post('/auth/login', (req, res) => {
    const user = {
        id: 1,
        username: 'user1',
        email: '[email protected]'
    };
    const token = generateToken(user);
    res.send({ token });
});

实现登录接口

登录接口负责验证用户身份,并生成JWT令牌。通常包括以下几个步骤:

  1. 验证用户身份(例如,通过用户名和密码验证)。
  2. 创建JWT令牌。
  3. 将JWT令牌返回给客户端。

示例代码:

app.post('/auth/login', (req, res) => {
    const user = {
        id: 1,
        username: 'user1',
        email: '[email protected]'
    };
    const token = generateToken(user);
    res.json({ token });
});

实现认证接口

认证接口用于验证JWT令牌的有效性,并可能附加一些额外的验证步骤(例如验证用户角色、权限等)。

示例代码:

app.get('/profile', verifyToken, (req, res) => {
    const user = {
        id: req.userId,
        username: 'user1',
        email: '[email protected]'
    };
    res.json(user);
});
JWT单点登录的应用场景

跨域认证

JWT非常适合在微服务架构下进行跨域认证,因为它不需要依赖会话状态,只要JWT签名有效且未过期,就可以通过身份验证。

跨应用认证

JWT可以用于多个应用之间共享同一个登录状态。例如,用户在一个网站上登录后,其他关联的网站或应用也能识别该用户的身份。

跨平台认证

JWT可以用于Web应用、移动应用和桌面应用之间的认证。由于JWT的数据格式是JSON,并且包含所有必要的身份信息,因此可以很容易地在不同平台之间传输。

JWT单点登录的常见问题及解决方案

令牌过期与刷新

JWT令牌通常有一个有限的有效期。过期后,客户端需要重新认证或刷新令牌。刷新机制通常通过客户端发送旧令牌来获取一个新的有效令牌。

示例代码(刷新令牌):

app.post('/auth/refresh', (req, res) => {
    const oldToken = req.headers.authorization && req.headers.authorization.split(' ')[1];
    jwt.verify(oldToken, secret, (err, decoded) => {
        if (err) {
            return res.status(403).send({ message: '无效的旧Token' });
        }
        const user = {
            id: decoded.id,
            username: decoded.username,
            email: decoded.email
        };
        const newToken = generateToken(user);
        res.json({ token: newToken });
    });
});

令牌安全存储

为了防止令牌被截获或窃取,客户端必须安全地存储JWT令牌。常见的做法是在浏览器的localStorage中存储令牌,或者使用HttpOnlycookie存储。

令牌被恶意攻击的防范

  1. 使用推荐的加密算法:如RSA、HMAC SHA256。
  2. 定期更新密钥:定期更换JWT的密钥,以增加攻击者的破解难度。
  3. 防止CSRF攻击:可以在JWT载荷中添加额外的声明来防止CSRF攻击。
  4. 令牌长度和复杂度:确保令牌长度足够长且包含足够的随机性,以增加破解难度。
实战案例分享

案例一:基于Node.js的JWT单点登录实现

本案例将展示如何使用Node.js和Express构建一个简单的JWT单点登录系统。

后端代码

安装依赖:

npm install express jsonwebtoken

创建server.js

const express = require('express');
const jwt = require('jsonwebtoken');
const secret = 'your256bitsecret'; // 秘钥

const app = express();

function generateToken(user) {
    const payload = {
        id: user.id,
        username: user.username,
        email: user.email
    };
    const token = jwt.sign(payload, secret, {
        expiresIn: '1h'
    });
    return token;
}

function verifyToken(req, res, next) {
    const token = req.headers.authorization && req.headers.authorization.split(' ')[1];
    if (token) {
        jwt.verify(token, secret, (err, decoded) => {
            if (err) {
                return res.status(403).send({ message: '无效的Token' });
            } else {
                req.userId = decoded.id;
                next();
            }
        });
    } else {
        return res.status(403).send({ message: '未提供Token' });
    }
}

app.post('/auth/login', (req, res) => {
    const user = {
        id: 1,
        username: 'user1',
        email: '[email protected]'
    };
    const token = generateToken(user);
    res.json({ token });
});

app.get('/profile', verifyToken, (req, res) => {
    const user = {
        id: req.userId,
        username: 'user1',
        email: '[email protected]'
    };
    res.json(user);
});

app.listen(3000, () => {
    console.log('Server running on port 3000');
});

前端代码

创建index.html

<!DOCTYPE html>
<html>
<head>
    <title>JWT SSO Example</title>
    <script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            fetch('/auth/login', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    username: 'user1',
                    password: 'pass1'
                })
            })
            .then(response => response.json())
            .then(data => {
                localStorage.setItem('token', data.token);
                console.log('Token:', data.token);
                fetch('/profile', {
                    headers: {
                        'Authorization': `Bearer ${data.token}`
                    }
                })
                .then(response => response.json())
                .then(profile => {
                    console.log('Profile:', profile);
                });
            });
        });
    </script>
</head>
<body>
    <h1>JWT SSO Example</h1>
</body>
</html>

案例二:基于Spring Boot的JWT单点登录实现

本案例将展示如何使用Spring Boot构建一个简单的JWT单点登录系统。

后端代码

创建pom.xml

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>jwt-sso</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.9.1</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.9.1</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.9.1</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
``

创建`JwtUtil.java`:
```java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class JwtUtil {
    private String secret = "your256bitsecret";

    public String generateToken(User user) {
        return Jwts.builder()
                .setSubject(user.getUsername())
                .claim("id", user.getId())
                .claim("email", user.getEmail())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60))
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact();
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public Claims getClaimsFromToken(String token) {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
    }
}

创建User.java

public class User {
    private int id;
    private String username;
    private String email;
    // getters and setters
}

创建JwtAuthenticationFilter.java

import org.springframework.security.core.context.SecurityContextHolder;
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;

public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader("Authorization");
        if (token != null) {
            JwtUtil util = new JwtUtil();
            if (util.validateToken(token)) {
                Claims claims = util.getClaimsFromToken(token);
                User user = new User();
                user.setId((Integer) claims.get("id"));
                user.setUsername((String) claims.getSubject());
                user.setEmail((String) claims.get("email"));
                SecurityContextHolder.getContext().setAuthentication(new UserAuthenticationToken(user));
            }
        }
        filterChain.doFilter(request, response);
    }
}

创建UserAuthenticationToken.java

import org.springframework.security.core.Authentication;

public class UserAuthenticationToken implements Authentication {
    private User user;

    public UserAuthenticationToken(User user) {
        this.user = user;
    }

    @Override
    public Object getCredentials() {
        return user.getPassword();
    }

    @Override
    public Object getPrincipal() {
        return user;
    }

    @Override
    public Object getDetails() {
        return null;
    }

    @Override
    public Object getAuthorities() {
        return null;
    }

    @Override
    public boolean isAuthenticated() {
        return true;
    }

    @Override
    public void setAuthenticated(boolean b) {
        this.user.setPassword(null);
    }

    @Override
    public String getName() {
        return user.getUsername();
    }
}

创建UserController.java

import org.springframework.web.bind.annotation.*;
import java.util.Map;

@RestController
@RequestMapping("/auth")
public class UserController {
    @PostMapping("/login")
    public Map<String, String> login(@RequestBody User user) {
        JwtUtil util = new JwtUtil();
        String token = util.generateToken(user);
        return Map.of("token", token);
    }

    @GetMapping("/profile")
    public User profile() {
        return (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }
}

前端代码

创建index.html

<!DOCTYPE html>
<html>
<head>
    <title>JWT SSO Example</title>
    <script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            axios.post('/auth/login', {
                username: 'user1',
                password: 'pass1'
            })
            .then(response => {
                localStorage.setItem('token', response.data.token);
                console.log('Token:', response.data.token);
                return axios.get('/auth/profile', {
                    headers: {
                        'Authorization': `Bearer ${response.data.token}`
                    }
                });
            })
            .then(response => {
                console.log('Profile:', response.data);
            })
            .catch(error => {
                console.error('Error:', error);
            });
        });
    </script>
</head>
<body>
    <h1>JWT SSO Example</h1>
</body>
</html>
點擊查看更多內容
TA 點贊

若覺得本文不錯,就分享一下吧!

評論

作者其他優質文章

正在加載中
  • 推薦
  • 評論
  • 收藏
  • 共同學習,寫下你的評論
感謝您的支持,我會繼續努力的~
掃碼打賞,你說多少就多少
贊賞金額會直接到老師賬戶
支付方式
打開微信掃一掃,即可進行掃碼打賞哦
今天注冊有機會得

100積分直接送

付費專欄免費學

大額優惠券免費領

立即參與 放棄機會
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號

舉報

0/150
提交
取消