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:
6
pom.xml
6
pom.xml
@ -38,6 +38,7 @@
|
||||
<module>zkh-common</module>
|
||||
<module>zkh-web</module>
|
||||
<module>zkh-data</module>
|
||||
<module>zkh-log</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
@ -71,6 +72,11 @@
|
||||
<artifactId>zkh-data</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>vip.jcfd</groupId>
|
||||
<artifactId>zkh-log</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-common</artifactId>
|
||||
|
||||
@ -13,12 +13,6 @@
|
||||
<name>ZKH Data</name>
|
||||
<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>
|
||||
<dependency>
|
||||
<groupId>jakarta.persistence</groupId>
|
||||
|
||||
23
zkh-log/pom.xml
Normal file
23
zkh-log/pom.xml
Normal 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>
|
||||
10
zkh-log/src/main/java/vip/jcfd/log/annotation/Log.java
Normal file
10
zkh-log/src/main/java/vip/jcfd/log/annotation/Log.java
Normal 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();
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package vip.jcfd.log.annotation.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration("_logConfiguration")
|
||||
public class LogConfig {
|
||||
}
|
||||
@ -19,6 +19,10 @@
|
||||
<groupId>vip.jcfd</groupId>
|
||||
<artifactId>zkh-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>vip.jcfd</groupId>
|
||||
<artifactId>zkh-log</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
|
||||
@ -13,35 +13,35 @@ import vip.jcfd.common.core.R;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@RestControllerAdvice
|
||||
@RestControllerAdvice("_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)
|
||||
public R<String> handleException(Exception e) {
|
||||
log.error("服务异常", e);
|
||||
return R.serverError("服务器繁忙,请稍候重试");
|
||||
}
|
||||
@ExceptionHandler(value = Exception.class)
|
||||
public R<String> handleException(Exception e) {
|
||||
log.error("服务异常", e);
|
||||
return R.serverError("服务器繁忙,请稍候重试");
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = BizException.class)
|
||||
public R<String> handleBizException(BizException e) {
|
||||
log.error("业务异常", e);
|
||||
return R.error(e.getMessage());
|
||||
}
|
||||
@ExceptionHandler(value = BizException.class)
|
||||
public R<String> handleBizException(BizException e) {
|
||||
log.error("业务异常", e);
|
||||
return R.error(e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = NoResourceFoundException.class)
|
||||
public R<String> handleNotFoundException(NoResourceFoundException e) {
|
||||
log.error("404异常", e);
|
||||
return new R<>(404, "您访问的地址不存在", false, null);
|
||||
}
|
||||
@ExceptionHandler(value = NoResourceFoundException.class)
|
||||
public R<String> handleNotFoundException(NoResourceFoundException e) {
|
||||
log.error("404异常", e);
|
||||
return new R<>(404, "您访问的地址不存在", false, null);
|
||||
}
|
||||
|
||||
|
||||
@ExceptionHandler(value = BindException.class)
|
||||
public R<String> handleBindException(BindException e) {
|
||||
log.error("接口入参校验失败", e);
|
||||
@ExceptionHandler(value = BindException.class)
|
||||
public R<String> handleBindException(BindException e) {
|
||||
log.error("接口入参校验失败", e);
|
||||
|
||||
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
|
||||
return R.error(String.join("。\n", fieldErrors.stream().map(FieldError::getDefaultMessage).toList()));
|
||||
}
|
||||
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
|
||||
return R.error(String.join("。\n", fieldErrors.stream().map(FieldError::getDefaultMessage).toList()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,26 +10,26 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import vip.jcfd.web.config.props.SecurityProps;
|
||||
import vip.jcfd.web.redis.TokenRedisStorage;
|
||||
|
||||
@Configuration
|
||||
@Configuration("_redisConfiguration")
|
||||
public class RedisConfig {
|
||||
|
||||
private final SecurityProps securityProps;
|
||||
private final SecurityProps securityProps;
|
||||
|
||||
public RedisConfig(SecurityProps securityProps) {
|
||||
this.securityProps = securityProps;
|
||||
}
|
||||
public RedisConfig(SecurityProps securityProps) {
|
||||
this.securityProps = securityProps;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TokenRedisStorage tokenRedisTemplate(RedisConnectionFactory factory, StringRedisTemplate stringRedisTemplate, ObjectMapper objectMapper) {
|
||||
TokenRedisStorage tokenRedisStorage = new TokenRedisStorage(
|
||||
securityProps.getAccessTokenDuration(),
|
||||
securityProps.getRefreshTokenDuration(),
|
||||
stringRedisTemplate,
|
||||
objectMapper
|
||||
);
|
||||
tokenRedisStorage.setConnectionFactory(factory);
|
||||
tokenRedisStorage.setValueSerializer(new JdkSerializationRedisSerializer());
|
||||
tokenRedisStorage.setKeySerializer(new StringRedisSerializer());
|
||||
return tokenRedisStorage;
|
||||
}
|
||||
@Bean
|
||||
public TokenRedisStorage tokenRedisTemplate(RedisConnectionFactory factory, StringRedisTemplate stringRedisTemplate, ObjectMapper objectMapper) {
|
||||
TokenRedisStorage tokenRedisStorage = new TokenRedisStorage(
|
||||
securityProps.getAccessTokenDuration(),
|
||||
securityProps.getRefreshTokenDuration(),
|
||||
stringRedisTemplate,
|
||||
objectMapper
|
||||
);
|
||||
tokenRedisStorage.setConnectionFactory(factory);
|
||||
tokenRedisStorage.setValueSerializer(new JdkSerializationRedisSerializer());
|
||||
tokenRedisStorage.setKeySerializer(new StringRedisSerializer());
|
||||
return tokenRedisStorage;
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,63 +10,63 @@ import org.springdoc.core.customizers.OpenApiCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration("_springDocConfig")
|
||||
@Configuration("_springDocConfiguration")
|
||||
public class SpringDocConfig {
|
||||
|
||||
@Bean
|
||||
public OpenApiCustomizer openApiCustomizer() {
|
||||
return (openAPI) -> {
|
||||
openAPI.path("/login", new PathItem()
|
||||
.post(new Operation()
|
||||
.summary("登录接口")
|
||||
.description("用于用户登录,返回token")
|
||||
.addTagsItem("认证管理")
|
||||
.requestBody(new RequestBody()
|
||||
.description("帐号密码")
|
||||
.required(true)
|
||||
.content(new Content().addMediaType("application/json", new MediaType().schema(new Schema<>()
|
||||
.addProperty("username", new StringSchema().example("admin"))
|
||||
.addProperty("password", new StringSchema().example("123456"))))))
|
||||
.responses(new ApiResponses()
|
||||
.addApiResponse("成功", new ApiResponse()
|
||||
.content(new Content().addMediaType("application/json", new MediaType().schema(new Schema<>()
|
||||
.addProperty("data", new JsonSchema()
|
||||
.addProperty("accessToken", new StringSchema().example("550e8400-e29b-41d4-a716-446655440000"))
|
||||
.addProperty("refreshToken", new StringSchema().example("550e8400-e29b-41d4-a716-446655440001"))
|
||||
.addProperty("tokenType", new StringSchema().example("Bearer"))
|
||||
.addProperty("expiresIn", new NumberSchema().example(1800))
|
||||
.addProperty("username", new StringSchema().example("admin"))
|
||||
)
|
||||
.addProperty("success", new BooleanSchema().example(true))
|
||||
.addProperty("code", new IntegerSchema().example(200))
|
||||
.addProperty("message", new StringSchema().example("登录成功"))
|
||||
))))
|
||||
.addApiResponse("失败", new ApiResponse()
|
||||
.content(new Content().addMediaType("application/json", new MediaType().schema(new Schema<>()
|
||||
.addProperty("data", new StringSchema().example(null))
|
||||
.addProperty("success", new BooleanSchema().example(false))
|
||||
.addProperty("code", new IntegerSchema().example(401))
|
||||
.addProperty("message", new StringSchema().example("用户名或密码错误"))
|
||||
))))
|
||||
)));
|
||||
openAPI.path("/logout", new PathItem()
|
||||
.post(new Operation()
|
||||
.summary("登出接口")
|
||||
.description("用于用户登出")
|
||||
.addTagsItem("认证管理")
|
||||
.responses(new ApiResponses()
|
||||
.addApiResponse("成功", new ApiResponse()
|
||||
.content(new Content().addMediaType("application/json", new MediaType().schema(new Schema<>()
|
||||
.addProperty("data", new StringSchema().example(null))
|
||||
.addProperty("success", new BooleanSchema().example(true))
|
||||
.addProperty("code", new IntegerSchema().example(200))
|
||||
.addProperty("message", new StringSchema().example("登出成功"))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
));
|
||||
};
|
||||
}
|
||||
@Bean
|
||||
public OpenApiCustomizer openApiCustomizer() {
|
||||
return (openAPI) -> {
|
||||
openAPI.path("/login", new PathItem()
|
||||
.post(new Operation()
|
||||
.summary("登录接口")
|
||||
.description("用于用户登录,返回token")
|
||||
.addTagsItem("认证管理")
|
||||
.requestBody(new RequestBody()
|
||||
.description("帐号密码")
|
||||
.required(true)
|
||||
.content(new Content().addMediaType("application/json", new MediaType().schema(new Schema<>()
|
||||
.addProperty("username", new StringSchema().example("admin"))
|
||||
.addProperty("password", new StringSchema().example("123456"))))))
|
||||
.responses(new ApiResponses()
|
||||
.addApiResponse("成功", new ApiResponse()
|
||||
.content(new Content().addMediaType("application/json", new MediaType().schema(new Schema<>()
|
||||
.addProperty("data", new JsonSchema()
|
||||
.addProperty("accessToken", new StringSchema().example("550e8400-e29b-41d4-a716-446655440000"))
|
||||
.addProperty("refreshToken", new StringSchema().example("550e8400-e29b-41d4-a716-446655440001"))
|
||||
.addProperty("tokenType", new StringSchema().example("Bearer"))
|
||||
.addProperty("expiresIn", new NumberSchema().example(1800))
|
||||
.addProperty("username", new StringSchema().example("admin"))
|
||||
)
|
||||
.addProperty("success", new BooleanSchema().example(true))
|
||||
.addProperty("code", new IntegerSchema().example(200))
|
||||
.addProperty("message", new StringSchema().example("登录成功"))
|
||||
))))
|
||||
.addApiResponse("失败", new ApiResponse()
|
||||
.content(new Content().addMediaType("application/json", new MediaType().schema(new Schema<>()
|
||||
.addProperty("data", new StringSchema().example(null))
|
||||
.addProperty("success", new BooleanSchema().example(false))
|
||||
.addProperty("code", new IntegerSchema().example(401))
|
||||
.addProperty("message", new StringSchema().example("用户名或密码错误"))
|
||||
))))
|
||||
)));
|
||||
openAPI.path("/logout", new PathItem()
|
||||
.post(new Operation()
|
||||
.summary("登出接口")
|
||||
.description("用于用户登出")
|
||||
.addTagsItem("认证管理")
|
||||
.responses(new ApiResponses()
|
||||
.addApiResponse("成功", new ApiResponse()
|
||||
.content(new Content().addMediaType("application/json", new MediaType().schema(new Schema<>()
|
||||
.addProperty("data", new StringSchema().example(null))
|
||||
.addProperty("success", new BooleanSchema().example(true))
|
||||
.addProperty("code", new IntegerSchema().example(200))
|
||||
.addProperty("message", new StringSchema().example("登出成功"))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Configuration
|
||||
@Configuration("_webSecurityConfiguration")
|
||||
@EnableWebSecurity
|
||||
@ConfigurationPropertiesScan(basePackageClasses = {SecurityProps.class})
|
||||
@EnableJpaAuditing
|
||||
@ -60,191 +60,191 @@ import java.util.UUID;
|
||||
@EnableScheduling
|
||||
public class WebSecurityConfig {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(WebSecurityConfig.class);
|
||||
private final SecurityProps securityProps;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final TokenRedisStorage tokenRedisStorage;
|
||||
private static final Logger log = LoggerFactory.getLogger(WebSecurityConfig.class);
|
||||
private final SecurityProps securityProps;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final TokenRedisStorage tokenRedisStorage;
|
||||
|
||||
public WebSecurityConfig(SecurityProps securityProps,
|
||||
ObjectMapper objectMapper,
|
||||
TokenRedisStorage tokenRedisStorage,
|
||||
AuthenticationManagerBuilder builder,
|
||||
UserDetailsService userDetailsService) {
|
||||
this.securityProps = securityProps;
|
||||
this.objectMapper = objectMapper;
|
||||
this.tokenRedisStorage = tokenRedisStorage;
|
||||
builder.authenticationProvider(new RefreshTokenAuthProvider(userDetailsService));
|
||||
DaoAuthenticationProvider authenticationProvider = new CustomDaoAuthenticationProvider(userDetailsService);
|
||||
authenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
|
||||
builder.authenticationProvider(authenticationProvider);
|
||||
}
|
||||
public WebSecurityConfig(SecurityProps securityProps,
|
||||
ObjectMapper objectMapper,
|
||||
TokenRedisStorage tokenRedisStorage,
|
||||
AuthenticationManagerBuilder builder,
|
||||
UserDetailsService userDetailsService) {
|
||||
this.securityProps = securityProps;
|
||||
this.objectMapper = objectMapper;
|
||||
this.tokenRedisStorage = tokenRedisStorage;
|
||||
builder.authenticationProvider(new RefreshTokenAuthProvider(userDetailsService));
|
||||
DaoAuthenticationProvider authenticationProvider = new CustomDaoAuthenticationProvider(userDetailsService);
|
||||
authenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
|
||||
builder.authenticationProvider(authenticationProvider);
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0 */30 * * * *")
|
||||
@Async
|
||||
public void scheduleClearExpiredTokens() {
|
||||
tokenRedisStorage.clearExpiredTokens();
|
||||
}
|
||||
@Scheduled(cron = "0 */30 * * * *")
|
||||
@Async
|
||||
public void scheduleClearExpiredTokens() {
|
||||
tokenRedisStorage.clearExpiredTokens();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuditorAware<String> auditorAware() {
|
||||
return () -> Optional.ofNullable(SecurityContextHolder.getContext())
|
||||
.map(SecurityContext::getAuthentication)
|
||||
.map(Authentication::getName)
|
||||
.or(() -> Optional.of("system"));
|
||||
}
|
||||
@Bean
|
||||
public AuditorAware<String> auditorAware() {
|
||||
return () -> Optional.ofNullable(SecurityContextHolder.getContext())
|
||||
.map(SecurityContext::getAuthentication)
|
||||
.map(Authentication::getName)
|
||||
.or(() -> Optional.of("system"));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TokenFilter tokenFilter() {
|
||||
return new TokenFilter(tokenRedisStorage);
|
||||
}
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TokenFilter tokenFilter() {
|
||||
return new TokenFilter(tokenRedisStorage);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
|
||||
return configuration.getAuthenticationManager();
|
||||
}
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
|
||||
return configuration.getAuthenticationManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain security(HttpSecurity http, TokenFilter tokenFilter, AuthenticationManager authenticationManager) throws Exception {
|
||||
http.authorizeHttpRequests(config -> {
|
||||
config.requestMatchers(securityProps.getIgnoreUrls()).permitAll();
|
||||
config.anyRequest().authenticated();
|
||||
});
|
||||
CustomAuthenticationEntryPoint authenticationEntryPoint = new CustomAuthenticationEntryPoint(objectMapper, tokenRedisStorage);
|
||||
http.formLogin(config -> {
|
||||
config.loginProcessingUrl("/login");
|
||||
});
|
||||
http.csrf(AbstractHttpConfigurer::disable);
|
||||
http.logout(config -> {
|
||||
config.addLogoutHandler(new CustomLogoutSuccessHandler(objectMapper, tokenRedisStorage));
|
||||
});
|
||||
http.rememberMe(AbstractHttpConfigurer::disable);
|
||||
http.sessionManagement(AbstractHttpConfigurer::disable);
|
||||
http.exceptionHandling(config -> {
|
||||
config.authenticationEntryPoint(authenticationEntryPoint);
|
||||
config.accessDeniedHandler(new CustomAccessDeniedHandler(objectMapper));
|
||||
});
|
||||
@Bean
|
||||
public SecurityFilterChain security(HttpSecurity http, TokenFilter tokenFilter, AuthenticationManager authenticationManager) throws Exception {
|
||||
http.authorizeHttpRequests(config -> {
|
||||
config.requestMatchers(securityProps.getIgnoreUrls()).permitAll();
|
||||
config.anyRequest().authenticated();
|
||||
});
|
||||
CustomAuthenticationEntryPoint authenticationEntryPoint = new CustomAuthenticationEntryPoint(objectMapper, tokenRedisStorage);
|
||||
http.formLogin(config -> {
|
||||
config.loginProcessingUrl("/login");
|
||||
});
|
||||
http.csrf(AbstractHttpConfigurer::disable);
|
||||
http.logout(config -> {
|
||||
config.addLogoutHandler(new CustomLogoutSuccessHandler(objectMapper, tokenRedisStorage));
|
||||
});
|
||||
http.rememberMe(AbstractHttpConfigurer::disable);
|
||||
http.sessionManagement(AbstractHttpConfigurer::disable);
|
||||
http.exceptionHandling(config -> {
|
||||
config.authenticationEntryPoint(authenticationEntryPoint);
|
||||
config.accessDeniedHandler(new CustomAccessDeniedHandler(objectMapper));
|
||||
});
|
||||
|
||||
http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
JsonUsernamePasswordAuthenticationFilter filter = new JsonUsernamePasswordAuthenticationFilter(objectMapper, authenticationManager);
|
||||
filter.setAuthenticationSuccessHandler(authenticationEntryPoint);
|
||||
filter.setAuthenticationFailureHandler(authenticationEntryPoint);
|
||||
http.addFilterAt(filter, UsernamePasswordAuthenticationFilter.class);
|
||||
return http.build();
|
||||
}
|
||||
http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
JsonUsernamePasswordAuthenticationFilter filter = new JsonUsernamePasswordAuthenticationFilter(objectMapper, authenticationManager);
|
||||
filter.setAuthenticationSuccessHandler(authenticationEntryPoint);
|
||||
filter.setAuthenticationFailureHandler(authenticationEntryPoint);
|
||||
http.addFilterAt(filter, UsernamePasswordAuthenticationFilter.class);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
private record CustomAuthenticationEntryPoint(
|
||||
ObjectMapper objectMapper,
|
||||
TokenRedisStorage tokenRedisStorage) implements AuthenticationEntryPoint, AuthenticationFailureHandler, AuthenticationSuccessHandler {
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
|
||||
log.warn("认证失败", authException);
|
||||
R<Object> data = new R<>(HttpServletResponse.SC_UNAUTHORIZED, "未登录", false, null);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
objectMapper.writeValue(response.getWriter(), data);
|
||||
}
|
||||
private record CustomAuthenticationEntryPoint(
|
||||
ObjectMapper objectMapper,
|
||||
TokenRedisStorage tokenRedisStorage) implements AuthenticationEntryPoint, AuthenticationFailureHandler, AuthenticationSuccessHandler {
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
|
||||
log.warn("认证失败", authException);
|
||||
R<Object> data = new R<>(HttpServletResponse.SC_UNAUTHORIZED, "未登录", false, null);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
objectMapper.writeValue(response.getWriter(), data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
|
||||
log.warn("登录失败", exception);
|
||||
R<Object> data = new R<>(HttpServletResponse.SC_UNAUTHORIZED, "用户名或密码错误", false, null);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
objectMapper.writeValue(response.getWriter(), data);
|
||||
}
|
||||
@Override
|
||||
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
|
||||
log.warn("登录失败", exception);
|
||||
R<Object> data = new R<>(HttpServletResponse.SC_UNAUTHORIZED, "用户名或密码错误", false, null);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
objectMapper.writeValue(response.getWriter(), data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
|
||||
log.info("用户「{}」登录成功", authentication.getName());
|
||||
@Override
|
||||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
|
||||
log.info("用户「{}」登录成功", authentication.getName());
|
||||
|
||||
// 生成双重Token
|
||||
String accessToken = UUID.randomUUID().toString();
|
||||
String refreshToken = UUID.randomUUID().toString();
|
||||
// 生成双重Token
|
||||
String accessToken = UUID.randomUUID().toString();
|
||||
String refreshToken = UUID.randomUUID().toString();
|
||||
|
||||
// 存储Access Token
|
||||
tokenRedisStorage.putAccessToken(accessToken, authentication);
|
||||
// 存储Access Token
|
||||
tokenRedisStorage.putAccessToken(accessToken, authentication);
|
||||
|
||||
// 存储Refresh Token
|
||||
String deviceId = extractDeviceId(request);
|
||||
tokenRedisStorage.putRefreshToken(refreshToken, authentication.getName(), deviceId);
|
||||
// 存储Refresh Token
|
||||
String deviceId = extractDeviceId(request);
|
||||
tokenRedisStorage.putRefreshToken(refreshToken, authentication.getName(), deviceId);
|
||||
|
||||
// 构造登录响应
|
||||
LoginResponse loginResponse = new LoginResponse(
|
||||
accessToken,
|
||||
refreshToken,
|
||||
"Bearer",
|
||||
1800, // 30分钟,秒数
|
||||
authentication.getName()
|
||||
);
|
||||
// 构造登录响应
|
||||
LoginResponse loginResponse = new LoginResponse(
|
||||
accessToken,
|
||||
refreshToken,
|
||||
"Bearer",
|
||||
1800, // 30分钟,秒数
|
||||
authentication.getName()
|
||||
);
|
||||
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
R<LoginResponse> data = new R<>(HttpServletResponse.SC_OK, "登录成功", true, loginResponse);
|
||||
objectMapper.writeValue(response.getWriter(), data);
|
||||
}
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
R<LoginResponse> data = new R<>(HttpServletResponse.SC_OK, "登录成功", true, loginResponse);
|
||||
objectMapper.writeValue(response.getWriter(), data);
|
||||
}
|
||||
|
||||
private String extractDeviceId(HttpServletRequest request) {
|
||||
// 尝试从User-Agent提取设备信息
|
||||
String userAgent = request.getHeader("User-Agent");
|
||||
if (userAgent != null) {
|
||||
// 简单的设备识别逻辑,生产环境可以使用更复杂的识别算法
|
||||
if (userAgent.contains("Mobile") || userAgent.contains("Android") || userAgent.contains("iPhone")) {
|
||||
return "mobile-" + request.getRemoteAddr();
|
||||
} else if (userAgent.contains("Tablet") || userAgent.contains("iPad")) {
|
||||
return "tablet-" + request.getRemoteAddr();
|
||||
} else {
|
||||
return "desktop-" + request.getRemoteAddr();
|
||||
}
|
||||
}
|
||||
return "unknown-" + request.getRemoteAddr();
|
||||
}
|
||||
}
|
||||
private String extractDeviceId(HttpServletRequest request) {
|
||||
// 尝试从User-Agent提取设备信息
|
||||
String userAgent = request.getHeader("User-Agent");
|
||||
if (userAgent != null) {
|
||||
// 简单的设备识别逻辑,生产环境可以使用更复杂的识别算法
|
||||
if (userAgent.contains("Mobile") || userAgent.contains("Android") || userAgent.contains("iPhone")) {
|
||||
return "mobile-" + request.getRemoteAddr();
|
||||
} else if (userAgent.contains("Tablet") || userAgent.contains("iPad")) {
|
||||
return "tablet-" + request.getRemoteAddr();
|
||||
} else {
|
||||
return "desktop-" + request.getRemoteAddr();
|
||||
}
|
||||
}
|
||||
return "unknown-" + request.getRemoteAddr();
|
||||
}
|
||||
}
|
||||
|
||||
private record CustomAccessDeniedHandler(ObjectMapper objectMapper) implements AccessDeniedHandler {
|
||||
private record CustomAccessDeniedHandler(ObjectMapper objectMapper) implements AccessDeniedHandler {
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
log.warn("访问被拒绝", accessDeniedException);
|
||||
if (authentication.isAuthenticated()) {
|
||||
log.warn("用户「{}」访问「{}」被拒绝,因为:{}", authentication.getPrincipal(), request.getRequestURI(), accessDeniedException.getMessage());
|
||||
} else {
|
||||
log.warn("匿名用户访问「{}」被拒绝,因为:{}", request.getRequestURI(), accessDeniedException.getMessage());
|
||||
}
|
||||
R<Object> data = new R<>(HttpServletResponse.SC_FORBIDDEN, "无权限", false, null);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
objectMapper.writeValue(response.getWriter(), data);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
log.warn("访问被拒绝", accessDeniedException);
|
||||
if (authentication.isAuthenticated()) {
|
||||
log.warn("用户「{}」访问「{}」被拒绝,因为:{}", authentication.getPrincipal(), request.getRequestURI(), accessDeniedException.getMessage());
|
||||
} else {
|
||||
log.warn("匿名用户访问「{}」被拒绝,因为:{}", request.getRequestURI(), accessDeniedException.getMessage());
|
||||
}
|
||||
R<Object> data = new R<>(HttpServletResponse.SC_FORBIDDEN, "无权限", false, null);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
objectMapper.writeValue(response.getWriter(), data);
|
||||
}
|
||||
}
|
||||
|
||||
private record CustomLogoutSuccessHandler(ObjectMapper objectMapper,
|
||||
TokenRedisStorage tokenRedisStorage) implements LogoutHandler {
|
||||
@Override
|
||||
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
|
||||
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
private record CustomLogoutSuccessHandler(ObjectMapper objectMapper,
|
||||
TokenRedisStorage tokenRedisStorage) implements LogoutHandler {
|
||||
@Override
|
||||
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
|
||||
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
|
||||
if (header != null && header.startsWith("Bearer ")) {
|
||||
String token = header.substring(7);
|
||||
authentication = tokenRedisStorage.get(token);
|
||||
tokenRedisStorage.remove(token);
|
||||
}
|
||||
if (authentication != null) {
|
||||
log.info("用户「{}」退出成功", authentication.getName());
|
||||
String all = request.getParameter("all");
|
||||
if ("true".equals(all)) {
|
||||
tokenRedisStorage.removeByUserName(authentication.getName());
|
||||
}
|
||||
}
|
||||
R<Object> data = new R<>(HttpServletResponse.SC_OK, "退出成功", true, null);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
try {
|
||||
objectMapper.writeValue(response.getWriter(), data);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (header != null && header.startsWith("Bearer ")) {
|
||||
String token = header.substring(7);
|
||||
authentication = tokenRedisStorage.get(token);
|
||||
tokenRedisStorage.remove(token);
|
||||
}
|
||||
if (authentication != null) {
|
||||
log.info("用户「{}」退出成功", authentication.getName());
|
||||
String all = request.getParameter("all");
|
||||
if ("true".equals(all)) {
|
||||
tokenRedisStorage.removeByUserName(authentication.getName());
|
||||
}
|
||||
}
|
||||
R<Object> data = new R<>(HttpServletResponse.SC_OK, "退出成功", true, null);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
try {
|
||||
objectMapper.writeValue(response.getWriter(), data);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
import vip.jcfd.common.core.R;
|
||||
import vip.jcfd.common.dto.TokenRefreshRequest;
|
||||
import vip.jcfd.common.dto.TokenRefreshResponse;
|
||||
import vip.jcfd.log.annotation.Log;
|
||||
import vip.jcfd.web.auth.RefreshTokenAuthenticationToken;
|
||||
import vip.jcfd.web.redis.TokenRedisStorage;
|
||||
|
||||
@ -37,6 +38,7 @@ public class AuthController {
|
||||
|
||||
@PostMapping("/refresh-token")
|
||||
@Operation(summary = "刷新Token", description = "使用Refresh Token获取新的Access Token和Refresh Token")
|
||||
@Log("刷新了token")
|
||||
public R<TokenRefreshResponse> refreshToken(
|
||||
@Valid @RequestBody TokenRefreshRequest request) {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user