feat(log): 新增日志注解功能并集成到认证流程

- 新增 @Log 注解及其配置类 LogConfig
- 在 AuthController 的 refreshToken 方法上添加 @Log 注解
- 创建 zkh-log 模块并引入 spring-boot-starter-aop 依赖
- 在 zkh-web 模块中引入 zkh-log 依赖
- 调整 GlobalExceptionHandler 和 RedisConfig 类中的代码缩进格式
- 更新 pom.xml 文件,加入 zkh-log 模块并调整依赖版本声明位置
- 修改 SpringDocConfig、WebSecurityConfig 等配置类的 bean 名称以避免冲突
This commit is contained in:
zkh
2025-12-01 16:31:45 +08:00
parent a7e1c26853
commit fe2240e266
11 changed files with 317 additions and 271 deletions

View File

@ -38,6 +38,7 @@
<module>zkh-common</module> <module>zkh-common</module>
<module>zkh-web</module> <module>zkh-web</module>
<module>zkh-data</module> <module>zkh-data</module>
<module>zkh-log</module>
</modules> </modules>
<properties> <properties>
@ -71,6 +72,11 @@
<artifactId>zkh-data</artifactId> <artifactId>zkh-data</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>vip.jcfd</groupId>
<artifactId>zkh-log</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.springdoc</groupId> <groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-common</artifactId> <artifactId>springdoc-openapi-common</artifactId>

View File

@ -13,12 +13,6 @@
<name>ZKH Data</name> <name>ZKH Data</name>
<description>Data layer components for ZKH framework</description> <description>Data layer components for ZKH framework</description>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>jakarta.persistence</groupId> <groupId>jakarta.persistence</groupId>

23
zkh-log/pom.xml Normal file
View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>vip.jcfd</groupId>
<artifactId>zkh-framework</artifactId>
<version>1.4</version>
</parent>
<artifactId>zkh-log</artifactId>
<name>ZKH log</name>
<description>Logging utilities for ZKH framework</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,10 @@
package vip.jcfd.log.annotation;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
String value();
}

View File

@ -0,0 +1,7 @@
package vip.jcfd.log.annotation.config;
import org.springframework.context.annotation.Configuration;
@Configuration("_logConfiguration")
public class LogConfig {
}

View File

@ -19,6 +19,10 @@
<groupId>vip.jcfd</groupId> <groupId>vip.jcfd</groupId>
<artifactId>zkh-common</artifactId> <artifactId>zkh-common</artifactId>
</dependency> </dependency>
<dependency>
<groupId>vip.jcfd</groupId>
<artifactId>zkh-log</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>

View File

@ -13,35 +13,35 @@ import vip.jcfd.common.core.R;
import java.util.List; import java.util.List;
@RestControllerAdvice @RestControllerAdvice("_globalExceptionHandler")
public class GlobalExceptionHandler { public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(value = Exception.class) @ExceptionHandler(value = Exception.class)
public R<String> handleException(Exception e) { public R<String> handleException(Exception e) {
log.error("服务异常", e); log.error("服务异常", e);
return R.serverError("服务器繁忙,请稍候重试"); return R.serverError("服务器繁忙,请稍候重试");
} }
@ExceptionHandler(value = BizException.class) @ExceptionHandler(value = BizException.class)
public R<String> handleBizException(BizException e) { public R<String> handleBizException(BizException e) {
log.error("业务异常", e); log.error("业务异常", e);
return R.error(e.getMessage()); return R.error(e.getMessage());
} }
@ExceptionHandler(value = NoResourceFoundException.class) @ExceptionHandler(value = NoResourceFoundException.class)
public R<String> handleNotFoundException(NoResourceFoundException e) { public R<String> handleNotFoundException(NoResourceFoundException e) {
log.error("404异常", e); log.error("404异常", e);
return new R<>(404, "您访问的地址不存在", false, null); return new R<>(404, "您访问的地址不存在", false, null);
} }
@ExceptionHandler(value = BindException.class) @ExceptionHandler(value = BindException.class)
public R<String> handleBindException(BindException e) { public R<String> handleBindException(BindException e) {
log.error("接口入参校验失败", e); log.error("接口入参校验失败", e);
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors(); List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
return R.error(String.join("\n", fieldErrors.stream().map(FieldError::getDefaultMessage).toList())); return R.error(String.join("\n", fieldErrors.stream().map(FieldError::getDefaultMessage).toList()));
} }
} }

