feat(jwt): implement JWT-based authentication system

This commit is contained in:
2025-11-29 12:00:30 +08:00
parent 894a1c5d07
commit de52e2816c
23 changed files with 540 additions and 8 deletions

4
.idea/compiler.xml generated
View File

@@ -8,10 +8,12 @@
<processorPath useClasspath="false"> <processorPath useClasspath="false">
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.42/8365263844ebb62398e0dc33057ba10ba472d3b8/lombok-1.18.42.jar" /> <entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.42/8365263844ebb62398e0dc33057ba10ba472d3b8/lombok-1.18.42.jar" />
</processorPath> </processorPath>
<module name="weblog-springboot.weblog-module-jwt.main" />
<module name="weblog-springboot.weblog-web.test" /> <module name="weblog-springboot.weblog-web.test" />
<module name="weblog-springboot.weblog-module-admin.test" />
<module name="weblog-springboot.weblog-module-common.test" /> <module name="weblog-springboot.weblog-module-common.test" />
<module name="weblog-springboot.weblog-module-admin.main" /> <module name="weblog-springboot.weblog-module-admin.main" />
<module name="weblog-springboot.weblog-module-jwt.test" />
<module name="weblog-springboot.weblog-module-admin.test" />
<module name="weblog-springboot.weblog-module-common.main" /> <module name="weblog-springboot.weblog-module-common.main" />
<module name="weblog-springboot.weblog-web.main" /> <module name="weblog-springboot.weblog-web.main" />
</profile> </profile>

1
.idea/gradle.xml generated
View File

@@ -10,6 +10,7 @@
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/weblog-module-admin" /> <option value="$PROJECT_DIR$/weblog-module-admin" />
<option value="$PROJECT_DIR$/weblog-module-common" /> <option value="$PROJECT_DIR$/weblog-module-common" />
<option value="$PROJECT_DIR$/weblog-module-jwt" />
<option value="$PROJECT_DIR$/weblog-web" /> <option value="$PROJECT_DIR$/weblog-web" />
</set> </set>
</option> </option>

3
.idea/modules.xml generated
View File

@@ -3,9 +3,8 @@
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/weblog-springboot.iml" filepath="$PROJECT_DIR$/weblog-springboot.iml" /> <module fileurl="file://$PROJECT_DIR$/weblog-springboot.iml" filepath="$PROJECT_DIR$/weblog-springboot.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/weblog-module-admin/weblog-springboot.weblog-module-admin.main.iml" filepath="$PROJECT_DIR$/.idea/modules/weblog-module-admin/weblog-springboot.weblog-module-admin.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/weblog-module-common/weblog-springboot.weblog-module-common.main.iml" filepath="$PROJECT_DIR$/.idea/modules/weblog-module-common/weblog-springboot.weblog-module-common.main.iml" /> <module fileurl="file://$PROJECT_DIR$/.idea/modules/weblog-module-common/weblog-springboot.weblog-module-common.main.iml" filepath="$PROJECT_DIR$/.idea/modules/weblog-module-common/weblog-springboot.weblog-module-common.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/weblog-web/weblog-springboot.weblog-web.main.iml" filepath="$PROJECT_DIR$/.idea/modules/weblog-web/weblog-springboot.weblog-web.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/weblog-web/weblog-springboot.weblog-web.test.iml" filepath="$PROJECT_DIR$/.idea/modules/weblog-web/weblog-springboot.weblog-web.test.iml" />
</modules> </modules>
</component> </component>
</project> </project>

10
gradle/libs.versions.toml Normal file
View File

@@ -0,0 +1,10 @@
[versions]
# 定义版本号
jjwt = "0.13.0"
[libraries]
# 定义依赖包的别名 (bundles 是可选的libraries 是必须的)
# 对应 Maven 的 <groupId>io.jsonwebtoken</groupId>
jjwt-api = { group = "io.jsonwebtoken", name = "jjwt-api", version.ref = "jjwt" }
jjwt-impl = { group = "io.jsonwebtoken", name = "jjwt-impl", version.ref = "jjwt" }
jjwt-jackson = { group = "io.jsonwebtoken", name = "jjwt-jackson", version.ref = "jjwt" }

