目录
1.背景介绍
2.前提工作
3.具体代码
(1)相关依赖
(2)相关配置文件
(3)JwtUtils类
(4)准备好登录逻辑代码(Dao、Service、Controller)
(5)拦截器JwtInterceptor类
(6)注册拦截器到配置类
4. 测试
1.背景介绍
JWT在之前文章提到过,JWT(JSON Web Token)是一种用于身份验证和授权的开放标准(RFC 7519),它允许在网络中安全地传输声明(claims)作为 JSON 对象。JWT 可以通过数字签名或加密来验证数据的完整性和真实性,从而保证数据在传输过程中不被篡改。
工作流程:
- 用户通过用户名和密码等方式进行身份验证。
- 服务器验证用户身份,并生成一个 JWT。
- 服务器将 JWT 发送给客户端。
- 客户端将 JWT 存储起来,通常是在本地存储或者内存中。
- 客户端将 JWT 添加到每个后续的 HTTP 请求的 Authorization 头部中。
- 服务器收到请求后,解析 JWT 并验证签名。
- 如果验证通过,则处理请求;如果验证失败,则拒绝请求。
2.前提工作
1.Redis,用于将生成的token存入其中
2.用户登录Controller
3.Jwtutils、相关依赖
4.拦截器JwtInterceptor
5.配置类WebMvcConfig,用于注册拦截器
3.具体代码
(1)相关依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
(2)相关配置文件
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/login?useSSL=false&serverTimezone=UTC
username: #自己的数据库用户名
password: #自己的数据库密码
redis:
host: localhost
port: 6379
mybatis:
mapper-locations: classpath:mapper/*.xml
jwt:
secret: T7e3t3AhK9kS2DdF6gZr4e7hWmYq3t5vT7e3t3AhK9kS2DdF6gZr4e7hWmYq3t5vT7e3t3AhK9kS2DdF6gZr4e7hWmYq3t5vT7e3t3AhK9kS2DdF6gZr4e7hWmYq3t5v
expiration: 864000
(3)JwtUtils类
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@Component
public class JwtUtils {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
/**
* 生成token
* @param username
* @return
*/
public String generateToken(String username) {
String token = Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
// 将 Token 存储到 Redis 中
redisTemplate.opsForValue().set(username, token, expiration, TimeUnit.MILLISECONDS);
return token;
}
/**
* 验证token
* @param token
* @return
*/
public boolean validateToken(String token) {
// 从 Token 中获取用户名
String username = getUsernameFromToken(token);
// 从 Redis 中获取存储的 Token
String storedToken = redisTemplate.opsForValue().get(username);
// 判断 Redis 中存储的 Token 是否与传入的 Token 相同
return storedToken != null && storedToken.equals(token);
}
/**
* 删除token
* @param username
*/
public void removeToken(String username) {
// 从 Redis 中删除 Token
redisTemplate.delete(username);
}
/**
* 根据token获取用户信息
* @param token
* @return
*/
public String getUsernameFromToken(String token) {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return claims.getSubject();
}
/**
* 判断token是否存在
* @param username
* @return
*/
public String getTokenIfExists(String username) {
// Check if a valid token exists in Redis for the given username
String storedToken = redisTemplate.opsForValue().get(username);
// Validate the stored token
if (storedToken != null && validateToken(storedToken)) {
return storedToken;
} else {
return null;
}
}
}
(4)准备好登录逻辑代码(Dao、Service、Controller)
import com.zhan.zhan215.Entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface UserMapper{
User getUserByUsernameAndPassword(@Param("username")String username,@Param("password")String password);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhan.zhan215.Dao.UserMapper">
<select id="getUserByUsernameAndPassword" parameterType="com.zhan.zhan215.Entity.User" resultType="com.zhan.zhan215.Entity.User">
select *
from user
WHERE username = #{username} AND password = #{password}
</select>
</mapper>
@RestController
@RequestMapping("/user")
@Api(tags = "User API", description = "Operations for managing users")
public class UserController {
@Resource
private UserService userService;
@Resource
private JwtUtils jwtUtils;
@PostMapping("/login")
@ApiOperation(value = "登录控制器")
public ResponseBean login(@RequestParam(value = "username") String username, @RequestParam(value = "password") String password) {
User user1 = userService.getUserByUsernameAndPassword(username, password);
System.out.println(user1);
if(user1!=null) {
// 检查用户是否有token
String existingToken = jwtUtils.getTokenIfExists(username);
if(existingToken!=null){
return ResponseBean.success("已存在token,无需重复登录",existingToken);
}else{
// 生成token
String token = jwtUtils.generateToken(username);
System.out.println(token);
return ResponseBean.success("登录成功", token);
// 将token返回给前端
}
}
return ResponseBean.error("用户名或密码错误");
}
@GetMapping("/get")
public ResponseBean getAll(@RequestHeader("Authorization") String token){
if(jwtUtils.validateToken(token)){
return ResponseBean.success(userMapper.getAll());
}
else{
return ResponseBean.error("token无效");
}
// 倘若不写拦截器 则每个请求方法都要像getAll这样去做判断,非常麻烦
}
@GetMapping("/getAllByPage")
public ResponseBean getAllByPage(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "2") Integer size) {
return ResponseBean.success(userService.getAllByPage(page, size));
}
}
(5)拦截器JwtInterceptor类
import com.zhan.zhan215.Utils.JwtUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtils jwtUtils;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从请求头中获取 token
String token = request.getHeader("Authorization");
// 验证 token
if (token != null && jwtUtils.validateToken(token)) {
return true; // 验证通过,继续处理请求
} else {
// 设置响应状态码为 401
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
// 设置响应内容为 JSON 格式的错误信息
String errorMessage = "{\"error\": \"token无效\"}";
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(errorMessage);
return false; // 验证失败,不继续处理请求
}
}
}
(6)注册拦截器到配置类
import com.zhan.zhan215.Interceptor.JwtInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
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 {
@Autowired
private JwtInterceptor jwtInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/user/getAllByPage")// 配置拦截路径,这里假设所有 API 都需要验证 token
.excludePathPatterns("/user/search");// 这是可以排除的路径,
// 比如登录接口,登录接口不需要 token 验证
// 如果有多个拦截器,可以这样继续添加
// registry.addInterceptor(jwtInterceptor).addPathPatterns("/user/login");
// registry.addInterceptor(jwtInterceptor).addPathPatterns("/user/update");
// registry.addInterceptor(jwtInterceptor).addPathPatterns("/user/delete");
// registry.addInterceptor(jwtInterceptor).addPathPatterns("/user/getById");
}
}
4. 测试
先进行登录:
在后端控制台查看token
未携带请求头:
携带请求头后:
说明拦截器生效