View File

@ -10,26 +10,26 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
import vip.jcfd.web.config.props.SecurityProps; import vip.jcfd.web.config.props.SecurityProps;
import vip.jcfd.web.redis.TokenRedisStorage; import vip.jcfd.web.redis.TokenRedisStorage;
@Configuration @Configuration("_redisConfiguration")
public class RedisConfig { public class RedisConfig {
private final SecurityProps securityProps; private final SecurityProps securityProps;
public RedisConfig(SecurityProps securityProps) { public RedisConfig(SecurityProps securityProps) {
this.securityProps = securityProps; this.securityProps = securityProps;
} }
@Bean @Bean
public TokenRedisStorage tokenRedisTemplate(RedisConnectionFactory factory, StringRedisTemplate stringRedisTemplate, ObjectMapper objectMapper) { public TokenRedisStorage tokenRedisTemplate(RedisConnectionFactory factory, StringRedisTemplate stringRedisTemplate, ObjectMapper objectMapper) {
TokenRedisStorage tokenRedisStorage = new TokenRedisStorage( TokenRedisStorage tokenRedisStorage = new TokenRedisStorage(
securityProps.getAccessTokenDuration(), securityProps.getAccessTokenDuration(),
securityProps.getRefreshTokenDuration(), securityProps.getRefreshTokenDuration(),
stringRedisTemplate, stringRedisTemplate,
objectMapper objectMapper
); );
tokenRedisStorage.setConnectionFactory(factory); tokenRedisStorage.setConnectionFactory(factory);
tokenRedisStorage.setValueSerializer(new JdkSerializationRedisSerializer()); tokenRedisStorage.setValueSerializer(new JdkSerializationRedisSerializer());
tokenRedisStorage.setKeySerializer(new StringRedisSerializer()); tokenRedisStorage.setKeySerializer(new StringRedisSerializer());
return tokenRedisStorage; return tokenRedisStorage;
} }
} }

View File