View File

@@ -3,3 +3,4 @@ rootProject.name = "weblog-springboot"
include("weblog-web") include("weblog-web")
include("weblog-module-admin") include("weblog-module-admin")
include("weblog-module-common") include("weblog-module-common")
include("weblog-module-jwt")

View File

@@ -3,8 +3,13 @@ plugins {
} }
dependencies { dependencies {
// Spring Security
implementation("org.springframework.boot:spring-boot-starter-security")
// Test // Test
testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.boot:spring-boot-starter-test")
implementation(project(":weblog-module-common")) implementation(project(":weblog-module-common"))
implementation(project(":weblog-module-jwt"))
testImplementation("org.springframework.security:spring-security-test")
} }

View File

@@ -0,0 +1,61 @@
package com.hanserwei.admin.config;
import com.hanserwei.jwt.config.JwtAuthenticationSecurityConfig;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Resource
private JwtAuthenticationSecurityConfig jwtAuthenticationSecurityConfig;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 1. 禁用 CSRF
.csrf(AbstractHttpConfigurer::disable)
// 2. 禁用表单登录
.formLogin(AbstractHttpConfigurer::disable)
// 3. 授权规则配置
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").authenticated() // 保护 /admin/**
.anyRequest().permitAll() // 其他放行
)
// 4. 会话管理:无状态
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
// 5. 应用自定义配置 (核心变化)
.with(jwtAuthenticationSecurityConfig, Customizer.withDefaults());
return http.build();
}
/**
* 定义 AuthenticationProvider Bean。
* Spring Security 会自动将此 Provider 注入到 AuthenticationManager 中。
*/
@Bean
public AuthenticationProvider authenticationProvider(UserDetailsService userDetailsService,
PasswordEncoder passwordEncoder) {
// 修正点:直接在构造函数中传入 userDetailsService
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(userDetailsService);
// 设置密码编码器 (PasswordEncoder 依然使用 setter 设置)
authProvider.setPasswordEncoder(passwordEncoder);
return authProvider;
}
}

View File

@@ -18,7 +18,7 @@ dependencies {
// aop // aop
implementation("org.springframework.boot:spring-boot-starter-aop") implementation("org.springframework.boot:spring-boot-starter-aop")
// web // web
implementation("org.springframework.boot:spring-boot-starter-web") api("org.springframework.boot:spring-boot-starter-web")
// postgresql // postgresql
runtimeOnly("org.postgresql:postgresql") runtimeOnly("org.postgresql:postgresql")
} }

View File

@@ -4,4 +4,5 @@ import com.hanserwei.common.domain.dataobject.User;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> { public interface UserRepository extends JpaRepository<User, Long> {
User getUsersByUsername(String username);
} }

View File

