From a1c65ac65f8f0a99b0d9fdd114d4a5ff09d87780 Mon Sep 17 00:00:00 2001 From: many2many <6168830@qq.com> Date: Tue, 19 Nov 2024 14:36:09 +0800 Subject: [PATCH] example add jwt --- examples/chapter07/paopao/build.gradle | 5 ++ .../java/com/lk/paopao/conf/CorsConfig.java | 4 +- .../java/com/lk/paopao/jwt/JwtConfig.java | 36 ++++++++ .../java/com/lk/paopao/jwt/JwtFilter.java | 69 +++++++++++++++ .../java/com/lk/paopao/jwt/JwtService.java | 87 +++++++++++++++++++ 5 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 examples/chapter07/paopao/src/main/java/com/lk/paopao/jwt/JwtConfig.java create mode 100644 examples/chapter07/paopao/src/main/java/com/lk/paopao/jwt/JwtFilter.java create mode 100644 examples/chapter07/paopao/src/main/java/com/lk/paopao/jwt/JwtService.java diff --git a/examples/chapter07/paopao/build.gradle b/examples/chapter07/paopao/build.gradle index 3f4d2b2..4ba0709 100644 --- a/examples/chapter07/paopao/build.gradle +++ b/examples/chapter07/paopao/build.gradle @@ -25,6 +25,11 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' runtimeOnly 'com.h2database:h2:2.1.214' + + implementation 'io.jsonwebtoken:jjwt-api:0.12.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5' + testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } diff --git a/examples/chapter07/paopao/src/main/java/com/lk/paopao/conf/CorsConfig.java b/examples/chapter07/paopao/src/main/java/com/lk/paopao/conf/CorsConfig.java index 47a727b..25be73a 100644 --- a/examples/chapter07/paopao/src/main/java/com/lk/paopao/conf/CorsConfig.java +++ b/examples/chapter07/paopao/src/main/java/com/lk/paopao/conf/CorsConfig.java @@ -26,9 +26,9 @@ public class CorsConfig { CorsConfiguration config = new CorsConfiguration(); // 配置允许的源 - config.setAllowedOrigins(List.of("http://localhost")); + config.setAllowedOrigins(List.of("http://localhost","http://localhost:8880")); // 配置允许的方法 - config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE")); + config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); // 配置允许的头部 config.setAllowedHeaders(List.of("*")); // 允许凭据 diff --git a/examples/chapter07/paopao/src/main/java/com/lk/paopao/jwt/JwtConfig.java b/examples/chapter07/paopao/src/main/java/com/lk/paopao/jwt/JwtConfig.java new file mode 100644 index 0000000..915dcb1 --- /dev/null +++ b/examples/chapter07/paopao/src/main/java/com/lk/paopao/jwt/JwtConfig.java @@ -0,0 +1,36 @@ +package com.lk.paopao.jwt; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 配置类,用于在Spring Boot应用中注册JWT过滤器 + */ +@Configuration +public class JwtConfig { + + /** + * 自动注入JwtFilter,用于处理JWT相关的请求 + */ + @Autowired + private JwtFilter jwtFilter; + + /** + * 创建并配置JwtFilter的FilterRegistrationBean + * + * @return FilterRegistrationBean 配置了JwtFilter的FilterRegistrationBean实例 + */ + @Bean + public FilterRegistrationBean loggingFilter() { + // 创建FilterRegistrationBean对象,用于注册JwtFilter + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + // 设置过滤器实例 + registrationBean.setFilter(jwtFilter); + // 添加需要过滤的URL模式 + registrationBean.addUrlPatterns("/v1/user/*","/v1/post/*"); + // 返回配置好的FilterRegistrationBean + return registrationBean; + } +} diff --git a/examples/chapter07/paopao/src/main/java/com/lk/paopao/jwt/JwtFilter.java b/examples/chapter07/paopao/src/main/java/com/lk/paopao/jwt/JwtFilter.java new file mode 100644 index 0000000..ec63910 --- /dev/null +++ b/examples/chapter07/paopao/src/main/java/com/lk/paopao/jwt/JwtFilter.java @@ -0,0 +1,69 @@ +package com.lk.paopao.jwt; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; +import java.io.IOException; + +/** + * JWT过滤器,用于处理每次请求,验证JWT令牌并设置用户认证信息。 + */ +@Component +public class JwtFilter extends OncePerRequestFilter { + + @Autowired + private JwtService jwtService; + + /** + * 核心过滤方法,处理每次请求。 + * + * @param request 请求对象,用于获取请求头中的JWT令牌。 + * @param response 响应对象,用于处理响应。 + * @param filterChain 过滤链,用于继续处理请求链。 + * @throws ServletException 如果过滤过程中发生Servlet异常。 + * @throws IOException 如果过滤过程中发生IO异常。 + */ + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + try { + // 解析JWT令牌。 + String jwt = parseJwt(request); + if (jwt != null && jwtService.validateToken(jwt)) { + // 从JWT令牌中获取用户名。 + String username = jwtService.getUserNameFromJwtToken(jwt); + + // 将用户名设置为请求属性,以便后续处理使用。 + request.setAttribute("username", username); + } + } catch (Exception e) { + // 记录无法设置用户认证的错误。 + logger.error("Cannot set user authentication:", e); + } + + // 继续处理请求链。 + filterChain.doFilter(request, response); + } + + /** + * 解析请求中的JWT令牌。 + * + * @param request 请求对象,用于获取请求头中的JWT令牌。 + * @return 如果解析成功,则返回JWT令牌字符串;否则返回null。 + */ + private String parseJwt(HttpServletRequest request) { + // 从请求头中获取"Authorization"字段。 + String headerAuth = request.getHeader("Authorization"); + // 如果存在且以"Bearer "开头,则提取并返回令牌字符串。 + if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) { + return headerAuth.substring(7); + } + + // 如果不符合条件,则返回null。 + return null; + } +} diff --git a/examples/chapter07/paopao/src/main/java/com/lk/paopao/jwt/JwtService.java b/examples/chapter07/paopao/src/main/java/com/lk/paopao/jwt/JwtService.java new file mode 100644 index 0000000..2bef99f --- /dev/null +++ b/examples/chapter07/paopao/src/main/java/com/lk/paopao/jwt/JwtService.java @@ -0,0 +1,87 @@ +package com.lk.paopao.jwt; + +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import org.springframework.stereotype.Component; +import io.jsonwebtoken.Jwts; + +import javax.crypto.SecretKey; +import java.util.Date; + +/** + * JWT服务类,用于生成和验证JWT Token + */ +@Component +public class JwtService { + + // JWT密钥,用于签名和验证 + private static final String SECRET = "uYuVw0mke38MfLhO19wUQyRgwrmYo89ibpQTXPHi4vg="; + + // JWT过期时间,单位:毫秒 + private static final long JWT_EXPIRATION_MS = 1000 * 3600 * 24; + + /** + * 生成JWT Token + * + * @param username 用户名,作为JWT的主题 + * @return 生成的JWT Token字符串 + */ + public String generateJwtToken(String username) { + // 创建JWT,设置主题、签发时间、过期时间和签名密钥 + return Jwts.builder().subject(username) + .issuedAt(new Date()) + .expiration(new Date(new Date().getTime() + JWT_EXPIRATION_MS)) + .signWith(getSigningKey()) + .compact(); + } + + /** + * 获取签名密钥 + * + * @return 签名所用的SecretKey + */ + private SecretKey getSigningKey() { + // 解码JWT密钥 + byte[] keyBytes = Decoders.BASE64.decode(SECRET); + return Keys.hmacShaKeyFor(keyBytes); + } + + /** + * 从JWT Token中提取用户名 + * + * @param token JWT Token字符串 + * @return 提取的用户名 + */ + public String getUserNameFromJwtToken(String token) { + // 解析JWT Token并提取主题(用户名) + return Jwts.parser() + .verifyWith(getSigningKey()) + .build() + .parseSignedClaims(token) + .getPayload() + .getSubject(); + } + + /** + * 验证JWT Token的有效性 + * + * @param authToken JWT Token字符串 + * @return 如果Token有效返回true,否则返回false + */ + public boolean validateToken(String authToken) { + try { + // 尝试解析JWT Token,如无异常则表示Token有效 + Jwts.parser() + .verifyWith(getSigningKey()) + .build() + .parseSignedClaims(authToken); + return true; + } catch (Exception e) { + // 记录JWT签名异常 + System.out.println("Invalid JWT signature: " + e.getMessage()); + } + + return false; + } + +}