Compare commits

...

2 Commits

Author SHA1 Message Date
a1c65ac65f example add jwt 2024-11-19 14:36:09 +08:00
f99948603b c7 changed 2024-11-19 12:36:55 +08:00
7 changed files with 298 additions and 8 deletions

View File

@ -119,7 +119,7 @@ nginx中部署 [paopao-web-ui.zip](../examples/chapter07/paopao-web-ui.zip)
### 7.2 用户注册功能开发
基本上按照层次结构创建系统的包结构, 在`com.lk.paopao`包下创建子包`controllers`、`services`、`entities`、`repositories`。
基本上按照层次结构创建系统的包结构, 在`com.lk.paopao`包下创建子包`controller`、`service`、`entity`、`repository`、`config`。
#### 7.2.1 API设计
@ -158,19 +158,89 @@ spring:
```
yml配置文件的格式需要注意缩进和空格是敏感的需要严格遵循格式。
#### 7.2.3 实体类和Repository接口
#### 7.2.3 实体类UserUserRepository接口
设计和实现实体类User
创建实体类User
对应的数据库接口: UserRepository
创建User对应的数据库接口: UserRepository
#### 7.2.3 创建服务类AuthService
#### 7.2.3 Controller和service
#### 7.2.4 解决跨域问题
创建service类处理用户注册和登录逻辑 AuthService
#### 7.2.4 创建AuthController类
创建controller类处理用户注册和登录请求 AuthController
#### 7.2.5 解决跨域问题
跨域问题是指在使用AJAX进行跨域请求时浏览器阻止访问不同源的Web资源的问题。跨域问题的本质是浏览器的一种安全机制称为同源策略Same-Origin Policy。同源策略限制了来自不同源的文档或脚本间的交互以防止恶意网站窃取数据或进行其他恶意操作
跨域问题的原因
跨域问题主要由以下几种情况引起:
协议不同例如http和https。
域名不同例如example.com和test.com。
端口不同例如80和8080。
目前最常用的解决方案:
> CORSCross-Origin Resource Sharing服务器端设置CORS头部信息允许特定的域名或所有域名进行跨域请求。
在spring boot中可使用自己创建CorsFilter的bean实例的方式解决。
在项目`config`包中创建类`CorsConfig`,添加如下代码:
```java
package com.lk.paopao.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.List;
/**
* 配置类,用于设置跨域请求
*/
@Configuration
public class CorsConfig {
/**
* 创建并配置CorsFilter bean
*
* @return CorsFilter bean实例用于处理跨域请求
*/
@Bean
public CorsFilter corsFilter() {
// 创建URL基于的Cors配置源
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// 创建Cors配置实例
CorsConfiguration config = new CorsConfiguration();
// 配置允许的源
config.setAllowedOrigins(List.of("http://localhost"));
// 配置允许的方法
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
// 配置允许的头部
config.setAllowedHeaders(List.of("*"));
// 允许凭据
config.setAllowCredentials(true);
// 在配置源中注册全局跨域配置
source.registerCorsConfiguration("/**", config);
// 返回CorsFilter实例
return new CorsFilter(source);
}
}
```
### 7.3 发布文章功能开发

View File

@ -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'
}

View File

@ -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("*"));
// 允许凭据

View File

@ -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> 配置了JwtFilter的FilterRegistrationBean实例
*/
@Bean
public FilterRegistrationBean<JwtFilter> loggingFilter() {
// 创建FilterRegistrationBean对象用于注册JwtFilter
FilterRegistrationBean<JwtFilter> registrationBean = new FilterRegistrationBean<>();
// 设置过滤器实例
registrationBean.setFilter(jwtFilter);
// 添加需要过滤的URL模式
registrationBean.addUrlPatterns("/v1/user/*","/v1/post/*");
// 返回配置好的FilterRegistrationBean
return registrationBean;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,23 @@
spring:
jackson:
property-naming-strategy: SNAKE_CASE # 驼峰转下划线
datasource:
url: jdbc:h2:file:./db.h2 # 使用文件存储
driverClassName: org.h2.Driver
username: root
password: root
h2:
console: # 开启console访问 默认false
enabled: true
settings:
trace: true # 开启h2 console 跟踪 方便调试 默认 false
web-allow-others: true # 允许console 远程访问 默认false
path: /h2 # h2 访问路径上下文
jpa:
show-sql: true
open-in-view: false
defer-datasource-initialization: true
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: update