@@ -13,6 +13,8 @@ public enum ResponseCodeEnum implements BaseExceptionInterface {
PARAM_NOT_VALID("10001", "参数错误"), PARAM_NOT_VALID("10001", "参数错误"),
// ----------- 业务异常状态码 ----------- // ----------- 业务异常状态码 -----------
LOGIN_FAIL("20000", "登录失败"),
USERNAME_OR_PWD_ERROR("20001", "用户名或密码错误"),
; ;
// 异常码 // 异常码

View File

@@ -0,0 +1,16 @@
plugins {
`java-library`
}
group = "com.hanserwei.jwt"
version = project.parent?.version ?: "0.0.1-SNAPSHOT"
dependencies {
implementation("org.springframework.boot:spring-boot-starter-security")
implementation(project(":weblog-module-common"))
// jwt
api(libs.jjwt.api)
runtimeOnly(libs.jjwt.impl)
runtimeOnly(libs.jjwt.jackson)
}

View File

@@ -0,0 +1,39 @@
package com.hanserwei.jwt.config;
import com.hanserwei.jwt.filter.JwtAuthenticationFilter;
import com.hanserwei.jwt.handler.RestAuthenticationFailureHandler;
import com.hanserwei.jwt.handler.RestAuthenticationSuccessHandler;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class JwtAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Resource
private RestAuthenticationSuccessHandler restAuthenticationSuccessHandler;
@Resource
private RestAuthenticationFailureHandler restAuthenticationFailureHandler;
@Override
public void configure(HttpSecurity httpSecurity) {
// 1. 实例化自定义过滤器
JwtAuthenticationFilter filter = new JwtAuthenticationFilter();
// 2. 这里的 AuthenticationManager 是从外部传入的 (httpSecurity sharedObject)
filter.setAuthenticationManager(httpSecurity.getSharedObject(AuthenticationManager.class));
// 3. 设置成功/失败处理器
filter.setAuthenticationSuccessHandler(restAuthenticationSuccessHandler);
filter.setAuthenticationFailureHandler(restAuthenticationFailureHandler);
// 4. 将过滤器添加到 UsernamePasswordAuthenticationFilter 之前
// (JWT 校验通常在用户名密码校验之前或者用来替换它addFilterBefore 是最稳妥的)
httpSecurity.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
}
}

View File

@@ -0,0 +1,20 @@
package com.hanserwei.jwt.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class PasswordEncoderConfig {
public static void main(String[] args) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
System.out.println(encoder.encode("hanserwei"));
}
@Bean
public PasswordEncoder passwordEncoder() {
// BCrypt 是一种安全且适合密码存储的哈希算法,它在进行哈希时会自动加入“盐”,增加密码的安全性。
return new BCryptPasswordEncoder();
}
}

View File

@@ -0,0 +1,14 @@
package com.hanserwei.jwt.exception;
import org.springframework.security.core.AuthenticationException;
public class UsernameOrPasswordNullException extends AuthenticationException {
public UsernameOrPasswordNullException(String msg, Throwable cause) {
super(msg, cause);
}
public UsernameOrPasswordNullException(String msg) {
super(msg);
}
}

View File

@@ -0,0 +1,47 @@
package com.hanserwei.jwt.filter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hanserwei.jwt.exception.UsernameOrPasswordNullException;
import io.micrometer.common.util.StringUtils;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RegexRequestMatcher;
import java.io.IOException;
import java.util.Objects;
public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public JwtAuthenticationFilter() {
super(new RegexRequestMatcher("/login", HttpMethod.POST.name()));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException {
ObjectMapper mapper = new ObjectMapper();
// 解析提交的JSON数据
JsonNode jsonNode = mapper.readTree(request.getInputStream());
JsonNode usernameNode = jsonNode.get("username");
JsonNode passwordNode = jsonNode.get("password");
// 判断用户名、密码是否为空
if (Objects.isNull(usernameNode) || Objects.isNull(passwordNode)
|| StringUtils.isBlank(usernameNode.textValue()) || StringUtils.isBlank(passwordNode.textValue())) {
throw new UsernameOrPasswordNullException("用户名或密码不能为空");
}
String username = usernameNode.textValue();
String password = passwordNode.textValue();
// 将用户名、密码封装到 Token 中
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
= new UsernamePasswordAuthenticationToken(username, password);
return getAuthenticationManager().authenticate(usernamePasswordAuthenticationToken);
}
}

View File

@@ -0,0 +1,37 @@
package com.hanserwei.jwt.handler;
import com.hanserwei.common.enums.ResponseCodeEnum;
import com.hanserwei.common.utils.Response;
import com.hanserwei.jwt.exception.UsernameOrPasswordNullException;
import com.hanserwei.jwt.utils.ResultUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Slf4j
@Component
public class RestAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
log.warn("AuthenticationException: ", exception);
if (exception instanceof UsernameOrPasswordNullException) {
// 用户名或密码为空
ResultUtil.fail(response, Response.fail(exception.getMessage()));
return;
} else if (exception instanceof BadCredentialsException) {
// 用户名或密码错误
ResultUtil.fail(response, Response.fail(ResponseCodeEnum.USERNAME_OR_PWD_ERROR));
return;
}
// 登录失败
ResultUtil.fail(response, Response.fail(ResponseCodeEnum.LOGIN_FAIL));
}
}

