Skip to content

Commit

Permalink
✨ feat(Menu.java, RedisConfiguration.java, `SessionConfiguration.…
Browse files Browse the repository at this point in the history
…java`, `R2dbcConfiguration.java`, `SecurityConfiguration.java`, `LoggerFilter.java`): Enhance code documentation and refine security configurations

This commit introduces comprehensive documentation comments for the `Menu` class, improving the understanding of its structure and methods. Additionally, it augments security configurations in `SecurityConfiguration.java` with detailed explanations for OAuth2 client services, session registry, password encoding, RSocket security, and CSRF protection customization. Furthermore, it refines Redis cache management in `RedisConfiguration.java` and enhances database connection setup in `R2dbcConfiguration.java`. Lastly, it bolsters request-response logging mechanics in `LoggerFilter.java` with clearer commentary.
  • Loading branch information
vnobo committed Sep 18, 2024
1 parent 89b0dd8 commit 94f7dc0
Show file tree
Hide file tree
Showing 6 changed files with 494 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,44 @@
@RequiredArgsConstructor
public class R2dbcConfiguration extends AbstractR2dbcConfiguration {

/**
* A collection of custom converters used to adapt between various data types when interacting with the database.
* These converters facilitate the mapping of application-specific objects to database-compatible representations and vice versa.
*/
private final List<Converter<?, ?>> customConverters;

/**
* Establishes and returns the configured R2DBC Connection Factory instance.
* This method is part of the configuration setup for R2DBC within the application,
* providing the necessary connection details to interact with the database in a reactive manner.
*
* @return A non-null instance of {@link ConnectionFactory} configured for R2DBC connectivity.
*/
@Override
public @NonNull ConnectionFactory connectionFactory() {
return ConnectionFactories.get("r2dbc:..");
}

/**
* Retrieves a list of custom converters that have been configured for the application.
* These converters are typically used to customize data binding between Java objects and
* the database, enabling the application to handle specific data types or transformations.
*
* @return A non-null list containing instances of custom converters. The list is a fresh copy
* and modifications to it will not affect the original configuration.
*/
@Override
public @NonNull List<Object> getCustomConverters() {
return Lists.newArrayList(customConverters);
}

/**
* Provides a bean instance of {@link ReactiveAuditorAware} configured to use {@link UserAuditorAware}.
* This method sets up auditing awareness for reactive contexts, allowing the system to automatically
* populate audit fields like creator, updater, etc., with details from the current security context.
*
* @return A bean instance of {@link ReactiveAuditorAware<UserAuditor>} that supplies the current auditor details.
*/
@Bean
public ReactiveAuditorAware<UserAuditor> userAuditorProvider() {
return new UserAuditorAware();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@
@EnableCaching
public class RedisConfiguration {

/**
* Customizes the RedisCacheManagerBuilder by setting up default serialization for keys and values.
*
* <p>This method configures the cache manager to use a StringRedisSerializer for serializing keys,
* ensuring they are stored as strings in Redis. For values, it utilizes a Jackson2JsonRedisSerializer,
* which allows any Java object to be serialized into JSON format before being saved into Redis, with the
* help of the provided ObjectMapper instance.
*
* @param objectMapper The ObjectMapper instance used to serialize and deserialize Java objects to and from JSON.
* @return A RedisCacheManagerBuilderCustomizer that applies the serialization settings to the cache builder.
*/
@Bean
public RedisCacheManagerBuilderCustomizer myRedisCacheManagerBuilderCustomizer(ObjectMapper objectMapper) {
return (builder) -> builder.cacheDefaults()
Expand All @@ -37,6 +48,19 @@ public RedisCacheManagerBuilderCustomizer myRedisCacheManagerBuilderCustomizer(O
.fromSerializer(new Jackson2JsonRedisSerializer<>(objectMapper, Object.class)));
}

/**
* Creates and configures a {@link ReactiveRedisTemplate} for interacting with Redis in a reactive manner.
* This template is designed to work with string keys and arbitrary Java objects, serialized as JSON,
* enabling efficient storage and retrieval of complex data structures from a Redis data store.
*
* @param factory A {@link ReactiveRedisConnectionFactory} that provides the connection to the Redis server.
* This factory should be capable of supporting reactive operations.
* @param objectMapper An {@link ObjectMapper} instance used for serializing and deserializing Java objects
* to and from JSON. This is crucial for handling value serialization in a way that is
* compatible with the application's object model.
* @return A configured instance of {@link ReactiveRedisTemplate<String, Object>} ready to perform
* reactive Redis operations, with keys as strings and values as arbitrary Java objects (serialized as JSON).
*/
@Bean
public ReactiveRedisTemplate<String, Object> reactiveObjectRedisTemplate(ReactiveRedisConnectionFactory factory,
ObjectMapper objectMapper) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,25 +66,61 @@ public class SecurityConfiguration {

private final Oauth2SuccessHandler authenticationSuccessHandler;

/**
* Configures and provides an instance of {@link ReactiveOAuth2AuthorizedClientService} which is primarily responsible
* for managing OAuth2 authorized client details, storing and retrieving them from a reactive data source via
* {@link DatabaseClient} and handling client registrations defined in {@link ReactiveClientRegistrationRepository}.
*
* @param databaseClient The reactive database client used for interacting with the data source to persist OAuth2 authorization data.
* @param clientRepository The repository containing the definitions of all registered clients for OAuth2 authentication.
* @return A configured instance of {@link R2dbcReactiveOAuth2AuthorizedClientService} capable of handling OAuth2 authorized client services within a reactive context.
*/
@Bean
@Primary
public ReactiveOAuth2AuthorizedClientService oAuth2ClientService(DatabaseClient databaseClient,
ReactiveClientRegistrationRepository clientRepository) {
return new R2dbcReactiveOAuth2AuthorizedClientService(databaseClient, clientRepository);
}

/**
* Creates and configures a SpringSessionBackedReactiveSessionRegistry bean.
* This registry is designed to manage sessions within a reactive environment, backed by the provided
* ReactiveSessionRepository and ReactiveFindByIndexNameSessionRepository instances.
*
* @param <S> The type of session extending the Session interface.
* @param sessionRepository A reactive session repository for storing and retrieving session data.
* @param indexedSessionRepository A reactive session repository capable of finding sessions by index name,
* enhancing session management capabilities.
* @return An instance of SpringSessionBackedReactiveSessionRegistry configured with the given repositories,
* ready to manage and provide session-related services in a reactive context.
*/
@Bean
public <S extends Session> SpringSessionBackedReactiveSessionRegistry<S> sessionRegistry(
ReactiveSessionRepository<S> sessionRepository,
ReactiveFindByIndexNameSessionRepository<S> indexedSessionRepository) {
return new SpringSessionBackedReactiveSessionRegistry<>(sessionRepository, indexedSessionRepository);
}

/**
* Provides a PasswordEncoder bean that utilizes a delegating strategy for encoding passwords.
* This delegating encoder can handle different password encoding schemes based on encoded passwords' ids.
* It consults a set of encoders and picks the one matching the id prepended to the encoded password.
*
* @return A {@link PasswordEncoder} instance which is capable of delegating to the appropriate password encoding strategy.
*/
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

/**
* Configures and provides an RSocket interceptor for security purposes.
* This method sets up authorization policies for RSocket interactions,
* enforcing authentication requirements and permission rules.
*
* @param rsocket The RSocketSecurity configuration object used to define security rules.
* @return A configured PayloadSocketAcceptorInterceptor that enforces the defined security policies for RSocket communications.
*/
@Bean
public PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
rsocket.authorizePayload(authorize ->
Expand All @@ -97,6 +133,15 @@ public PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsock
return rsocket.build();
}

/**
* Configures and returns a SecurityWebFilterChain bean for Spring Security's WebFlux setup.
* This method customizes various aspects of security including authorization rules,
* session management, basic authentication entry point, form login, CSRF protection,
* logout handling, and OAuth2 login support.
*
* @param http The ServerHttpSecurity builder used to configure web security settings.
* @return A configured SecurityWebFilterChain ready to be used in the application's security filter chain.
*/
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange(exchange -> {
Expand All @@ -121,12 +166,33 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
return http.build();
}

/**
* Configures the Cross-Site Request Forgery (CSRF) protection specifications within the provided {@link ServerHttpSecurity.CsrfSpec}.
* This method customizes the CSRF behavior by:
* <ul>
* <li>Defining a matcher to ignore CSRF protection for certain endpoints.</li>
* <li>Setting a cookie-based repository for storing CSRF tokens with HTTP-only flag disabled.</li>
* <li>Registering a handler to place the CSRF token as a request attribute.</li>
* </ul>
*
* @param csrfSpec The CSRF specification object to be configured, part of the {@link ServerHttpSecurity} configuration.
*/
private void setCsrfSpec(ServerHttpSecurity.CsrfSpec csrfSpec) {
csrfSpec.requireCsrfProtectionMatcher(new IgnoreRequireCsrfProtectionMatcher());
csrfSpec.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse());
csrfSpec.csrfTokenRequestHandler(new ServerCsrfTokenRequestAttributeHandler());
}

/**
* Configures the logout behavior for the server HTTP security.
*
* This method sets up the logout process by adding a security context logout handler,
* a handler to clear site data through headers, and delegates these handlers
* to manage the logout process. It also specifies the URL that triggers the logout
* action and defines the success handler to return a HTTP status upon successful logout.
*
* @param logout The LogoutSpec instance to configure logout specifics of ServerHttpSecurity.
*/
private void setLogout(ServerHttpSecurity.LogoutSpec logout) {
ServerLogoutHandler securityContext = new SecurityContextServerLogoutHandler();
ClearSiteDataServerHttpHeadersWriter writer = new ClearSiteDataServerHttpHeadersWriter(
Expand All @@ -139,13 +205,31 @@ private void setLogout(ServerHttpSecurity.LogoutSpec logout) {
logout.logoutSuccessHandler(new HttpStatusReturningServerLogoutSuccessHandler());
}

/**
* Matcher implementation to exclude specific HTTP requests from requiring CSRF protection.
* This is particularly useful for endpoints that are deemed safe from CSRF attacks due to their nature
* or implementation specifics, such as certain GET requests or known safe POST requests like OAuth2 token exchanges.
* The matcher combines a set of allowed HTTP methods and a list of more specific matchers to determine
* if CSRF protection should be ignored for a given request.
*/
static class IgnoreRequireCsrfProtectionMatcher implements ServerWebExchangeMatcher {
private final Set<HttpMethod> allowedMethods = new HashSet<>(
Arrays.asList(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.TRACE, HttpMethod.OPTIONS));
private final List<ServerWebExchangeMatcher> allowedMatchers = List.of(
new PathPatternParserServerWebExchangeMatcher("/oauth2/none", HttpMethod.POST)
);

/**
* Evaluates whether a given server web exchange should be excluded from CSRF protection.
* It checks if the request's HTTP method is within the allowed set and if it matches any
* specified additional matchers.
*
* @param exchange The server web exchange to evaluate for CSRF protection exclusion.
* @return A Mono that emits a MatchResult indicating whether the exchange should be ignored for CSRF protection.
* - If the request method is allowed or matches custom criteria, emits MatchResult.notMatch(),
* suggesting the request should be ignored by CSRF protection.
* - Otherwise, emits MatchResult.match() indicating standard CSRF protection should apply.
*/
@Override
public Mono<MatchResult> matches(ServerWebExchange exchange) {
Mono<MatchResult> ignoreMono = new OrServerWebExchangeMatcher(allowedMatchers)
Expand All @@ -158,9 +242,34 @@ public Mono<MatchResult> matches(ServerWebExchange exchange) {
}
}

/**
* CustomServerAuthenticationEntryPoint is a specialized entry point for handling authentication failures in a reactive server environment.
* It extends the HttpBasicServerAuthenticationEntryPoint to customize the behavior when an authentication exception occurs.
* Specifically, it differentiates between standard requests and XMLHttpRequests (AJAX calls) to provide appropriate responses.
*
* <p>This class overrides the commence method to log authentication failure details and customize the response based on the nature
* of the incoming request. If the request is identified as an AJAX call, it will return a JSON formatted error response with
* an UNAUTHORIZED status code. Otherwise, it falls back to the default behavior provided by its superclass.</p>
*
* <p>Notable Methods:</p>
* <ul>
* <li>{@link #commence(ServerWebExchange, AuthenticationException)}: Handles the commencement of the authentication failure handling.</li>
* <li>{@link #isXmlHttpRequest(String)}: Determines if the request is an XMLHttpRequest.</li>
* <li>{@link #handleXmlHttpRequestFailure(ServerWebExchange, AuthenticationException)}: Manages the response for failed AJAX requests.</li>
* <li>{@link #createErrorResponse(ServerWebExchange, AuthenticationException)}: Constructs an error response object for AJAX request failures.</li>
* </ul>
*/
@Log4j2
static class CustomServerAuthenticationEntryPoint extends HttpBasicServerAuthenticationEntryPoint {

/**
* Handles the commencement of failure handling due to an authentication exception during a server web exchange.
* Determines the nature of the request and delegates to the appropriate failure handling method.
*
* @param exchange The current server web exchange containing the request and response objects.
* @param e The authentication exception that triggered this failure handling.
* @return A Mono that, when subscribed to, signals the completion of the failure handling, typically without a value (Void).
*/
@Override
public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException e) {
ServerHttpRequest request = exchange.getRequest();
Expand All @@ -173,10 +282,26 @@ public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException e
return super.commence(exchange, e);
}

/**
* Determines whether the provided request header 'requestedWith' indicates an XMLHttpRequest.
* This is typically used to check if the request was made via AJAX.
*
* @param requestedWith The value of the 'X-Requested-With' header from the HTTP request.
* @return {@code true} if the 'requestedWith' header indicates an XMLHttpRequest, {@code false} otherwise.
*/
private boolean isXmlHttpRequest(String requestedWith) {
return requestedWith != null && requestedWith.contains(XML_HTTP_REQUEST);
}

/**
* Handles the failure case of an XML HTTP request by setting the response status to UNAUTHORIZED,
* preparing an error response in JSON format, and sending it back to the client.
*
* @param exchange The current server web exchange containing the request and response objects.
* @param e The authentication exception that caused the request handling failure.
* @return A Mono that, when subscribed to, completes after writing the error response to the client
* and handling any potential errors during the write operation by releasing allocated resources.
*/
private Mono<Void> handleXmlHttpRequestFailure(ServerWebExchange exchange, AuthenticationException e) {
var response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
Expand All @@ -191,6 +316,14 @@ private Mono<Void> handleXmlHttpRequestFailure(ServerWebExchange exchange, Authe
.doOnError((error) -> DataBufferUtils.release(bodyBuffer));
}

/**
* Constructs an error response object for authentication failures.
*
* @param exchange The ServerWebExchange associated with the request that triggered the error.
* @param e The AuthenticationException that caused the error response to be generated.
* @return An ErrorResponse instance representing the authentication failure, including details
* from the original request and the exception message.
*/
private ErrorResponse createErrorResponse(ServerWebExchange exchange, AuthenticationException e) {
return ErrorResponse.of(exchange.getRequest().getId(), exchange.getRequest().getPath().value(),
401, "认证失败,检查你的用户名,密码是否正确或安全密钥是否过期!", List.of(e.getMessage()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public void setSessionId(@NonNull ServerWebExchange exchange, @NonNull String id

/**
* Expires the session associated with the given server web exchange.
*
* <p>
* This method utilizes the {@code cookieWebSessionIdResolver} to expire the session,
* effectively invalidating any session data linked to the provided exchange.
*
Expand Down
Loading

0 comments on commit 94f7dc0

Please sign in to comment.