冬眠的笔记
首页文章分类书单项目关于
冬眠
X

© 2026 冬眠的笔记 · 用文字记录思考,用思考改变生活

首页>文章>Java
JavaJWTSpringBoot鉴权

JWT 实战应用

基于 SpringBoot 实现 JWT 鉴权的完整方案:拦截器、过滤器以及刷新 Token 设计

冬眠
冬眠
专注于技术、阅读与思考
2025-11-19
发布日期
10 min read
阅读时长
浏览量
JWT 实战应用

Spring Boot + JWT 完整示例

1. 项目依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.11.5</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.11.5</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId>
        <version>0.11.5</version>
        <scope>runtime</scope>
    </dependency>
</dependencies>

2. JWT工具类

@Component
public class JwtUtil {
    
    private static final String SECRET = "mySecretKey";
    private static final int JWT_EXPIRATION = 86400000; // 24小时
    
    private Key getSigningKey() {
        byte[] keyBytes = SECRET.getBytes(StandardCharsets.UTF_8);
        return Keys.hmacShaKeyFor(keyBytes);
    }
    
    // 生成JWT令牌
    public String generateToken(String username, List<String> roles) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("roles", roles);
        return createToken(claims, username);
    }
    
    private String createToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + JWT_EXPIRATION))
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                .compact();
    }
    
    // 验证JWT令牌
    public Boolean validateToken(String token, String username) {
        final String tokenUsername = getUsernameFromToken(token);
        return (tokenUsername.equals(username) && !isTokenExpired(token));
    }
    
    // 从令牌中获取用户名
    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }
    
    // 从令牌中获取过期时间
    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }
    
    // 从令牌中获取角色
    @SuppressWarnings("unchecked")
    public List<String> getRolesFromToken(String token) {
        Claims claims = getAllClaimsFromToken(token);
        return (List<String>) claims.get("roles");
    }
    
    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }
    
    private Claims getAllClaimsFromToken(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
    }
    
    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }
}

3. JWT过滤器

@Component
public class JwtRequestFilter extends OncePerRequestFilter {
    
    @Autowired
    private UserDetailsService userDetailsService;
    
    @Autowired
    private JwtUtil jwtUtil;
    
    @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格式为 "Bearer token"
        if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
            jwtToken = requestTokenHeader.substring(7);
            try {
                username = jwtUtil.getUsernameFromToken(jwtToken);
            } catch (IllegalArgumentException e) {
                System.out.println("Unable to get JWT Token");
            } catch (ExpiredJwtException e) {
                System.out.println("JWT Token has expired");
            }
        } else {
            logger.warn("JWT Token does not begin with Bearer String");
        }
        
        // 验证令牌
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            
            if (jwtUtil.validateToken(jwtToken, userDetails.getUsername())) {
                UsernamePasswordAuthenticationToken authToken = 
                    new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authToken);
            }
        }
        chain.doFilter(request, response);
    }
}

4. 认证控制器

@RestController
@RequestMapping("/api/auth")
public class AuthController {
    
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Autowired
    private JwtUtil jwtUtil;
    
    @Autowired
    private UserDetailsService userDetailsService;
    
    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
        try {
            authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                    loginRequest.getUsername(), 
                    loginRequest.getPassword())
            );
        } catch (BadCredentialsException e) {
            throw new BadCredentialsException("Invalid credentials", e);
        }
        
        final UserDetails userDetails = userDetailsService
            .loadUserByUsername(loginRequest.getUsername());
        
        List<String> roles = userDetails.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .collect(Collectors.toList());
        
        final String jwt = jwtUtil.generateToken(userDetails.getUsername(), roles);
        
        return ResponseEntity.ok(new JwtResponse(jwt));
    }
    
    @PostMapping("/refresh")
    public ResponseEntity<?> refreshToken(@RequestBody RefreshTokenRequest request) {
        String token = request.getToken();
        String username = jwtUtil.getUsernameFromToken(token);
        
        if (username != null && !jwtUtil.isTokenExpired(token)) {
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            List<String> roles = userDetails.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList());
            
            String newToken = jwtUtil.generateToken(username, roles);
            return ResponseEntity.ok(new JwtResponse(newToken));
        }
        
        return ResponseEntity.badRequest().body("Invalid token");
    }
}