View File

@@ -0,0 +1,40 @@
package com.hanserwei.jwt.handler;
import com.hanserwei.common.utils.Response;
import com.hanserwei.jwt.model.LoginRspVO;
import com.hanserwei.jwt.utils.JwtTokenHelper;
import com.hanserwei.jwt.utils.ResultUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Slf4j
@Component
public class RestAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Resource
private JwtTokenHelper jwtTokenHelper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
// 从 authentication 对象中获取用户的 UserDetails 实例,这里是获取用户的用户名
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
// 通过用户名生成 Token
String username = userDetails.getUsername();
String token = jwtTokenHelper.generateToken(username);
// 返回 Token
LoginRspVO loginRspVO = LoginRspVO.builder().token(token).build();
ResultUtil.ok(response, Response.success(loginRspVO));
}
}

View File

@@ -0,0 +1,19 @@
package com.hanserwei.jwt.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class LoginRspVO {
/**
* Token 值
*/
private String token;
}

View File

@@ -0,0 +1,36 @@
package com.hanserwei.jwt.service;
import com.hanserwei.common.domain.dataobject.User;
import com.hanserwei.common.domain.repository.UserRepository;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Objects;
@Service
@Slf4j
public class UserDetailServiceImpl implements UserDetailsService {
@Resource
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 从数据库查询用户信息
User user = userRepository.getUsersByUsername(username);
// 判断用户是否存在
if (Objects.isNull(user)) {
throw new UsernameNotFoundException("用户不存在");
}
// authorities 用于指定角色,这里写死为 ADMIN 管理员
return org.springframework.security.core.userdetails.User.withUsername(user.getUsername())
.password(user.getPassword())
.authorities("ADMIN")
.build();
}
}

View File

@@ -0,0 +1,110 @@
package com.hanserwei.jwt.utils;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
@Component
public class JwtTokenHelper implements InitializingBean {
/**
* 签发人
*/
@Value("${jwt.issuer}")
private String issuer;
/**
* 秘钥 (JJWT 0.12+ 强制要求使用 SecretKey 接口,且长度必须符合算法安全标准)
*/
private SecretKey key;
/**
* JWT 解析器 (线程安全,建议复用)
*/
private JwtParser jwtParser;
/**
* 工具方法:生成一个符合 HS512 标准的安全 Base64 秘钥
* 用于生成后填入 application.yml
*/
public static String generateBase64Key() {
SecretKey secretKey = Jwts.SIG.HS512.key().build();
return Encoders.BASE64.encode(secretKey.getEncoded()); // Fixed line
}
public static void main(String[] args) {
String key = generateBase64Key();
System.out.println("请将此 Key 复制到配置文件中 (HS512需要长Key): " + key);
}
/**
* 设置 Base64 秘钥
* 官方建议HS512 算法至少需要 512 bit (64字节) 的秘钥长度,否则会抛出 WeakKeyException
*/
@Value("${jwt.secret}")
public void setBase64Key(String base64Key) {
// 使用 JJWT 提供的 Decoders 工具,或者使用 java.util.Base64 均可
byte[] keyBytes = Decoders.BASE64.decode(base64Key);
this.key = Keys.hmacShaKeyFor(keyBytes);
}
/**
* 初始化 JwtParser
*/
@Override
public void afterPropertiesSet() {
// 0.13.0 标准写法:
// 1. 使用 parser() 而不是 parserBuilder()
// 2. 使用 verifyWith() 设置验签秘钥
jwtParser = Jwts.parser()
.requireIssuer(issuer)
.verifyWith(key)
// 允许的时钟偏差 (防止服务器时间不一致导致验证失败),默认单位秒
.clockSkewSeconds(10)
.build();
}
/**
* 生成 Token
*/
public String generateToken(String username) {
Instant now = Instant.now();
Instant expireTime = now.plus(1, ChronoUnit.HOURS);
return Jwts.builder()
.header().add("type", "JWT").and() // 推荐添加 header
.subject(username)
.issuer(issuer)
.issuedAt(Date.from(now))
.expiration(Date.from(expireTime))
// 0.13.0 标准:使用 Jwts.SIG 中的常量指定算法
.signWith(key, Jwts.SIG.HS512)
.compact();
}
/**
* 解析 Token
*/
public Jws<Claims> parseToken(String token) {
try {
// 0.13.0 标准:解析已签名的 JWS 使用 parseSignedClaims
return jwtParser.parseSignedClaims(token);
} catch (MalformedJwtException | UnsupportedJwtException | IllegalArgumentException e) {
// 注意SignatureException 现在属于 io.jsonwebtoken.security 包
throw new BadCredentialsException("Token 不可用或签名无效", e);
} catch (ExpiredJwtException e) {
throw new CredentialsExpiredException("Token 已失效", e);
}
}
}