@ -10,63 +10,63 @@ import org.springdoc.core.customizers.OpenApiCustomizer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@Configuration("_springDocConfig") @Configuration("_springDocConfiguration")
public class SpringDocConfig { public class SpringDocConfig {
@Bean @Bean
public OpenApiCustomizer openApiCustomizer() { public OpenApiCustomizer openApiCustomizer() {
return (openAPI) -> { return (openAPI) -> {
openAPI.path("/login", new PathItem() openAPI.path("/login", new PathItem()
.post(new Operation() .post(new Operation()
.summary("登录接口") .summary("登录接口")
.description("用于用户登录,返回token") .description("用于用户登录,返回token")
.addTagsItem("认证管理") .addTagsItem("认证管理")
.requestBody(new RequestBody() .requestBody(new RequestBody()
.description("帐号密码") .description("帐号密码")
.required(true) .required(true)
.content(new Content().addMediaType("application/json", new MediaType().schema(new Schema<>() .content(new Content().addMediaType("application/json", new MediaType().schema(new Schema<>()
.addProperty("username", new StringSchema().example("admin")) .addProperty("username", new StringSchema().example("admin"))
.addProperty("password", new StringSchema().example("123456")))))) .addProperty("password", new StringSchema().example("123456"))))))
.responses(new ApiResponses() .responses(new ApiResponses()
.addApiResponse("成功", new ApiResponse() .addApiResponse("成功", new ApiResponse()
.content(new Content().addMediaType("application/json", new MediaType().schema(new Schema<>() .content(new Content().addMediaType("application/json", new MediaType().schema(new Schema<>()
.addProperty("data", new JsonSchema() .addProperty("data", new JsonSchema()
.addProperty("accessToken", new StringSchema().example("550e8400-e29b-41d4-a716-446655440000")) .addProperty("accessToken", new StringSchema().example("550e8400-e29b-41d4-a716-446655440000"))
.addProperty("refreshToken", new StringSchema().example("550e8400-e29b-41d4-a716-446655440001")) .addProperty("refreshToken", new StringSchema().example("550e8400-e29b-41d4-a716-446655440001"))
.addProperty("tokenType", new StringSchema().example("Bearer")) .addProperty("tokenType", new StringSchema().example("Bearer"))
.addProperty("expiresIn", new NumberSchema().example(1800)) .addProperty("expiresIn", new NumberSchema().example(1800))
.addProperty("username", new StringSchema().example("admin")) .addProperty("username", new StringSchema().example("admin"))
) )
.addProperty("success", new BooleanSchema().example(true)) .addProperty("success", new BooleanSchema().example(true))
.addProperty("code", new IntegerSchema().example(200)) .addProperty("code", new IntegerSchema().example(200))
.addProperty("message", new StringSchema().example("登录成功")) .addProperty("message", new StringSchema().example("登录成功"))
)))) ))))
.addApiResponse("失败", new ApiResponse() .addApiResponse("失败", new ApiResponse()
.content(new Content().addMediaType("application/json", new MediaType().schema(new Schema<>() .content(new Content().addMediaType("application/json", new MediaType().schema(new Schema<>()
.addProperty("data", new StringSchema().example(null)) .addProperty("data", new StringSchema().example(null))
.addProperty("success", new BooleanSchema().example(false)) .addProperty("success", new BooleanSchema().example(false))
.addProperty("code", new IntegerSchema().example(401)) .addProperty("code", new IntegerSchema().example(401))
.addProperty("message", new StringSchema().example("用户名或密码错误")) .addProperty("message", new StringSchema().example("用户名或密码错误"))
)))) ))))
))); )));
openAPI.path("/logout", new PathItem() openAPI.path("/logout", new PathItem()
.post(new Operation() .post(new Operation()
.summary("登出接口") .summary("登出接口")
.description("用于用户登出") .description("用于用户登出")
.addTagsItem("认证管理") .addTagsItem("认证管理")
.responses(new ApiResponses() .responses(new ApiResponses()
.addApiResponse("成功", new ApiResponse() .addApiResponse("成功", new ApiResponse()
.content(new Content().addMediaType("application/json", new MediaType().schema(new Schema<>() .content(new Content().addMediaType("application/json", new MediaType().schema(new Schema<>()
.addProperty("data", new StringSchema().example(null)) .addProperty("data", new StringSchema().example(null))
.addProperty("success", new BooleanSchema().example(true)) .addProperty("success", new BooleanSchema().example(true))
.addProperty("code", new IntegerSchema().example(200)) .addProperty("code", new IntegerSchema().example(200))
.addProperty("message", new StringSchema().example("登出成功")) .addProperty("message", new StringSchema().example("登出成功"))
) )
) )
) )
) )
) )
)); ));
}; };
} }
} }

View File