5. 请求和响应模型

// 登录请求
public class LoginRequest {
    private String username;
    private String password;
    
    // getters and setters
}

// JWT响应
public class JwtResponse {
    private String token;
    private String type = "Bearer";
    
    public JwtResponse(String token) {
        this.token = token;
    }
    
    // getters and setters
}

// 刷新令牌请求
public class RefreshTokenRequest {
    private String token;
    
    // getters and setters
}

Express.js + JWT 示例

1. 安装依赖

npm install express jsonwebtoken bcryptjs cors helmet
npm install --save-dev nodemon

2. JWT中间件

const jwt = require('jsonwebtoken');
const config = require('../config/config');

// JWT验证中间件
const authenticateToken = (req, res, next) => {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
    
    if (!token) {
        return res.status(401).json({ message: 'Access token required' });
    }
    
    jwt.verify(token, config.JWT_SECRET, (err, user) => {
        if (err) {
            return res.status(403).json({ message: 'Invalid or expired token' });
        }
        req.user = user;
        next();
    });
};

// 角色验证中间件
const authorizeRoles = (...roles) => {
    return (req, res, next) => {
        if (!req.user) {
            return res.status(401).json({ message: 'User not authenticated' });
        }
        
        if (!roles.includes(req.user.role)) {
            return res.status(403).json({ message: 'Insufficient permissions' });
        }
        
        next();
    };
};

module.exports = { authenticateToken, authorizeRoles };

3. 认证路由

const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const config = require('../config/config');
const { authenticateToken } = require('../middleware/auth');

const router = express.Router();

// 用户登录
router.post('/login', async (req, res) => {
    try {
        const { username, password } = req.body;
        
        // 查找用户
        const user = await User.findOne({ username });
        if (!user) {
            return res.status(401).json({ message: 'Invalid credentials' });
        }
        
        // 验证密码
        const isValidPassword = await bcrypt.compare(password, user.password);
        if (!isValidPassword) {
            return res.status(401).json({ message: 'Invalid credentials' });
        }
        
        // 生成JWT
        const payload = {
            userId: user._id,
            username: user.username,
            role: user.role
        };
        
        const accessToken = jwt.sign(payload, config.JWT_SECRET, { 
            expiresIn: config.JWT_EXPIRES_IN 
        });
        
        const refreshToken = jwt.sign(payload, config.JWT_REFRESH_SECRET, { 
            expiresIn: config.JWT_REFRESH_EXPIRES_IN 
        });
        
        // 保存refresh token到数据库
        user.refreshToken = refreshToken;
        await user.save();
        
        res.json({
            message: 'Login successful',
            accessToken,
            refreshToken,
            user: {
                id: user._id,
                username: user.username,
                role: user.role
            }
        });
        
    } catch (error) {
        res.status(500).json({ message: 'Server error', error: error.message });
    }
});

// 刷新令牌
router.post('/refresh', async (req, res) => {
    try {
        const { refreshToken } = req.body;
        
        if (!refreshToken) {
            return res.status(401).json({ message: 'Refresh token required' });
        }
        
        // 验证refresh token
        const decoded = jwt.verify(refreshToken, config.JWT_REFRESH_SECRET);
        
        // 查找用户
        const user = await User.findById(decoded.userId);
        if (!user || user.refreshToken !== refreshToken) {
            return res.status(403).json({ message: 'Invalid refresh token' });
        }
        
        // 生成新的access token
        const payload = {
            userId: user._id,
            username: user.username,
            role: user.role
        };
        
        const newAccessToken = jwt.sign(payload, config.JWT_SECRET, { 
            expiresIn: config.JWT_EXPIRES_IN 
        });
        
        res.json({ accessToken: newAccessToken });
        
    } catch (error) {
        res.status(403).json({ message: 'Invalid refresh token' });
    }
});

