在现代Web开发中,前后端分离架构已经成为主流。若依框架(RuoYi)作为一个基于Spring Boot和Spring Cloud的快速开发平台,提供了丰富的功能模块和良好的扩展性。通过整合JWT(JSON Web Token),可以实现更加安全、高效的登录验证机制。
下面我们将详细解析如何在若依框架中整合JWT实现前后端分离的登录验证。
JWT是一种用于在网络应用环境间传递声明的开放标准(RFC 7519)。它由三部分组成:Header(头部)、Payload(载荷)和Signature(签名)。JWT的主要特点包括:
一个典型的JWT结构如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
在pom.xml
中添加JWT相关的依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
创建一个工具类用于生成和解析JWT令牌:
import io.jsonwebtoken.*;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtTokenUtil {
private static final String SECRET_KEY = "yourSecretKey"; // 密钥
private static final long EXPIRATION_TIME = 86400000; // 过期时间(毫秒)
// 生成JWT
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
return Jwts.builder()
.setClaims(claims)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET_KEY)
.compact();
}
// 解析JWT
public String getUsernameFromToken(String token) {
return extractClaim(token, Claims::getSubject);
}
// 验证JWT是否有效
public Boolean validateToken(String token, String username) {
final String extractedUsername = getUsernameFromToken(token);
return (extractedUsername.equals(username) && !isTokenExpired(token));
}
// 提取JWT中的声明
private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
// 检查JWT是否过期
private Boolean isTokenExpired(String token) {
return extractClaim(token, Claims::getExpiration).before(new Date());
}
// 提取所有声明
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
}
在用户登录成功后,生成JWT并返回给前端:
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
String username = loginRequest.getUsername();
String password = loginRequest.getPassword();
// 验证用户名和密码(此处假设已实现认证逻辑)
if (!"admin".equals(username) || !"password".equals(password)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid credentials");
}
// 生成JWT
String token = jwtTokenUtil.generateToken(username);
// 返回JWT给前端
return ResponseEntity.ok(new HashMap<String, String>() {{
put("token", token);
}});
}
}
为了确保每次请求都携带有效的JWT,可以添加一个拦截器进行验证:
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtTokenUtil.getUsernameFromToken(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
if (jwtTokenUtil.validateToken(jwt, username)) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
chain.doFilter(request, response);
}
}
前端在登录成功后保存JWT,并在每次请求时将JWT放入Authorization
头中:
function login(username, password) {
fetch('/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
})
.then(response => response.json())
.then(data => {
localStorage.setItem('token', data.token); // 保存JWT
});
}
function callApi() {
const token = localStorage.getItem('token');
fetch('/api/some-endpoint', {
method: 'GET',
headers: { 'Authorization': `Bearer ${token}` }
})
.then(response => response.json())
.then(data => console.log(data));
}
以下是整个JWT登录验证的流程图:
sequenceDiagram participant 用户 as User participant 前端 as Frontend participant 后端 as Backend participant 数据库 as Database Note over 用户,Frontend: 用户输入账号密码 Frontend->>Backend: POST /auth/login {username, password} Backend->>Database: 验证用户名和密码 Database-->>Backend: 返回验证结果 Backend->>Frontend: 返回JWT Frontend->>用户: 显示登录成功 Note over 用户,Frontend: 用户发起API请求 Frontend->>Backend: GET /api/... Authorization: BearerBackend->>Backend: 验证JWT有效性 alt JWT有效 Backend->>Database: 查询数据 Database-->>Backend: 返回数据 Backend-->>Frontend: 返回响应数据 else JWT无效 Backend-->>Frontend: 返回401 Unauthorized end
通过以上步骤,我们成功地在若依框架中整合了JWT,实现了前后端分离的登录验证机制。这种方式不仅简化了会话管理,还提高了系统的可扩展性和安全性。