Skip to content

Commit 3dad57f

Browse files
feature : can customize the knife error payload
1 parent 136f7a4 commit 3dad57f

File tree

10 files changed

+161
-197
lines changed

10 files changed

+161
-197
lines changed

README.md

+18-12
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
}
2424
```
2525

26-
* Error Payload
26+
* Error Payload (Customizable)
2727
```json
2828
{
2929
"timestamp": 1719470948370,
@@ -71,10 +71,9 @@ mvnw clean install # Integration tests are done here, which creates docs by Spri
7171
- The client module definitely consumes the API module, but not vice versa.
7272

7373
## API Guide
74-
#### The implementation method is shown in the client source code.
7574

7675
### **Registration**
77-
- As the Api module consumes JPA, adding it to Beans is required.
76+
- See the `client` folder. As the Api module consumes JPA, adding it to Beans is required.
7877

7978
```java
8079

@@ -117,16 +116,23 @@ public class CommonDataSourceConfiguration {
117116
}
118117
```
119118

120-
### **Implementation**
119+
### **Implementation of...**
120+
121+
#### "Mandatory" settings
121122
- The only mandatory setting is ``client.config.securityimpl.service.userdetail.CustomUserDetailsServiceFactory``. The rest depend on your specific situation.
122-
123-
- **Use PointCut when events happen such as tokens created**
124-
- ``SecurityPointCut``
125-
- See the source code in ``client.config.securityimpl.aop``
126-
- **Register error user messages as desired**
127-
- ``ISecurityUserExceptionMessageService``
128-
- See the source code in ``client.config.securityimpl.message``
129-
123+
124+
#### "Customizable" settings
125+
126+
- **Use PointCut when events happen such as tokens created**
127+
- ``SecurityPointCut``
128+
- See the source code in ``client.config.securityimpl.aop``
129+
- **Register error user messages as desired**
130+
- ``ISecurityUserExceptionMessageService``
131+
- See the source code in ``client.config.securityimpl.message``
132+
- **Customize the whole error payload as desired**
133+
- Customize only two points in
134+
- ``client.config.securityimpl.errorhandler.CustomAuthenticationFailureHandlerImpl``
135+
- ``client.config.response.error.GlobalExceptionHandler``
130136
## Running this App with Docker
131137
* Use the following module for Blue-Green deployment:
132138
* https://github.com/patternknife/docker-blue-green-runner

client/pom.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd">
77
<modelVersion>4.0.0</modelVersion>
88
<groupId>com.patternknife.securityhelper.oauth2.client</groupId>
99
<artifactId>spring-security-oauth2-password-jpa-implementation-client</artifactId>
10-
<version>2.4.0</version>
10+
<version>2.5.0</version>
1111
<packaging>jar</packaging>
1212

1313
<properties>
@@ -41,7 +41,7 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd">
4141
<dependency>
4242
<groupId>io.github.patternknife.securityhelper.oauth2.api</groupId>
4343
<artifactId>spring-security-oauth2-password-jpa-implementation</artifactId>
44-
<version>2.4.0</version>
44+
<version>2.5.0</version>
4545
</dependency>
4646

4747
<!-- DB -->

client/src/main/java/com/patternknife/securityhelper/oauth2/client/config/response/error/GlobalExceptionHandler.java

+38-164
Original file line numberDiff line numberDiff line change
@@ -2,184 +2,60 @@
22

33

44
import com.patternknife.securityhelper.oauth2.client.config.response.error.dto.CustomErrorResponsePayload;
5-
import com.patternknife.securityhelper.oauth2.client.config.response.error.exception.auth.*;
6-
import com.patternknife.securityhelper.oauth2.client.config.response.error.exception.data.*;
7-
8-
import com.patternknife.securityhelper.oauth2.client.config.response.error.exception.payload.SearchFilterException;
95

106
import com.patternknife.securityhelper.oauth2.client.config.response.error.message.GeneralErrorMessage;
11-
import jakarta.servlet.http.HttpServletRequest;
12-
import jakarta.validation.ConstraintViolationException;
7+
import io.github.patternknife.securityhelper.oauth2.api.config.response.error.ExceptionKnifeUtils;
8+
import io.github.patternknife.securityhelper.oauth2.api.config.response.error.dto.ErrorResponsePayload;
9+
import io.github.patternknife.securityhelper.oauth2.api.config.response.error.exception.auth.KnifeOauth2AuthenticationException;
10+
import io.github.patternknife.securityhelper.oauth2.api.config.security.message.DefaultSecurityUserExceptionMessage;
11+
import io.github.patternknife.securityhelper.oauth2.api.config.security.message.ISecurityUserExceptionMessageService;
12+
import lombok.RequiredArgsConstructor;;
1313
import org.springframework.http.HttpStatus;
1414
import org.springframework.http.ResponseEntity;
15-
import org.springframework.http.converter.HttpMessageNotReadableException;
16-
import org.springframework.validation.BindException;
17-
import org.springframework.validation.FieldError;
18-
import org.springframework.web.bind.MethodArgumentNotValidException;
19-
import org.springframework.web.bind.MissingServletRequestParameterException;
15+
import org.springframework.security.access.AccessDeniedException;
16+
import org.springframework.security.core.AuthenticationException;
2017
import org.springframework.web.bind.annotation.ControllerAdvice;
2118
import org.springframework.web.bind.annotation.ExceptionHandler;
22-
import org.springframework.web.client.ResourceAccessException;
23-
import org.springframework.web.context.request.WebRequest;
2419

25-
import java.util.HashMap;
26-
import java.util.Map;
20+
import org.springframework.web.context.request.WebRequest;
2721

2822

23+
/*
24+
*
25+
* Customize the exception payload by implementing this, which replaces
26+
* 'io.github.patternknife.securityhelper.oauth2.api.config.response.error.SecurityKnifeExceptionHandler'
27+
*
28+
* Once you create 'GlobalExceptionHandler', you should insert the following two as default. Otherwise, 'unhandledExceptionHandler' is prior to 'io.github.patternknife.securityhelper.oauth2.api.config.response.error.SecurityKnifeExceptionHandler'.
29+
*
30+
* */
2931
@ControllerAdvice
32+
@RequiredArgsConstructor
3033
public class GlobalExceptionHandler {
3134

32-
33-
// UserDeletedException : caused by the process of user deactivation
34-
// UserRestoredException : caused by the process of user reactivation
35-
@ExceptionHandler({UserDeletedException.class, UserRestoredException.class})
36-
public ResponseEntity<?> activationException(Exception ex, WebRequest request) {
37-
CustomErrorResponsePayload customErrorResponsePayload = new CustomErrorResponsePayload(ex.getMessage() != null ? ex.getMessage() : CustomExceptionUtils.getAllCauses(ex),
38-
request.getDescription(false),ex.getMessage() , ex.getStackTrace()[0].toString());
39-
return new ResponseEntity<>(customErrorResponsePayload, HttpStatus.FORBIDDEN);
40-
}
41-
42-
43-
// 2. data
44-
@ExceptionHandler(ResourceNotFoundException.class)
45-
public ResponseEntity<?> resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
46-
47-
CustomErrorResponsePayload customErrorResponsePayload;
48-
if(ex.getErrorMessages() != null){
49-
50-
customErrorResponsePayload = new CustomErrorResponsePayload(ex.getErrorMessages(),
51-
ex, request.getDescription(false), CustomExceptionUtils.getAllStackTraces(ex),
52-
CustomExceptionUtils.getAllCauses(ex), null);
53-
54-
}else{
55-
customErrorResponsePayload = new CustomErrorResponsePayload(ex.getMessage(), request.getDescription(false),
56-
ex.getMessage(), CustomExceptionUtils.getAllStackTraces(ex),
57-
CustomExceptionUtils.getAllCauses(ex));
35+
private final ISecurityUserExceptionMessageService iSecurityUserExceptionMessageService;
36+
37+
// 401 : Authentication
38+
@ExceptionHandler({AuthenticationException.class})
39+
public ResponseEntity<?> authenticationException(Exception ex, WebRequest request) {
40+
ErrorResponsePayload errorResponsePayload;
41+
if(ex instanceof KnifeOauth2AuthenticationException && ((KnifeOauth2AuthenticationException) ex).getErrorMessages() != null) {
42+
errorResponsePayload = new ErrorResponsePayload(((KnifeOauth2AuthenticationException) ex).getErrorMessages(),
43+
ex, request.getDescription(false), ExceptionKnifeUtils.getAllStackTraces(ex),
44+
ExceptionKnifeUtils.getAllCauses(ex), null);
45+
}else {
46+
errorResponsePayload = new ErrorResponsePayload(ExceptionKnifeUtils.getAllCauses(ex), request.getDescription(false), iSecurityUserExceptionMessageService.getUserMessage(DefaultSecurityUserExceptionMessage.AUTHENTICATION_LOGIN_FAILURE),
47+
ex.getMessage(), ex.getStackTrace()[0].toString());
5848
}
59-
return new ResponseEntity<>(customErrorResponsePayload, HttpStatus.NOT_FOUND);
60-
61-
}
62-
63-
64-
65-
@ExceptionHandler(SearchFilterException.class)
66-
public ResponseEntity<?> searchFilterException(SearchFilterException ex, WebRequest request) {
67-
68-
//logger.error(ex.getMessage());
69-
CustomErrorResponsePayload customErrorResponsePayload = new CustomErrorResponsePayload(ex.getCause().getMessage(), request.getDescription(false),
70-
ex.getMessage(), ex.getStackTrace()[0].toString());
71-
return new ResponseEntity<>(customErrorResponsePayload, HttpStatus.BAD_REQUEST);
49+
return new ResponseEntity<>(errorResponsePayload, HttpStatus.UNAUTHORIZED);
7250
}
7351

74-
75-
76-
@ExceptionHandler(NullPointerException.class)
77-
public ResponseEntity<?> nullPointerException(NullPointerException ex, WebRequest request) {
78-
CustomErrorResponsePayload customErrorResponsePayload = new CustomErrorResponsePayload(ex.getMessage(), request.getDescription(false),
79-
GeneralErrorMessage.NULL_VALUE_FOUND.getUserMessage(), CustomExceptionUtils.getAllStackTraces(ex), CustomExceptionUtils.getAllCauses(ex));
80-
return new ResponseEntity<>(customErrorResponsePayload, HttpStatus.NOT_FOUND);
52+
// 403 : Authorization
53+
@ExceptionHandler({ AccessDeniedException.class })
54+
public ResponseEntity<?> authorizationException(Exception ex, WebRequest request) {
55+
ErrorResponsePayload errorResponsePayload = new ErrorResponsePayload(ex.getMessage() != null ? ex.getMessage() : ExceptionKnifeUtils.getAllCauses(ex), request.getDescription(false),
56+
ex.getMessage() == null || ex.getMessage().equals("Access Denied") ? iSecurityUserExceptionMessageService.getUserMessage(DefaultSecurityUserExceptionMessage.AUTHORIZATION_FAILURE) : ex.getMessage(), ex.getStackTrace()[0].toString());
57+
return new ResponseEntity<>(errorResponsePayload, HttpStatus.FORBIDDEN);
8158
}
82-
83-
84-
85-
86-
87-
// 3. Request @Valid
88-
/* 1. Validating the request body (when not using @RequestParam): Error thrown by @Valid. */
89-
@ExceptionHandler(MethodArgumentNotValidException.class)
90-
public ResponseEntity<?> methodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) {
91-
92-
Map<String, String> userValidationMessages = CustomExceptionUtils.extractMethodArgumentNotValidErrors(ex);
93-
94-
CustomErrorResponsePayload customErrorResponsePayload = new CustomErrorResponsePayload(ex.getMessage(), request.getDescription(false),
95-
null,
96-
userValidationMessages,
97-
CustomExceptionUtils.getAllStackTraces(ex), CustomExceptionUtils.getAllCauses(ex));
98-
return new ResponseEntity<>(customErrorResponsePayload, HttpStatus.UNPROCESSABLE_ENTITY);
99-
}
100-
//
101-
//The types of individual parameters in the request body (String, Date, Integer) are different, or there is a JSON format error.
102-
@ExceptionHandler(HttpMessageNotReadableException.class)
103-
public ResponseEntity<?> httpMessageNotReadableExceptionHandler(HttpMessageNotReadableException ex, WebRequest request) {
104-
105-
CustomErrorResponsePayload customErrorResponsePayload = new CustomErrorResponsePayload(ex.getMessage(), request.getDescription(false),
106-
"The received form does not match. Please contact the administrator with the following information. (Error details: " + ex.getMostSpecificCause().getMessage() + ")",
107-
null,
108-
CustomExceptionUtils.getAllStackTraces(ex), CustomExceptionUtils.getAllCauses(ex));
109-
return new ResponseEntity<>(customErrorResponsePayload, HttpStatus.BAD_REQUEST);
110-
}
111-
112-
/* 2-1. In the case of @RequestParam: Validity check thrown by @Validated. */
113-
@ExceptionHandler(ConstraintViolationException.class)
114-
public ResponseEntity<?> missingServletRequestParameterException(ConstraintViolationException ex, WebRequest request, HttpServletRequest h) {
115-
116-
CustomErrorResponsePayload customErrorResponsePayload = new CustomErrorResponsePayload(ex.getMessage(), request.getDescription(false),
117-
ex.getMessage(), CustomExceptionUtils.getAllStackTraces(ex), CustomExceptionUtils.getAllCauses(ex));
118-
return new ResponseEntity<>(customErrorResponsePayload, HttpStatus.UNPROCESSABLE_ENTITY);
119-
}
120-
/* 2-2. In the case of @RequestParam: The error thrown when @RequestParam is completely missing in the Controller. */
121-
@ExceptionHandler(MissingServletRequestParameterException.class)
122-
public ResponseEntity<?> missingServletRequestParameterException(MissingServletRequestParameterException ex, WebRequest request, HttpServletRequest h) {
123-
124-
CustomErrorResponsePayload customErrorResponsePayload = new CustomErrorResponsePayload(ex.getMessage(), request.getDescription(false),
125-
"Required parameter (" + ex.getParameterName() + ") is missing.", CustomExceptionUtils.getAllStackTraces(ex), CustomExceptionUtils.getAllCauses(ex));
126-
return new ResponseEntity<>(customErrorResponsePayload, HttpStatus.UNPROCESSABLE_ENTITY);
127-
}
128-
129-
/* 3. Other Custom Validation: For example, search in the source with @ValidPart. */
130-
@ExceptionHandler(BindException.class)
131-
public ResponseEntity<?> bindExceptionHandler(BindException ex, WebRequest request) {
132-
133-
Map<String, String> errorMessages = new HashMap<>();
134-
135-
for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) {
136-
errorMessages.put(fieldError.getField(), fieldError.getDefaultMessage());
137-
}
138-
139-
CustomErrorResponsePayload customErrorResponsePayload = new CustomErrorResponsePayload(ex.getMessage(), request.getDescription(false), null,
140-
errorMessages,
141-
CustomExceptionUtils.getAllStackTraces(ex), CustomExceptionUtils.getAllCauses(ex));
142-
return new ResponseEntity<>(customErrorResponsePayload, HttpStatus.UNPROCESSABLE_ENTITY);
143-
}
144-
145-
146-
// 4. Custom Validation using DB(select)
147-
148-
@ExceptionHandler(AlreadyExistsException.class)
149-
public ResponseEntity<?> alreadyExistsException(AlreadyExistsException ex, WebRequest request) {
150-
151-
CustomErrorResponsePayload customErrorResponsePayload = new CustomErrorResponsePayload(ex.getMessage(), request.getDescription(false),
152-
ex.getMessage(), CustomExceptionUtils.getAllStackTraces(ex), CustomExceptionUtils.getAllCauses(ex));
153-
return new ResponseEntity<>(customErrorResponsePayload, HttpStatus.UNPROCESSABLE_ENTITY);
154-
}
155-
156-
@ExceptionHandler(IllegalStateException.class)
157-
public ResponseEntity<?> illegalStateException(IllegalStateException ex, WebRequest request) {
158-
159-
CustomErrorResponsePayload customErrorResponsePayload = new CustomErrorResponsePayload(ex.getMessage(), request.getDescription(false),
160-
ex.getMessage(), CustomExceptionUtils.getAllStackTraces(ex), CustomExceptionUtils.getAllCauses(ex));
161-
return new ResponseEntity<>(customErrorResponsePayload, HttpStatus.BAD_REQUEST);
162-
}
163-
164-
165-
@ExceptionHandler(AlreadyInProgressException.class)
166-
public ResponseEntity<?> alreadyInProgressException(AlreadyInProgressException ex, WebRequest request) {
167-
168-
CustomErrorResponsePayload customErrorResponsePayload = new CustomErrorResponsePayload(ex.getMessage(), request.getDescription(false),
169-
ex.getMessage(), CustomExceptionUtils.getAllStackTraces(ex), CustomExceptionUtils.getAllCauses(ex));
170-
return new ResponseEntity<>(customErrorResponsePayload, HttpStatus.UNPROCESSABLE_ENTITY);
171-
}
172-
173-
174-
// config/resttemplate
175-
@ExceptionHandler(ResourceAccessException.class)
176-
public ResponseEntity<?> restTemplateAccessException(ResourceAccessException ex, WebRequest request) {
177-
178-
CustomErrorResponsePayload customErrorResponsePayload = new CustomErrorResponsePayload(ex.getMessage(), request.getDescription(false),
179-
"We apologize for the inconvenience. The call to the 3rd Party API provider has failed. If the problem persists, please contact customer service.", CustomExceptionUtils.getAllStackTraces(ex), CustomExceptionUtils.getAllCauses(ex));
180-
return new ResponseEntity<>(customErrorResponsePayload, HttpStatus.REQUEST_TIMEOUT);
181-
}
182-
18359

18460
// Unhandled
18561
@ExceptionHandler(Exception.class)
@@ -189,6 +65,4 @@ public ResponseEntity<?> unhandledExceptionHandler(Exception ex, WebRequest requ
18965
return new ResponseEntity<>(customErrorResponsePayload, HttpStatus.INTERNAL_SERVER_ERROR);
19066
}
19167

192-
193-
19468
}

0 commit comments

Comments
 (0)