// 登出
router.post('/logout', authenticateToken, async (req, res) => {
    try {
        const user = await User.findById(req.user.userId);
        if (user) {
            user.refreshToken = null;
            await user.save();
        }
        
        res.json({ message: 'Logout successful' });
    } catch (error) {
        res.status(500).json({ message: 'Server error' });
    }
});

module.exports = router;

4. 受保护的路由示例

const express = require('express');
const { authenticateToken, authorizeRoles } = require('../middleware/auth');

const router = express.Router();

// 所有用户都可以访问
router.get('/profile', authenticateToken, (req, res) => {
    res.json({
        message: 'Profile data',
        user: req.user
    });
});

// 只有管理员可以访问
router.get('/admin', authenticateToken, authorizeRoles('admin'), (req, res) => {
    res.json({
        message: 'Admin only data',
        user: req.user
    });
});

// 管理员和版主可以访问
router.get('/moderator', 
    authenticateToken, 
    authorizeRoles('admin', 'moderator'), 
    (req, res) => {
        res.json({
            message: 'Moderator data',
            user: req.user
        });
    }
);

module.exports = router;

前端集成示例

React + Axios

// api.js - Axios配置
import axios from 'axios';

const API_BASE_URL = 'http://localhost:3000/api';

// 创建axios实例
const api = axios.create({
    baseURL: API_BASE_URL,
});

// 请求拦截器 - 添加JWT token
api.interceptors.request.use(
    (config) => {
        const token = localStorage.getItem('accessToken');
        if (token) {
            config.headers.Authorization = `Bearer ${token}`;
        }
        return config;
    },
    (error) => {
        return Promise.reject(error);
    }
);

// 响应拦截器 - 处理token过期
api.interceptors.response.use(
    (response) => {
        return response;
    },
    async (error) => {
        const originalRequest = error.config;
        
        if (error.response?.status === 401 && !originalRequest._retry) {
            originalRequest._retry = true;
            
            try {
                const refreshToken = localStorage.getItem('refreshToken');
                const response = await axios.post(`${API_BASE_URL}/auth/refresh`, {
                    refreshToken
                });
                
                const { accessToken } = response.data;
                localStorage.setItem('accessToken', accessToken);
                
                // 重试原始请求
                originalRequest.headers.Authorization = `Bearer ${accessToken}`;
                return api(originalRequest);
                
            } catch (refreshError) {
                // Refresh token也过期了,跳转到登录页
                localStorage.removeItem('accessToken');
                localStorage.removeItem('refreshToken');
                window.location.href = '/login';
                return Promise.reject(refreshError);
            }
        }
        
        return Promise.reject(error);
    }
);

export default api;

Vue.js 示例

// store/auth.js - Vuex store
import api from '@/services/api';

const state = {
    user: null,
    accessToken: localStorage.getItem('accessToken'),
    refreshToken: localStorage.getItem('refreshToken'),
    isAuthenticated: false
};

const mutations = {
    SET_USER(state, user) {
        state.user = user;
        state.isAuthenticated = !!user;
    },
    SET_TOKENS(state, { accessToken, refreshToken }) {
        state.accessToken = accessToken;
        state.refreshToken = refreshToken;
        localStorage.setItem('accessToken', accessToken);
        localStorage.setItem('refreshToken', refreshToken);
    },
    CLEAR_AUTH(state) {
        state.user = null;
        state.accessToken = null;
        state.refreshToken = null;
        state.isAuthenticated = false;
        localStorage.removeItem('accessToken');
        localStorage.removeItem('refreshToken');
    }
};