@ -52,7 +52,7 @@ import java.io.IOException;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@Configuration @Configuration("_webSecurityConfiguration")
@EnableWebSecurity @EnableWebSecurity
@ConfigurationPropertiesScan(basePackageClasses = {SecurityProps.class}) @ConfigurationPropertiesScan(basePackageClasses = {SecurityProps.class})
@EnableJpaAuditing @EnableJpaAuditing
@ -60,191 +60,191 @@ import java.util.UUID;
@EnableScheduling @EnableScheduling
public class WebSecurityConfig { public class WebSecurityConfig {
private static final Logger log = LoggerFactory.getLogger(WebSecurityConfig.class); private static final Logger log = LoggerFactory.getLogger(WebSecurityConfig.class);
private final SecurityProps securityProps; private final SecurityProps securityProps;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
private final TokenRedisStorage tokenRedisStorage; private final TokenRedisStorage tokenRedisStorage;
public WebSecurityConfig(SecurityProps securityProps, public WebSecurityConfig(SecurityProps securityProps,
ObjectMapper objectMapper, ObjectMapper objectMapper,
TokenRedisStorage tokenRedisStorage, TokenRedisStorage tokenRedisStorage,
AuthenticationManagerBuilder builder, AuthenticationManagerBuilder builder,
UserDetailsService userDetailsService) { UserDetailsService userDetailsService) {
this.securityProps = securityProps; this.securityProps = securityProps;
this.objectMapper = objectMapper; this.objectMapper = objectMapper;
this.tokenRedisStorage = tokenRedisStorage; this.tokenRedisStorage = tokenRedisStorage;
builder.authenticationProvider(new RefreshTokenAuthProvider(userDetailsService)); builder.authenticationProvider(new RefreshTokenAuthProvider(userDetailsService));
DaoAuthenticationProvider authenticationProvider = new CustomDaoAuthenticationProvider(userDetailsService); DaoAuthenticationProvider authenticationProvider = new CustomDaoAuthenticationProvider(userDetailsService);
authenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder()); authenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
builder.authenticationProvider(authenticationProvider); builder.authenticationProvider(authenticationProvider);
} }
@Scheduled(cron = "0 */30 * * * *") @Scheduled(cron = "0 */30 * * * *")
@Async @Async
public void scheduleClearExpiredTokens() { public void scheduleClearExpiredTokens() {
tokenRedisStorage.clearExpiredTokens(); tokenRedisStorage.clearExpiredTokens();
} }
@Bean @Bean
public AuditorAware<String> auditorAware() { public AuditorAware<String> auditorAware() {
return () -> Optional.ofNullable(SecurityContextHolder.getContext()) return () -> Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication) .map(SecurityContext::getAuthentication)
.map(Authentication::getName) .map(Authentication::getName)
.or(() -> Optional.of("system")); .or(() -> Optional.of("system"));
} }
@Bean @Bean
public PasswordEncoder passwordEncoder() { public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); return new BCryptPasswordEncoder();
} }
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public TokenFilter tokenFilter() { public TokenFilter tokenFilter() {
return new TokenFilter(tokenRedisStorage); return new TokenFilter(tokenRedisStorage);
} }
@Bean @Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager(); return configuration.getAuthenticationManager();
} }
@Bean @Bean
public SecurityFilterChain security(HttpSecurity http, TokenFilter tokenFilter, AuthenticationManager authenticationManager) throws Exception { public SecurityFilterChain security(HttpSecurity http, TokenFilter tokenFilter, AuthenticationManager authenticationManager) throws Exception {
http.authorizeHttpRequests(config -> { http.authorizeHttpRequests(config -> {
config.requestMatchers(securityProps.getIgnoreUrls()).permitAll(); config.requestMatchers(securityProps.getIgnoreUrls()).permitAll();
config.anyRequest().authenticated(); config.anyRequest().authenticated();
}); });
CustomAuthenticationEntryPoint authenticationEntryPoint = new CustomAuthenticationEntryPoint(objectMapper, tokenRedisStorage); CustomAuthenticationEntryPoint authenticationEntryPoint = new CustomAuthenticationEntryPoint(objectMapper, tokenRedisStorage);
http.formLogin(config -> { http.formLogin(config -> {
config.loginProcessingUrl("/login"); config.loginProcessingUrl("/login");
}); });
http.csrf(AbstractHttpConfigurer::disable); http.csrf(AbstractHttpConfigurer::disable);
http.logout(config -> { http.logout(config -> {
config.addLogoutHandler(new CustomLogoutSuccessHandler(objectMapper, tokenRedisStorage)); config.addLogoutHandler(new CustomLogoutSuccessHandler(objectMapper, tokenRedisStorage));
}); });
http.rememberMe(AbstractHttpConfigurer::disable); http.rememberMe(AbstractHttpConfigurer::disable);
http.sessionManagement(AbstractHttpConfigurer::disable); http.sessionManagement(AbstractHttpConfigurer::disable);
http.exceptionHandling(config -> { http.exceptionHandling(config -> {
config.authenticationEntryPoint(authenticationEntryPoint); config.authenticationEntryPoint(authenticationEntryPoint);
config.accessDeniedHandler(new CustomAccessDeniedHandler(objectMapper)); config.accessDeniedHandler(new CustomAccessDeniedHandler(objectMapper));
}); });
http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class); http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
JsonUsernamePasswordAuthenticationFilter filter = new JsonUsernamePasswordAuthenticationFilter(objectMapper, authenticationManager); JsonUsernamePasswordAuthenticationFilter filter = new JsonUsernamePasswordAuthenticationFilter(objectMapper, authenticationManager);
filter.setAuthenticationSuccessHandler(authenticationEntryPoint); filter.setAuthenticationSuccessHandler(authenticationEntryPoint);
filter.setAuthenticationFailureHandler(authenticationEntryPoint); filter.setAuthenticationFailureHandler(authenticationEntryPoint);
http.addFilterAt(filter, UsernamePasswordAuthenticationFilter.class); http.addFilterAt(filter, UsernamePasswordAuthenticationFilter.class);
return http.build(); return http.build();
} }
private record CustomAuthenticationEntryPoint( private record CustomAuthenticationEntryPoint(
ObjectMapper objectMapper, ObjectMapper objectMapper,
TokenRedisStorage tokenRedisStorage) implements AuthenticationEntryPoint, AuthenticationFailureHandler, AuthenticationSuccessHandler { TokenRedisStorage tokenRedisStorage) implements AuthenticationEntryPoint, AuthenticationFailureHandler, AuthenticationSuccessHandler {
@Override @Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
log.warn("认证失败", authException); log.warn("认证失败", authException);
R<Object> data = new R<>(HttpServletResponse.SC_UNAUTHORIZED, "未登录", false, null); R<Object> data = new R<>(HttpServletResponse.SC_UNAUTHORIZED, "未登录", false, null);
response.setContentType("application/json;charset=UTF-8"); response.setContentType("application/json;charset=UTF-8");
objectMapper.writeValue(response.getWriter(), data); objectMapper.writeValue(response.getWriter(), data);
} }
@Override @Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
log.warn("登录失败", exception); log.warn("登录失败", exception);
R<Object> data = new R<>(HttpServletResponse.SC_UNAUTHORIZED, "用户名或密码错误", false, null); R<Object> data = new R<>(HttpServletResponse.SC_UNAUTHORIZED, "用户名或密码错误", false, null);
response.setContentType("application/json;charset=UTF-8"); response.setContentType("application/json;charset=UTF-8");
objectMapper.writeValue(response.getWriter(), data); objectMapper.writeValue(response.getWriter(), data);
} }
@Override @Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
log.info("用户「{}」登录成功", authentication.getName()); log.info("用户「{}」登录成功", authentication.getName());
// 生成双重Token // 生成双重Token
String accessToken = UUID.randomUUID().toString(); String accessToken = UUID.randomUUID().toString();
String refreshToken = UUID.randomUUID().toString(); String refreshToken = UUID.randomUUID().toString();
// 存储Access Token // 存储Access Token
tokenRedisStorage.putAccessToken(accessToken, authentication); tokenRedisStorage.putAccessToken(accessToken, authentication);
// 存储Refresh Token // 存储Refresh Token
String deviceId = extractDeviceId(request); String deviceId = extractDeviceId(request);
tokenRedisStorage.putRefreshToken(refreshToken, authentication.getName(), deviceId); tokenRedisStorage.putRefreshToken(refreshToken, authentication.getName(), deviceId);
// 构造登录响应 // 构造登录响应
LoginResponse loginResponse = new LoginResponse( LoginResponse loginResponse = new LoginResponse(
accessToken, accessToken,
refreshToken, refreshToken,
"Bearer", "Bearer",
1800, // 30分钟秒数 1800, // 30分钟秒数
authentication.getName() authentication.getName()
); );
response.setContentType("application/json;charset=UTF-8"); response.setContentType("application/json;charset=UTF-8");
R<LoginResponse> data = new R<>(HttpServletResponse.SC_OK, "登录成功", true, loginResponse); R<LoginResponse> data = new R<>(HttpServletResponse.SC_OK, "登录成功", true, loginResponse);
objectMapper.writeValue(response.getWriter(), data); objectMapper.writeValue(response.getWriter(), data);
} }
private String extractDeviceId(HttpServletRequest request) { private String extractDeviceId(HttpServletRequest request) {
// 尝试从User-Agent提取设备信息 // 尝试从User-Agent提取设备信息
String userAgent = request.getHeader("User-Agent"); String userAgent = request.getHeader("User-Agent");
if (userAgent != null) { if (userAgent != null) {
// 简单的设备识别逻辑,生产环境可以使用更复杂的识别算法 // 简单的设备识别逻辑,生产环境可以使用更复杂的识别算法
if (userAgent.contains("Mobile") || userAgent.contains("Android") || userAgent.contains("iPhone")) { if (userAgent.contains("Mobile") || userAgent.contains("Android") || userAgent.contains("iPhone")) {
return "mobile-" + request.getRemoteAddr(); return "mobile-" + request.getRemoteAddr();
} else if (userAgent.contains("Tablet") || userAgent.contains("iPad")) { } else if (userAgent.contains("Tablet") || userAgent.contains("iPad")) {
return "tablet-" + request.getRemoteAddr(); return "tablet-" + request.getRemoteAddr();
} else { } else {
return "desktop-" + request.getRemoteAddr(); return "desktop-" + request.getRemoteAddr();
} }
} }
return "unknown-" + request.getRemoteAddr(); return "unknown-" + request.getRemoteAddr();
} }
} }
private record CustomAccessDeniedHandler(ObjectMapper objectMapper) implements AccessDeniedHandler { private record CustomAccessDeniedHandler(ObjectMapper objectMapper) implements AccessDeniedHandler {
@Override @Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
log.warn("访问被拒绝", accessDeniedException); log.warn("访问被拒绝", accessDeniedException);
if (authentication.isAuthenticated()) { if (authentication.isAuthenticated()) {
log.warn("用户「{}」访问「{}」被拒绝,因为:{}", authentication.getPrincipal(), request.getRequestURI(), accessDeniedException.getMessage()); log.warn("用户「{}」访问「{}」被拒绝,因为:{}", authentication.getPrincipal(), request.getRequestURI(), accessDeniedException.getMessage());
} else { } else {
log.warn("匿名用户访问「{}」被拒绝,因为:{}", request.getRequestURI(), accessDeniedException.getMessage()); log.warn("匿名用户访问「{}」被拒绝,因为:{}", request.getRequestURI(), accessDeniedException.getMessage());
} }
R<Object> data = new R<>(HttpServletResponse.SC_FORBIDDEN, "无权限", false, null); R<Object> data = new R<>(HttpServletResponse.SC_FORBIDDEN, "无权限", false, null);
response.setContentType("application/json;charset=UTF-8"); response.setContentType("application/json;charset=UTF-8");
objectMapper.writeValue(response.getWriter(), data); objectMapper.writeValue(response.getWriter(), data);
} }
} }
private record CustomLogoutSuccessHandler(ObjectMapper objectMapper, private record CustomLogoutSuccessHandler(ObjectMapper objectMapper,
TokenRedisStorage tokenRedisStorage) implements LogoutHandler { TokenRedisStorage tokenRedisStorage) implements LogoutHandler {
@Override @Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
String header = request.getHeader(HttpHeaders.AUTHORIZATION); String header = request.getHeader(HttpHeaders.AUTHORIZATION);
if (header != null && header.startsWith("Bearer ")) { if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7); String token = header.substring(7);
authentication = tokenRedisStorage.get(token); authentication = tokenRedisStorage.get(token);
tokenRedisStorage.remove(token); tokenRedisStorage.remove(token);
} }
if (authentication != null) { if (authentication != null) {
log.info("用户「{}」退出成功", authentication.getName()); log.info("用户「{}」退出成功", authentication.getName());
String all = request.getParameter("all"); String all = request.getParameter("all");
if ("true".equals(all)) { if ("true".equals(all)) {
tokenRedisStorage.removeByUserName(authentication.getName()); tokenRedisStorage.removeByUserName(authentication.getName());
} }
} }
R<Object> data = new R<>(HttpServletResponse.SC_OK, "退出成功", true, null); R<Object> data = new R<>(HttpServletResponse.SC_OK, "退出成功", true, null);
response.setContentType("application/json;charset=UTF-8"); response.setContentType("application/json;charset=UTF-8");
try { try {
objectMapper.writeValue(response.getWriter(), data); objectMapper.writeValue(response.getWriter(), data);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
} }
} }

View File

@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.*;
import vip.jcfd.common.core.R; import vip.jcfd.common.core.R;
import vip.jcfd.common.dto.TokenRefreshRequest; import vip.jcfd.common.dto.TokenRefreshRequest;
import vip.jcfd.common.dto.TokenRefreshResponse; import vip.jcfd.common.dto.TokenRefreshResponse;
import vip.jcfd.log.annotation.Log;
import vip.jcfd.web.auth.RefreshTokenAuthenticationToken; import vip.jcfd.web.auth.RefreshTokenAuthenticationToken;
import vip.jcfd.web.redis.TokenRedisStorage; import vip.jcfd.web.redis.TokenRedisStorage;
@ -37,6 +38,7 @@ public class AuthController {
@PostMapping("/refresh-token") @PostMapping("/refresh-token")
@Operation(summary = "刷新Token", description = "使用Refresh Token获取新的Access Token和Refresh Token") @Operation(summary = "刷新Token", description = "使用Refresh Token获取新的Access Token和Refresh Token")
@Log("刷新了token")
public R<TokenRefreshResponse> refreshToken( public R<TokenRefreshResponse> refreshToken(
@Valid @RequestBody TokenRefreshRequest request) { @Valid @RequestBody TokenRefreshRequest request) {