View File

@@ -0,0 +1,67 @@
package com.hanserwei.jwt.utils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hanserwei.common.utils.Response;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import java.io.IOException;
import java.io.PrintWriter;
public class ResultUtil {
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* 成功响应
*
* @param response HttpServletResponse对象
* @param result 响应数据
* @throws IOException IO异常
*/
public static void ok(HttpServletResponse response, Response<?> result) throws IOException {
writeResponse(response, HttpStatus.OK.value(), result);
}
/**
* 失败响应
*
* @param response HttpServletResponse对象
* @param result 响应数据
* @throws IOException IO异常
*/
public static void fail(HttpServletResponse response, Response<?> result) throws IOException {
writeResponse(response, HttpStatus.OK.value(), result);
}
/**
* 失败响应
*
* @param response HttpServletResponse对象
* @param status HTTP状态码
* @param result 响应数据
* @throws IOException IO异常
*/
public static void fail(HttpServletResponse response, int status, Response<?> result) throws IOException {
writeResponse(response, status, result);
}
/**
* 写入响应数据
*
* @param response HttpServletResponse对象
* @param status HTTP状态码
* @param result 响应数据
* @throws IOException IO异常
*/
private static void writeResponse(HttpServletResponse response, int status, Response<?> result) throws IOException {
response.setCharacterEncoding("UTF-8");
response.setStatus(status);
response.setContentType("application/json");
try (PrintWriter writer = response.getWriter()) {
writer.write(objectMapper.writeValueAsString(result));
writer.flush();
}
}
}

View File

@@ -17,9 +17,9 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
public class TestController { public class TestController {
@PostMapping("/test") @PostMapping("/admin/test")
@ApiOperationLog(description = "测试接口") @ApiOperationLog(description = "测试接口")
public ResponseEntity<String> test(@RequestBody @Validated User user, BindingResult bindingResult) { public ResponseEntity<String>test(@RequestBody @Validated User user, BindingResult bindingResult) {
// 是否存在校验错误 // 是否存在校验错误
if (bindingResult.hasErrors()) { if (bindingResult.hasErrors()) {
// 获取校验不通过字段的提示信息 // 获取校验不通过字段的提示信息

View File

@@ -3,3 +3,8 @@ spring:
name: han-blog name: han-blog
profiles: profiles:
active: dev active: dev
jwt:
# 签发人
issuer: Hanserwei
# 秘钥
secret: P81m2EjMkZj74ht+OuBCxsf25if8PzghbEEWASyf4zcYnxwwn3VbCiIohxmowYg/8I4mj6eJdaqLbJEhggUq3Q==