const actions = {
    async login({ commit }, credentials) {
        try {
            const response = await api.post('/auth/login', credentials);
            const { user, accessToken, refreshToken } = response.data;
            
            commit('SET_USER', user);
            commit('SET_TOKENS', { accessToken, refreshToken });
            
            return response.data;
        } catch (error) {
            throw error;
        }
    },
    
    async logout({ commit }) {
        try {
            await api.post('/auth/logout');
        } catch (error) {
            console.error('Logout error:', error);
        } finally {
            commit('CLEAR_AUTH');
        }
    },
    
    async refreshToken({ commit, state }) {
        try {
            const response = await api.post('/auth/refresh', {
                refreshToken: state.refreshToken
            });
            
            const { accessToken } = response.data;
            commit('SET_TOKENS', { 
                accessToken, 
                refreshToken: state.refreshToken 
            });
            
            return accessToken;
        } catch (error) {
            commit('CLEAR_AUTH');
            throw error;
        }
    }
};

export default {
    namespaced: true,
    state,
    mutations,
    actions
};

安全考虑和最佳实践

1. Token存储

// 推荐:使用httpOnly cookie存储refresh token
res.cookie('refreshToken', refreshToken, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'strict',
    maxAge: 7 * 24 * 60 * 60 * 1000 // 7天
});

// Access token可以存储在内存中或localStorage
// 内存存储更安全但页面刷新会丢失
class TokenManager {
    constructor() {
        this.accessToken = null;
    }
    
    setAccessToken(token) {
        this.accessToken = token;
    }
    
    getAccessToken() {
        return this.accessToken;
    }
    
    clearTokens() {
        this.accessToken = null;
    }
}

2. Token黑名单

// Redis实现token黑名单
const redis = require('redis');
const client = redis.createClient();

// 添加token到黑名单
const blacklistToken = async (token, expiresIn) => {
    await client.setex(`blacklist_${token}`, expiresIn, 'true');
};

// 检查token是否在黑名单中
const isTokenBlacklisted = async (token) => {
    const result = await client.get(`blacklist_${token}`);
    return result === 'true';
};

// 在JWT验证中间件中使用
const authenticateToken = async (req, res, next) => {
    const token = req.headers.authorization?.split(' ')[1];
    
    if (!token) {
        return res.status(401).json({ message: 'Token required' });
    }
    
    // 检查黑名单
    if (await isTokenBlacklisted(token)) {
        return res.status(401).json({ message: 'Token has been revoked' });
    }
    
    // 验证token...
};

3. 速率限制

const rateLimit = require('express-rate-limit');

// 登录接口限制
const loginLimiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15分钟
    max: 5, // 最多5次尝试
    message: 'Too many login attempts, please try again later',
    standardHeaders: true,
    legacyHeaders: false,
});

app.use('/api/auth/login', loginLimiter);

这些示例展示了JWT在实际项目中的完整应用,包括后端API、前端集成和安全考虑。根据具体需求,可以进一步定制和优化这些实现。

文章标签

JavaJWTSpringBoot鉴权
JWT 常见问题与故障排除
上一篇

JWT 常见问题与故障排除

2025-11-19

JWT 工作流程
下一篇

JWT 工作流程

2025-11-19

冬眠

冬眠

博主

专注于技术、阅读与思考。在这里记录学习、思考与生活。

116
文章
2
分类
关注我
系列:JWT 实战

第 3 篇,共 5 篇

上一篇

JWT 工作流程

下一篇

JWT 常见问题与故障排除

文章目录

目录

  • Spring Boot + JWT 完整示例
  • Express.js + JWT 示例
  • 前端集成示例
  • 安全考虑和最佳实践

相关文章

查看更多
JWT 工作流程

JWT 工作流程

2025-11-19 · 4 min read

JWT 基础知识

JWT 基础知识

2025-11-19 · 7 min read

JWT 常见问题与故障排除

JWT 常见问题与故障排除

2025-11-19 · 8 min read