目录
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.Driverurl: jdbc:mysql://localhost:3306/login?useSSL=false&serverTimezone=UTCusername: #自己的数据库用户名password: #自己的数据库密码redis:host: localhostport: 6379mybatis:mapper-locations: classpath:mapper/*.xmljwt:secret: T7e3t3AhK9kS2DdF6gZr4e7hWmYq3t5vT7e3t3AhK9kS2DdF6gZr4e7hWmYq3t5vT7e3t3AhK9kS2DdF6gZr4e7hWmYq3t5vT7e3t3AhK9kS2DdF6gZr4e7hWmYq3t5vexpiration: 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 {@Autowiredprivate 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 中获取存储的 TokenString storedToken = redisTemplate.opsForValue().get(username);// 判断 Redis 中存储的 Token 是否与传入的 Token 相同return storedToken != null && storedToken.equals(token);}/*** 删除token* @param username*/public void removeToken(String username) {// 从 Redis 中删除 TokenredisTemplate.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 usernameString storedToken = redisTemplate.opsForValue().get(username);// Validate the stored tokenif (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 userWHERE username = #{username} AND password = #{password}</select></mapper>
@RestController
@RequestMapping("/user")
@Api(tags = "User API", description = "Operations for managing users")
public class UserController {@Resourceprivate UserService userService;@Resourceprivate 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) {// 检查用户是否有tokenString existingToken = jwtUtils.getTokenIfExists(username);if(existingToken!=null){return ResponseBean.success("已存在token,无需重复登录",existingToken);}else{// 生成tokenString 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 {@Autowiredprivate JwtUtils jwtUtils;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 从请求头中获取 tokenString token = request.getHeader("Authorization");// 验证 tokenif (token != null && jwtUtils.validateToken(token)) {return true; // 验证通过,继续处理请求} else {// 设置响应状态码为 401response.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 {@Autowiredprivate JwtInterceptor jwtInterceptor;@Overridepublic 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
未携带请求头:
携带请求头后:
说明拦截器生效