原理
- 用户登录系统时,后端拿到账号密码进行登录校验(查询数据库),校验通过生成token返回给前端,并放行请求的资源
- 后续前端每次请求后端接口时,都在请求头中带上token,后端的全局拦截器拦截到请求,去redis查询缓存的token,找到对应token则放行请求到对应接口方法,否则返回未登录提醒。
pom文件中引入依赖(gradle同样)
<!-- Redis 相关依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- JWT 相关依赖 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.11.2</version>
</dependency>
配置redis
# Redis 连接配置
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=your_redis_password
JWT Token 生成校验工具
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class JwtTokenUtil {
private static final String SECRET_KEY = "your_secret_key";
private static final long EXPIRATION_TIME = 86400000; // 10 days
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET_KEY)
.compact();
}
public String getUsernameFromToken(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getSubject();
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
}
用户登录控制器:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.HashMap;
import java.util.Map;
@RestController
public class LoginController {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@PostMapping("/login")
public Map<String, String> login(@RequestBody Map<String, String> request) {
String username = request.get("username");
String password = request.get("password");
// todo 这里可以加入登录逻辑,验证用户名密码
// 校验通过 生成 Token
// 校验不通过 给出错误提示用户名密码错误
String token = jwtTokenUtil.generateToken(username);
// 缓存 Token 到 Redis
redisTemplate.opsForValue().set(username, token);
// 设置过期时间
redisTemplate.expire(username, jwtTokenUtil.EXPIRATION_TIME, TimeUnit.MILLISECONDS);
//token返回给前端
Map<String, String> response = new HashMap<>();
response.put("token", token);
return response;
}
}
编写全局拦截器
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class JwtTokenInterceptor extends HandlerInterceptorAdapter {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 排除swagger访问接口
String path = request.getRequestURI();
if (path.contains("swagger") || path.contains("api-docs")) {
return true;
}
String token = request.getHeader("Authorization");
// 校验 Token
if (token == null || !jwtTokenUtil.validateToken(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Please login first.");
return false;
}
//todo 查询redis缓存的token进行比对,比对上放行请求
return true;
}
}
注册拦截器:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JwtTokenInterceptor()).addPathPatterns("/**");
}
}
后记:
以上实现过程因项目不同会有所调整,这里只是提供一种能够实现的方案。