diff --git a/README.md b/README.md
index 6387a59..ad04502 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
io.github.patternknife.securityhelper.oauth2.api
spring-security-oauth2-password-jpa-implementation
- 2.6.0
+ 2.7.0
```
* Set up the same access & refresh token APIs on both ``/oauth2/token`` and on our controller layer such as ``/api/v1/traditional-oauth/token``, both of which function same and have `the same request & response payloads for success and errors`. (However, ``/oauth2/token`` is the standard that "spring-authorization-server" provides.)
@@ -44,13 +44,13 @@
## Dependencies
-| Category | Dependencies |
-|-------------------|--------------------------------------------|
-| Backend-Language | Java 17 |
-| Backend-Framework | Spring Boot 3.1.2 |
-| Main Libraries | Spring Security Authorization Server 1.2.3 |
-| Package-Manager | Maven 3.6.3 (mvnw, Dockerfile) |
-| RDBMS | Mysql 8.0.17 |
+| Category | Dependencies |
+|-------------------|-------------------------------------------------------------------|
+| Backend-Language | Java 17 |
+| Backend-Framework | Spring Boot 3.1.2 |
+| Main Libraries | Spring Security 6.1.2, Spring Security Authorization Server 1.2.3 |
+| Package-Manager | Maven 3.6.3 (mvnw, Dockerfile) |
+| RDBMS | Mysql 8.0.17 |
## Run the App
@@ -119,27 +119,34 @@ public class CommonDataSourceConfiguration {
### **Implementation of...**
#### "Mandatory" settings
+
- The only mandatory setting is ``client.config.securityimpl.service.userdetail.CustomUserDetailsServiceFactory``. The rest depend on your specific situation.
#### "Customizable" settings
- - **Use PointCut when events happen such as tokens created**
+ - **Insert your code when events happen such as tokens created**
- ``SecurityPointCut``
- - See the source code in ``client.config.securityimpl.aop``
+ - See the source code in ``client.config.securityimpl.aop``
+
+
- **Register error user messages as desired**
- ``ISecurityUserExceptionMessageService``
- See the source code in ``client.config.securityimpl.message``
+
+
- **Customize the whole error payload as desired for all cases**
- What is "all cases"?
- Authorization Server ("/oauth2/token", "/api/v1/traditional-oauth/token") and Resource Server (Bearer token inspection : 401, Permission : 403)
- - Customize two points such as
- - ``client.config.securityimpl.response.CustomAuthenticationFailureHandlerImpl`` ("/oauth2/token")
- - ``client.config.response.error.GlobalExceptionHandler`` ("/api/v1/traditional-oauth/token", Resource Server (Bearer token inspection))
- - ``client.config.securityimpl.response.CustomAuthenticationEntryPointImpl`` (Resource Server (Bearer token inspection : 401))
- - ``client.config.securityimpl.response.CustomAccessDeniedHandlerImpl`` (Resource Server (Permission inspection : 403))
+ - Customize errors of the following cases
+ - Login (/oauth2/token) : ``client.config.securityimpl.response.CustomAuthenticationFailureHandlerImpl``
+ - Login (/api/v1/traditional-oauth/token) : ``client.config.response.error.GlobalExceptionHandler.authenticationException`` ("/api/v1/traditional-oauth/token", Resource Server (Bearer token inspection))
+ - Resource Server (Bearer token expired or with a wrong value, 401) :``client.config.securityimpl.response.CustomAuthenticationEntryPointImpl``
+ - Resource Server (Permission, 403, @PreAuthorized on your APIs) ``client.config.response.error.GlobalExceptionHandler.authorizationException``
+
+
- **Customize the whole success payload as desired for the only "/oauth2/token"**
- ``client.config.securityimpl.response.CustomAuthenticationSuccessHandlerImpl``
- - The success response payload of "/api/v1/traditional-oauth/token" is in ``api.domain.traditionaloauth.dto``, which doesn't yet to be customizable.
+ - The success response payload of "/api/v1/traditional-oauth/token" is in ``api.domain.traditionaloauth.dto`` and is not yet customizable.
## Running this App with Docker
* Use the following module for Blue-Green deployment:
diff --git a/client/pom.xml b/client/pom.xml
index 4edc622..ba04a61 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -7,7 +7,7 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
com.patternknife.securityhelper.oauth2.client
spring-security-oauth2-password-jpa-implementation-client
- 2.6.0
+ 2.7.0
jar
@@ -41,7 +41,7 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd">
io.github.patternknife.securityhelper.oauth2.api
spring-security-oauth2-password-jpa-implementation
- 2.6.0
+ 2.7.0
diff --git a/client/src/main/java/com/patternknife/securityhelper/oauth2/client/config/response/error/GlobalExceptionHandler.java b/client/src/main/java/com/patternknife/securityhelper/oauth2/client/config/response/error/GlobalExceptionHandler.java
index a032a2f..ede2ed6 100644
--- a/client/src/main/java/com/patternknife/securityhelper/oauth2/client/config/response/error/GlobalExceptionHandler.java
+++ b/client/src/main/java/com/patternknife/securityhelper/oauth2/client/config/response/error/GlobalExceptionHandler.java
@@ -29,8 +29,8 @@
* Customize the exception payload by implementing this, which replaces
* 'io.github.patternknife.securityhelper.oauth2.api.config.security.response.error.handler.SecurityKnifeExceptionHandler'
*
- * Once you create 'GlobalExceptionHandler', you should insert the following two as default. Otherwise, 'unhandledExceptionHandler' is prior to 'io.github.patternknife.securityhelper.oauth2.api.config.security.response.error.handler.SecurityKnifeExceptionHandler'.
- *
+ * Once you create 'GlobalExceptionHandler', you should insert the following two (authenticationException, authorizationException) as default. Otherwise, 'unhandledExceptionHandler' is prior to 'io.github.patternknife.securityhelper.oauth2.api.config.security.response.error.handler.SecurityKnifeExceptionHandler'.
+ * "OrderConstants.SECURITY_KNIFE_EXCEPTION_HANDLER_ORDER - 1" means this is prior to "SecurityKnifeExceptionHandler"
* */
@Order(OrderConstants.SECURITY_KNIFE_EXCEPTION_HANDLER_ORDER - 1)
@ControllerAdvice
diff --git a/client/src/main/java/com/patternknife/securityhelper/oauth2/client/config/securityimpl/response/CustomAccessDeniedHandlerImpl.java b/client/src/main/java/com/patternknife/securityhelper/oauth2/client/config/securityimpl/response/CustomAccessDeniedHandlerImpl.java
deleted file mode 100644
index 7012368..0000000
--- a/client/src/main/java/com/patternknife/securityhelper/oauth2/client/config/securityimpl/response/CustomAccessDeniedHandlerImpl.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.patternknife.securityhelper.oauth2.client.config.securityimpl.response;
-
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import lombok.RequiredArgsConstructor;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.security.access.AccessDeniedException;
-import org.springframework.security.web.access.AccessDeniedHandler;
-import org.springframework.web.servlet.HandlerExceptionResolver;
-
-import java.io.IOException;
-
-@Configuration
-@RequiredArgsConstructor
-public class CustomAccessDeniedHandlerImpl implements AccessDeniedHandler {
-
- @Qualifier("handlerExceptionResolver")
- private final HandlerExceptionResolver resolver;
-
- @Override
- public void handle(HttpServletRequest request, HttpServletResponse response,
- AccessDeniedException accessDeniedException) throws IOException {
-
- resolver.resolveException(request, response, null, accessDeniedException);
- }
-}
diff --git a/client/src/main/java/com/patternknife/securityhelper/oauth2/client/domain/customer/api/CustomerApi.java b/client/src/main/java/com/patternknife/securityhelper/oauth2/client/domain/customer/api/CustomerApi.java
index 58ca38c..440b7dd 100644
--- a/client/src/main/java/com/patternknife/securityhelper/oauth2/client/domain/customer/api/CustomerApi.java
+++ b/client/src/main/java/com/patternknife/securityhelper/oauth2/client/domain/customer/api/CustomerApi.java
@@ -9,6 +9,7 @@
import com.patternknife.securityhelper.oauth2.client.domain.customer.dto.CustomerResDTO;
import com.patternknife.securityhelper.oauth2.client.domain.customer.service.CustomerService;
import com.patternknife.securityhelper.oauth2.client.util.CustomUtils;
+import jakarta.annotation.Nullable;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
@@ -40,8 +41,6 @@ public class CustomerApi {
@GetMapping("/customers/me")
public CustomerResDTO.IdNameWithAccessTokenRemainingSeconds getCustomerSelf(@AuthenticationPrincipal AccessTokenUserInfo accessTokenUserInfo,
@RequestHeader("Authorization") String authorizationHeader) throws ResourceNotFoundException {
-
-
String token = authorizationHeader.substring("Bearer ".length());
int accessTokenRemainingSeconds = 0;
@@ -102,7 +101,12 @@ public Map logoutCustomer(HttpServletRequest request) {
return response;
}
-
+ @PreAuthorize("@resourceServerAuthorityChecker.hasRole('CUSTOMER_ADMIN')")
+ @GetMapping("/customers/{id}")
+ public @Nullable CustomerResDTO.Id getCustomerForAuthorizationTest(@PathVariable final long id)
+ throws ResourceNotFoundException {
+ return null;
+ }
@PreAuthorize("@resourceServerAuthorityChecker.hasRole('CUSTOMER_ADMIN')")
@PutMapping("/customers/{id}")
diff --git a/client/src/main/java/com/patternknife/securityhelper/oauth2/client/domain/customer/service/CustomerService.java b/client/src/main/java/com/patternknife/securityhelper/oauth2/client/domain/customer/service/CustomerService.java
index 1b637e6..fc6cbb4 100644
--- a/client/src/main/java/com/patternknife/securityhelper/oauth2/client/domain/customer/service/CustomerService.java
+++ b/client/src/main/java/com/patternknife/securityhelper/oauth2/client/domain/customer/service/CustomerService.java
@@ -97,13 +97,4 @@ public CustomerResDTO.Id update(Long id, CustomerReqDTO.Update dto) {
}
- public boolean checkIdNameDuplicate(String idName) {
- return customerRepository.existsByIdName(idName);
- }
-
- public boolean checkHpDuplicate(String hp) {
- return customerRepository.existsByHp(CustomUtils.removeSpecialCharacters(hp));
- }
-
-
}
\ No newline at end of file
diff --git a/client/src/test/java/com/patternknife/securityhelper/oauth2/client/integration/auth/CustomerIntegrationTest.java b/client/src/test/java/com/patternknife/securityhelper/oauth2/client/integration/auth/CustomerIntegrationTest.java
index 26b77a2..418fb87 100644
--- a/client/src/test/java/com/patternknife/securityhelper/oauth2/client/integration/auth/CustomerIntegrationTest.java
+++ b/client/src/test/java/com/patternknife/securityhelper/oauth2/client/integration/auth/CustomerIntegrationTest.java
@@ -10,6 +10,8 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
@@ -62,6 +64,9 @@
@AutoConfigureRestDocs(outputDir = "target/generated-snippets",uriScheme = "https", uriHost = "vholic.com", uriPort = 8300)
public class CustomerIntegrationTest {
+ private static final Logger logger = LoggerFactory.getLogger(CustomerIntegrationTest.class);
+
+
@Autowired
private MockMvc mockMvc;
@@ -625,6 +630,93 @@ public void testLoginWithInvalidCredentials_EXPOSE() throws Exception {
assertEquals(userMessage, CustomSecurityUserExceptionMessage.AUTHENTICATION_WRONG_GRANT_TYPE.getMessage());
}
+ @Test
+ public void testFetchResourceWithInvalidCredentialsAndValidCredentialsButWithNoPermission() throws Exception {
+
+ MvcResult result = mockMvc.perform(RestDocumentationRequestBuilders.post("/oauth2/token")
+ .header(HttpHeaders.AUTHORIZATION, basicHeader)
+ .header(KnifeHttpHeaders.APP_TOKEN, "APPTOKENTESTRESOURCE")
+ .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+ .param("grant_type", "password")
+ .param("username", testUserName)
+ .param("password", testUserPassword))
+ .andExpect(status().isOk())
+ .andDo(document( "{class-name}/{method-name}/oauth-access-token",
+ preprocessRequest(new AccessTokenMaskingPreprocessor()),
+ preprocessResponse(new AccessTokenMaskingPreprocessor(), prettyPrint()),
+ requestHeaders(
+ headerWithName(HttpHeaders.AUTHORIZATION).description("Connect the received client_id and client_secret with ':', use the base64 function, and write Basic at the beginning. ex) Basic base64(client_id:client_secret)"),
+ headerWithName(KnifeHttpHeaders.APP_TOKEN).optional().description("Not having a value does not mean you cannot log in, but cases without an App-Token value share the same access_token. Please include it as a required value according to the device-specific session policy.")
+ ),
+ formParameters(
+ parameterWithName("grant_type").description("Uses the password method among Oauth2 grant_types. Please write password."),
+ parameterWithName("username").description("This is the user's email address."),
+ parameterWithName("password").description("This is the user's password.")
+ )))
+ .andReturn();
+
+
+ String responseString = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
+ JSONObject jsonResponse = new JSONObject(responseString);
+ String finalAccessTokenForTestResource = jsonResponse.getString("access_token");
+
+
+
+ result = mockMvc.perform(RestDocumentationRequestBuilders.get("/api/v1/customers/5")
+ .contentType(MediaType.APPLICATION_JSON)
+ .header(HttpHeaders.AUTHORIZATION, "Bearer " + finalAccessTokenForTestResource + "1"))
+ .andDo(document( "{class-name}/{method-name}/customers-id",
+ preprocessRequest(new AccessTokenMaskingPreprocessor()),
+ preprocessResponse(new AccessTokenMaskingPreprocessor(), prettyPrint()),
+ requestHeaders(
+ headerWithName(HttpHeaders.AUTHORIZATION).description("Bearer XXX")
+ )))
+ .andExpect(status().isUnauthorized()).andReturn(); // 401
+
+ responseString = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
+ jsonResponse = new JSONObject(responseString);
+
+
+ String userMessage = jsonResponse.getString("userMessage");
+
+ assertEquals(userMessage, CustomSecurityUserExceptionMessage.AUTHENTICATION_TOKEN_FAILURE.getMessage());
+
+
+
+
+ result = mockMvc.perform(RestDocumentationRequestBuilders.get("/api/v1/customers/5")
+ .contentType(MediaType.APPLICATION_JSON)
+ .header(HttpHeaders.AUTHORIZATION, "Bearer " + finalAccessTokenForTestResource))
+ .andDo(document( "{class-name}/{method-name}/customers-id",
+ preprocessRequest(new AccessTokenMaskingPreprocessor()),
+ preprocessResponse(new AccessTokenMaskingPreprocessor(), prettyPrint()),
+ requestHeaders(
+ headerWithName(HttpHeaders.AUTHORIZATION).description("Bearer XXX")
+ )))
+ .andExpect(status().isForbidden()).andReturn(); // 403
+
+ responseString = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
+ jsonResponse = new JSONObject(responseString);
+ userMessage = jsonResponse.getString("userMessage");
+
+ assertEquals(userMessage, CustomSecurityUserExceptionMessage.AUTHORIZATION_FAILURE.getMessage());
+
+
+ // Remove Access Token DB done tested
+ mockMvc.perform(RestDocumentationRequestBuilders.get("/api/v1/customers/me/logout")
+ .contentType(MediaType.APPLICATION_JSON)
+ .header(HttpHeaders.AUTHORIZATION, "Bearer " + finalAccessTokenForTestResource))
+
+ .andDo(document( "{class-name}/{method-name}/oauth-customer-logout",
+ requestHeaders(
+ headerWithName(HttpHeaders.AUTHORIZATION).description("Bearer XXX")
+ ),relaxedResponseFields(
+ fieldWithPath("logout").description("If true, logout is successful on the backend, if false, it fails. However, ignore this message and, considering UX, delete the token on the client side and move to the login screen.")
+
+ )));
+ }
+
+
private static class AccessTokenMaskingPreprocessor implements OperationPreprocessor {
diff --git a/pom.xml b/pom.xml
index ca53819..9795954 100644
--- a/pom.xml
+++ b/pom.xml
@@ -8,7 +8,7 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd">
io.github.patternknife.securityhelper.oauth2.api
spring-security-oauth2-password-jpa-implementation
- 2.6.0
+ 2.7.0
spring-security-oauth2-password-jpa-implementation
The implementation of Spring Security 6 Spring Authorization Server for stateful OAuth2 Password Grant
jar
@@ -340,7 +340,7 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd">
-