Skip to content

Commit

Permalink
GitMoji Convention Commit Message:
Browse files Browse the repository at this point in the history
```markdown
🔥 chore(`SecurityDetails`, `ErrorResponse`, `UsersService`, `GlobalExceptionHandler`, `SecurityConfiguration`, `RestServerException`): Refactor error handling and exception management

Refactored error responses to use HttpStatusCode, simplified exception constructors, and improved readability by removing redundant code and enhancing exception messages. Also, restructured the SecurityDetails class for better organization and security practices.
```
  • Loading branch information
vnobo committed Sep 18, 2024
1 parent 94f7dc0 commit b56fbe6
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 123 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,30 @@
import java.time.LocalDateTime;

/**
* Represents an error response structure that is used to convey detailed information
* about an error encountered during processing.
* This record encapsulates metadata such as the request identifier, the endpoint path,
* the error code, a descriptive message,
* additional error details, and the timestamp when the error occurred.
* Represents an error response structure, encapsulating details about a specific error occurrence.
* This record is designed to be serialized and used for communicating error information between systems.
*
* @param requestId A unique identifier for the request that encountered the error.
* @param code The HTTP status code representing the type of error.
* @param path The endpoint or URI path where the error occurred.
* @param message A human-readable description of the error.
* @param errors Additional details or objects related to the error, can vary based on the context.
* @param time The timestamp indicating when the error response was created.
*/
public record ErrorResponse(String requestId, String path, Integer code,
public record ErrorResponse(String requestId, Integer code, String path,
String message, Object errors, LocalDateTime time) implements Serializable {

/**
* Constructs a new {@link ErrorResponse} instance with the provided parameters,
* automatically setting the timestamp to the current date and time.
* Creates an instance of {@link ErrorResponse} with the current local date and time.
*
* @param requestId The unique identifier for the request associated with the error.
* @param path The endpoint path where the error occurred.
* @param code The error code representing the type of error.
* @param message A descriptive message explaining the error.
* @param errors Additional details about the error, can be an object like a list of errors or a structured error message.
* @return A new {@link ErrorResponse} instance encapsulating the error details including the current timestamp.
* @param requestId A unique identifier for the request associated with this error response.
* @param code The HTTP status code reflecting the error type.
* @param path The path or endpoint where the error was encountered.
* @param message A descriptive message explaining the error.
* @param errors Additional error-related data, which could include exception details, validation errors, etc.
* @return A new {@link ErrorResponse} instance initialized with the provided parameters and the current timestamp.
*/
public static ErrorResponse of(String requestId, String path, Integer code, String message, Object errors) {
return new ErrorResponse(requestId, path, code, message, errors, LocalDateTime.now());
public static ErrorResponse of(String requestId, Integer code, String path, String message, Object errors) {
return new ErrorResponse(requestId, code, path, message, errors, LocalDateTime.now());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import java.util.List;

import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;

/**
* GlobalExceptionHandler is a centralized exception handler that intercepts and processes
* various exceptions thrown during the execution of REST endpoints within an application.
Expand Down Expand Up @@ -65,8 +67,8 @@ public ResponseEntity<ErrorResponse> handleBindException(ServerWebExchange excha
log.error(ex.getReason(), ex);
}
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).contentType(MediaType.APPLICATION_JSON)
.body(ErrorResponse.of(exchange.getRequest().getId(), exchange.getRequest().getPath().value(),
417, ex.getReason(), errors));
.body(ErrorResponse.of(exchange.getRequest().getId(), HttpStatus.BAD_REQUEST.value(),
exchange.getRequest().getPath().value(), ex.getReason(), errors));
}

/**
Expand Down Expand Up @@ -99,8 +101,8 @@ public ResponseEntity<ErrorResponse> handleFailureException(ServerWebExchange ex
log.error(ex.getCause().getMessage(), ex.getCause());
}
return ResponseEntity.status(HttpStatus.INSUFFICIENT_STORAGE).contentType(MediaType.APPLICATION_JSON)
.body(ErrorResponse.of(exchange.getRequest().getId(), exchange.getRequest().getPath().value(),
507, ex.getLocalizedMessage(), errors));
.body(ErrorResponse.of(exchange.getRequest().getId(), HttpStatus.INSUFFICIENT_STORAGE.value(),
exchange.getRequest().getPath().value(), ex.getLocalizedMessage(), errors));
}

/**
Expand All @@ -123,30 +125,10 @@ public ResponseEntity<ErrorResponse> handleRestServerException(ServerWebExchange
if (log.isDebugEnabled()) {
log.error(ex.getLocalizedMessage(), ex);
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON)
.body(ErrorResponse.of(exchange.getRequest().getId(), exchange.getRequest().getPath().value(),
ex.getCode(), ex.getLocalizedMessage(), ex.getMsg()));
return ResponseEntity.status(INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON)
.body(ErrorResponse.of(exchange.getRequest().getId(), ex.getStatusCode().value(),
exchange.getRequest().getPath().value(), ex.getLocalizedMessage(), ex.getErrors()));
}

/**
* Handles exceptions that occur during the processing of requests within the application.
* This method logs the exception details and constructs an appropriate error response
* to be sent back to the client.
*
* @param exchange The current server web exchange which holds the request and response information.
* @param ex The exception that was thrown during request processing.
* @return A ResponseEntity containing an ErrorResponse with details about the exception,
* including the request ID, the endpoint path, HTTP status code 500 (Internal Server Error),
* the exception message, and the cause message if available. The response body is in JSON format.
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(ServerWebExchange exchange, Exception ex) {
if (log.isDebugEnabled()) {
log.error(ex.getLocalizedMessage(), ex);
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON)
.body(ErrorResponse.of(exchange.getRequest().getId(), exchange.getRequest().getPath().value(),
500, ex.getLocalizedMessage(), ex.getCause().getMessage()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;

import java.io.Serializable;

Expand All @@ -24,81 +26,25 @@
@EqualsAndHashCode(callSuper = true)
public class RestServerException extends RuntimeException implements Serializable {

/**
* Encapsulates additional metadata or details about the error condition.
* This field can hold any type of object, enabling the transmission of structured
* information alongside the exception, such as maps, lists, or custom objects that
* provide context for the error scenario.
*/
protected Object msg;
/**
* The error code associated with this {@link RestServerException}.
* This field represents a custom error code that provides more granular information
* about the specific error scenario beyond standard HTTP status codes.
* It is intended for use in identifying and differentiating between various error conditions.
*/
protected int code;
protected final HttpStatusCode statusCode;
protected final Object errors;

/**
* Constructs a new instance of {@code RestServerException} with the specified error message and cause.
* This exception is typically used to convey that a RESTful service encountered an issue on the server side,
* where the provided message describes the error and the throwable denotes the underlying cause.
*
* @param message The detailed message explaining the reason for the exception.
* @param throwable The cause of the exception, usually an instance of {@link Throwable}
* that triggered this exception.
*/
public RestServerException(String message, Throwable throwable) {
super(message, throwable);
this.code = 500;
this.msg = throwable.fillInStackTrace().getMessage();
public RestServerException(HttpStatusCode code, String message, Object msg) {
super(message);
this.statusCode = code;
this.errors = msg;
}

/**
* Constructs a new instance of {@code RestServerException} with specified error code, message, and additional details.
* This exception is intended for conveying REST server-side error conditions, providing a more granular error code
* alongside a standard exception message and an optional object that can carry detailed contextual information.
*
* @param code The custom error code associated with the exception. This can be used to differentiate between
* various error scenarios beyond the standard HTTP status codes.
* @param message The human-readable error message explaining the exception circumstances. Should be concise and informative.
* @param msg An optional object containing additional metadata or details about the error. Can be any type,
* facilitating the passing of structured error information (e.g., maps, lists, or domain-specific objects).
*/
public RestServerException(int code, String message, Object msg) {
super(message);
this.msg = msg;
this.code = code;
public RestServerException(String message, Throwable throwable) {
this(HttpStatus.INTERNAL_SERVER_ERROR, message, throwable.fillInStackTrace());
}

/**
* Creates a new instance of {@code RestServerException} with a predefined HTTP status code of 500 (Internal Server Error),
* a custom message, and additional details encapsulated in the {@code msg} parameter.
* This method serves as a convenience factory for generating exceptions that indicate a generic server error
* along with specific contextual information.
*
* @param message A descriptive message explaining the reason for the exception.
* @param msg An arbitrary object containing additional details about the error. This can be used to provide more
* extensive error context or metadata.
* @return A new instance of {@code RestServerException} initialized with the provided message, a status code of 500,
* and the additional details object.
*/
public static RestServerException withMsg(String message, Object msg) {
return withMsg(500, message, msg);
public static RestServerException withMsg(String message, Object errors) {
return withMsg(HttpStatusCode.valueOf(500), message, errors);
}

/**
* Creates a new instance of {@code RestServerException} with a specified error code, message, and additional details.
* This static factory method allows for customization of the error response by providing a unique error code and
* a message along with an arbitrary object that can contain further information about the error condition.
*
* @param code The custom error code to identify the specific error scenario. This code supplements the HTTP status code.
* @param message The error message describing the exception's nature. Should be informative for debugging purposes.
* @param msg An optional object holding additional metadata or details related to the error. Can be any type.
* @return A new instance of {@code RestServerException} initialized with the provided code, message, and additional details.
*/
public static RestServerException withMsg(int code, String message, Object msg) {
return new RestServerException(code, message, msg);
public static RestServerException withMsg(HttpStatusCode code, String message, Object errors) {
return new RestServerException(code, message, errors);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,9 @@ private Mono<Void> handleXmlHttpRequestFailure(ServerWebExchange exchange, Authe
* 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()));
return ErrorResponse.of(exchange.getRequest().getId(), HttpStatus.UNAUTHORIZED.value(),
exchange.getRequest().getPath().value(),
"认证失败,检查你的用户名,密码是否正确或安全密钥是否过期!", List.of(e.getMessage()));
}

}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.plate.boot.security;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.plate.boot.security.core.UserAuditor;
import com.plate.boot.security.core.group.member.GroupMemberResponse;
import com.plate.boot.security.core.tenant.member.TenantMemberResponse;
import lombok.Getter;
Expand All @@ -19,44 +19,92 @@
import java.util.Set;

/**
* @author <a href="https://github.com/vnobo">Alex bob</a>
* SecurityDetails represents a user's security details, extending DefaultOAuth2User and implementing UserDetails.
* It encapsulates user authentication and authorization data, including OAuth2 attributes,
* tenant and group affiliations, and account status flags.
*/
@Setter
@Getter
public final class SecurityDetails extends DefaultOAuth2User implements UserDetails {

/**
* The serialVersionUID is a unique identifier for Serializable classes.
* It ensures compatibility between serialized objects and their respective classes during deserialization.
* This field is automatically generated by the SpringSecurityCoreVersion class to maintain serialization compatibility.
*/
@Serial
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

/**
* Represents the unique identifier code for a security detail within the {@link SecurityDetails} class.
* This string value is used to distinguish different security details instances.
*/
private String code;

/**
* Represents the username of the security principal.
* This field stores the unique identifier for a user within the system, typically used for authentication and authorization purposes.
*/
private String username;

/**
* Represents the nickname of a user within the security details.
* This string holds the colloquial or display name associated with the user's account.
*/
private String nickname;

/**
* A collection of {@link TenantMemberResponse} objects representing the tenants associated with the security details.
* Each {@code TenantMemberResponse} encapsulates information about a tenant member, including a unique identifier,
* tenant code, user code, enabled status, and audit details provided by {@link UserAuditor}.
* This set facilitates management and access to tenant-specific data for the authenticated user within a security context.
*/
private Set<TenantMemberResponse> tenants;

/**
* Represents a collection of {@link GroupMemberResponse} instances associated with a security details object.
* This set encapsulates the group membership responses for a user, providing information about the groups
* the user is a part of, including each group's name and additional metadata in the form of a JSON node.
*/
private Set<GroupMemberResponse> groups;

/**
* The password field securely stores the authentication credential for a user.
* This field is marked as ignored for JSON serialization and deserialization to prevent
* password exposure in transit or storage. Use the provided password methods to handle
* password operations safely.
*/
@JsonIgnore
private String password;

/**
* Indicates whether the security details are disabled.
* This field is not serialized during JSON processing due to the {@link JsonIgnore} annotation.
*/
@JsonIgnore
private Boolean disabled;

/**
* Indicates whether the account has expired or not.
* This field is not serialized in JSON responses due to the {@link JsonIgnore} annotation.
*/
@JsonIgnore
private Boolean accountExpired;

/**
* Indicates whether the account is locked or not.
* This field is not serialized in JSON responses due to the {@link JsonIgnore} annotation.
*/
@JsonIgnore
private Boolean accountLocked;

/**
* Indicates whether the user's credentials (password) have expired.
* This flag is used to determine if the user needs to reset their password upon next login attempt.
*/
@JsonIgnore
private Boolean credentialsExpired;

@JsonCreator
public SecurityDetails() {
super(null, Map.of("username", "any_none"), "username");
}

public SecurityDetails(Collection<? extends GrantedAuthority> authorities,
Map<String, Object> attributes, String nameAttributeKey) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public Mono<User> loadByCode(String code) {
public Mono<User> add(UserRequest request) {
return this.usersRepository.existsByUsernameIgnoreCase(request.getUsername()).flatMap(exists -> {
if (exists) {
return Mono.error(RestServerException.withMsg(417, "User already exists",
return Mono.error(RestServerException.withMsg("User already exists",
"Username [" + request.getUsername() + "] already exists!"));
}
return this.operate(request);
Expand All @@ -58,7 +58,7 @@ public Mono<User> add(UserRequest request) {

public Mono<User> modify(UserRequest request) {
return this.usersRepository.findByUsername(request.getUsername())
.switchIfEmpty(Mono.defer(() -> Mono.error(RestServerException.withMsg(417,
.switchIfEmpty(Mono.defer(() -> Mono.error(RestServerException.withMsg(
"User not found!", "User by username [" + request.getUsername() + "] not found!"))))
.flatMap(user -> {
request.setId(user.getId());
Expand Down Expand Up @@ -89,7 +89,7 @@ public Mono<User> save(User user) {
} else {
assert user.getId() != null;
return this.usersRepository.findById(user.getId())
.switchIfEmpty(Mono.error(RestServerException.withMsg(1404, "User not found",
.switchIfEmpty(Mono.error(RestServerException.withMsg("User not found",
"User by id [" + user.getId() + "] not found!")))
.flatMap(old -> {
user.setCreatedTime(old.getCreatedTime());
Expand Down

0 comments on commit b56fbe6

Please sign in to comment.