Skip to content

Commit

Permalink
feature : Create more selective pointcuts (refer to README)
Browse files Browse the repository at this point in the history
  • Loading branch information
patternknife committed Jul 18, 2024
1 parent d8503dc commit f542bcb
Show file tree
Hide file tree
Showing 53 changed files with 430 additions and 215 deletions.
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
<dependency>
<groupId>io.github.patternknife.securityhelper.oauth2.api</groupId>
<artifactId>spring-security-oauth2-password-jpa-implementation</artifactId>
<version>2.5.0</version>
<version>2.6.0</version>
</dependency>
```
* 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`.
* 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.)
* As you are aware, the API ``/oauth2/token`` is what "spring-authorization-server" provides.
* ``/api/v1/traditional-oauth/token`` is what this library implemented manually.
* ``/api/v1/traditional-oauth/token`` is what this library implemented directly.
* Success Payload
```json
{
Expand Down Expand Up @@ -129,10 +129,18 @@ public class CommonDataSourceConfiguration {
- **Register error user messages as desired**
- ``ISecurityUserExceptionMessageService``
- See the source code in ``client.config.securityimpl.message``
- **Customize the whole error payload as desired**
- Customize only two points in
- ``client.config.securityimpl.errorhandler.CustomAuthenticationFailureHandlerImpl``
- ``client.config.response.error.GlobalExceptionHandler``
- **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 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.

## Running this App with Docker
* Use the following module for Blue-Green deployment:
* https://github.com/patternknife/docker-blue-green-runner
Expand Down
4 changes: 2 additions & 2 deletions client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.patternknife.securityhelper.oauth2.client</groupId>
<artifactId>spring-security-oauth2-password-jpa-implementation-client</artifactId>
<version>2.5.0</version>
<version>2.6.0</version>
<packaging>jar</packaging>

<properties>
Expand Down Expand Up @@ -41,7 +41,7 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd">
<dependency>
<groupId>io.github.patternknife.securityhelper.oauth2.api</groupId>
<artifactId>spring-security-oauth2-password-jpa-implementation</artifactId>
<version>2.5.0</version>
<version>2.6.0</version>
</dependency>

<!-- DB -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
import com.patternknife.securityhelper.oauth2.client.config.response.error.dto.CustomErrorResponsePayload;

import com.patternknife.securityhelper.oauth2.client.config.response.error.message.GeneralErrorMessage;
import io.github.patternknife.securityhelper.oauth2.api.config.response.error.ExceptionKnifeUtils;
import io.github.patternknife.securityhelper.oauth2.api.config.response.error.dto.ErrorResponsePayload;
import io.github.patternknife.securityhelper.oauth2.api.config.response.error.exception.auth.KnifeOauth2AuthenticationException;
import io.github.patternknife.securityhelper.oauth2.api.config.security.response.error.handler.SecurityKnifeExceptionHandler;
import io.github.patternknife.securityhelper.oauth2.api.config.security.response.error.util.ExceptionKnifeUtils;
import io.github.patternknife.securityhelper.oauth2.api.config.security.response.error.dto.ErrorResponsePayload;
import io.github.patternknife.securityhelper.oauth2.api.config.security.response.error.exception.KnifeOauth2AuthenticationException;
import io.github.patternknife.securityhelper.oauth2.api.config.security.message.DefaultSecurityUserExceptionMessage;
import io.github.patternknife.securityhelper.oauth2.api.config.security.message.ISecurityUserExceptionMessageService;
import io.github.patternknife.securityhelper.oauth2.api.config.security.util.OrderConstants;
import lombok.RequiredArgsConstructor;;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
Expand All @@ -23,11 +27,12 @@
/*
*
* Customize the exception payload by implementing this, which replaces
* 'io.github.patternknife.securityhelper.oauth2.api.config.response.error.SecurityKnifeExceptionHandler'
* '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.response.error.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'.
*
* */
@Order(OrderConstants.SECURITY_KNIFE_EXCEPTION_HANDLER_ORDER - 1)
@ControllerAdvice
@RequiredArgsConstructor
public class GlobalExceptionHandler {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@


import com.fasterxml.jackson.annotation.JsonIgnore;
import io.github.patternknife.securityhelper.oauth2.api.config.response.TimestampUtil;
import io.github.patternknife.securityhelper.oauth2.api.config.response.error.dto.ErrorMessages;
import io.github.patternknife.securityhelper.oauth2.api.config.util.TimestampUtil;
import io.github.patternknife.securityhelper.oauth2.api.config.security.response.error.dto.ErrorMessages;

import lombok.ToString;
import org.apache.commons.lang3.StringUtils;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.patternknife.securityhelper.oauth2.client.config.response.error.exception;

import io.github.patternknife.securityhelper.oauth2.api.config.response.error.dto.ErrorMessages;
import io.github.patternknife.securityhelper.oauth2.api.config.security.response.error.dto.ErrorMessages;

public abstract class ErrorMessagesContainedException extends RuntimeException {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.patternknife.securityhelper.oauth2.client.config.response.error.exception.data;

import io.github.patternknife.securityhelper.oauth2.api.config.response.error.dto.ErrorMessages;
import io.github.patternknife.securityhelper.oauth2.api.config.security.response.error.dto.ErrorMessages;
import com.patternknife.securityhelper.oauth2.client.config.response.error.exception.ErrorMessagesContainedException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@


import io.github.patternknife.securityhelper.oauth2.api.config.security.aop.SecurityPointCut;
import io.github.patternknife.securityhelper.oauth2.api.config.security.entity.CustomOauthAccessToken;
import io.github.patternknife.securityhelper.oauth2.api.config.security.entity.CustomOauthRefreshToken;
import io.github.patternknife.securityhelper.oauth2.api.config.security.entity.OauthClientDetail;
import io.github.patternknife.securityhelper.oauth2.api.config.security.entity.KnifeOauthAccessToken;
import io.github.patternknife.securityhelper.oauth2.api.config.security.entity.KnifeOauthRefreshToken;
import io.github.patternknife.securityhelper.oauth2.api.config.security.entity.KnifeOauthClientDetail;

import jakarta.annotation.Nullable;
import lombok.RequiredArgsConstructor;
Expand All @@ -16,7 +16,7 @@
public class SecurityPointCutImpl implements SecurityPointCut {

@Override
public <T> @Nullable T afterTokensSaved(@Nullable CustomOauthAccessToken customOauthAccessToken, @Nullable CustomOauthRefreshToken customOauthRefreshToken, @Nullable OauthClientDetail oauthClientDetail) {
public <T> @Nullable T afterTokensSaved(@Nullable KnifeOauthAccessToken knifeOauthAccessToken, @Nullable KnifeOauthRefreshToken knifeOauthRefreshToken, @Nullable KnifeOauthClientDetail knifeOauthClientDetail) {

// Implement what you need right after tokens are persisted.
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.web.servlet.HandlerExceptionResolver;

import java.io.IOException;

@Configuration
@RequiredArgsConstructor
public class CustomAuthenticationEntryPointImpl implements AuthenticationEntryPoint {

@Qualifier("handlerExceptionResolver")
private final HandlerExceptionResolver resolver;

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException ex) throws IOException {
resolver.resolveException(request, response, null, ex);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.patternknife.securityhelper.oauth2.client.config.securityimpl.errorhandler;
package com.patternknife.securityhelper.oauth2.client.config.securityimpl.response;


import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.patternknife.securityhelper.oauth2.api.config.logger.module.CustomSecurityLogConfig;
import io.github.patternknife.securityhelper.oauth2.api.config.response.error.ExceptionKnifeUtils;
import io.github.patternknife.securityhelper.oauth2.api.config.response.error.dto.ErrorResponsePayload;
import io.github.patternknife.securityhelper.oauth2.api.config.response.error.exception.auth.KnifeOauth2AuthenticationException;
import io.github.patternknife.securityhelper.oauth2.api.config.logger.KnifeSecurityLogConfig;
import io.github.patternknife.securityhelper.oauth2.api.config.security.message.DefaultSecurityUserExceptionMessage;
import io.github.patternknife.securityhelper.oauth2.api.config.security.message.ISecurityUserExceptionMessageService;
import io.github.patternknife.securityhelper.oauth2.api.config.security.response.error.dto.ErrorResponsePayload;
import io.github.patternknife.securityhelper.oauth2.api.config.security.response.error.exception.KnifeOauth2AuthenticationException;
import io.github.patternknife.securityhelper.oauth2.api.config.security.response.error.util.ExceptionKnifeUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
Expand All @@ -19,21 +19,20 @@
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;


import java.io.IOException;


/*
*
* Customize the exception payload by implementing this, which replaces
* 'io.github.patternknife.securityhelper.oauth2.api.security.errorhandler.auth.authentication.DefaultAuthenticationFailureHandlerImpl'
*
* */
*
* Customize the exception payload by implementing this, which replaces
* 'io.github.patternknife.securityhelper.oauth2.api.security.response.auth.authentication.CustomAuthenticationFailureHandlerImpl'
*
* */
@Configuration
@RequiredArgsConstructor
public class CustomAuthenticationFailureHandlerImpl implements AuthenticationFailureHandler {

private static final Logger logger = LoggerFactory.getLogger(CustomSecurityLogConfig.class);
private static final Logger logger = LoggerFactory.getLogger(KnifeSecurityLogConfig.class);

private final ISecurityUserExceptionMessageService iSecurityUserExceptionMessageService;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.patternknife.securityhelper.oauth2.client.config.securityimpl.response;


import io.github.patternknife.securityhelper.oauth2.api.config.security.message.DefaultSecurityUserExceptionMessage;
import io.github.patternknife.securityhelper.oauth2.api.config.security.message.ISecurityUserExceptionMessageService;
import io.github.patternknife.securityhelper.oauth2.api.config.security.response.error.dto.ErrorMessages;
import io.github.patternknife.securityhelper.oauth2.api.config.security.response.error.exception.KnifeOauth2AuthenticationException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import java.io.IOException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Map;

@Configuration
@RequiredArgsConstructor
public class CustomAuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {

private final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter =
new OAuth2AccessTokenResponseHttpMessageConverter();

private final ISecurityUserExceptionMessageService iSecurityUserExceptionMessageService;

@Override
public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication) throws IOException {


final OAuth2AccessTokenAuthenticationToken accessTokenAuthentication=(OAuth2AccessTokenAuthenticationToken)authentication;

OAuth2AccessToken accessToken = accessTokenAuthentication.getAccessToken();
OAuth2RefreshToken refreshToken = accessTokenAuthentication.getRefreshToken();
Map<String, Object> additionalParameters = accessTokenAuthentication.getAdditionalParameters();
/* Map<String, Object> additionalParameters = accessTokenAuthentication.getAdditionalParameters();
// Lookup the authorization using the access token
OAuth2Authorization authorization = this.authorizationService.findByToken(
accessToken.getTokenValue(), OAuth2TokenType.ACCESS_TOKEN);
Map<String, Object> opaqueTokenClaims = authorization.getAccessToken().getClaims();
Authentication userPrincipal = authorization.getAttribute(Principal.class.getName());*/

OAuth2AccessTokenResponse.Builder builder =
OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
.tokenType(accessToken.getTokenType())
.scopes(accessToken.getScopes());
if(((String)additionalParameters.get("grant_type")).equals(AuthorizationGrantType.PASSWORD.getValue())){
if(accessToken.getExpiresAt() != null) {
builder.expiresIn(ChronoUnit.SECONDS.between(Instant.now(), accessToken.getExpiresAt()));
}
}else if(((String)additionalParameters.get("grant_type")).equals(AuthorizationGrantType.REFRESH_TOKEN.getValue())){
assert refreshToken != null;
if(refreshToken.getExpiresAt() != null) {
builder.expiresIn(ChronoUnit.SECONDS.between(Instant.now(), refreshToken.getExpiresAt()));
}
}else{
throw new KnifeOauth2AuthenticationException(ErrorMessages.builder().message("Wrong grant type from Req : " + (String)additionalParameters.get("grant_type")).userMessage(iSecurityUserExceptionMessageService.getUserMessage(DefaultSecurityUserExceptionMessage.AUTHENTICATION_WRONG_GRANT_TYPE)).build());
}


if (refreshToken != null) {
builder.refreshToken(refreshToken.getTokenValue());
}
/* if (!CollectionUtils.isEmpty(additionalParameters)) {
builder.additionalParameters(additionalParameters);
}*/

// TODO Add custom response parameters using `opaqueTokenClaims` and/or `userPrincipal`


OAuth2AccessTokenResponse accessTokenResponse = builder.build();
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
this.accessTokenHttpResponseConverter.write(accessTokenResponse, null, httpResponse);
}
}
Loading

0 comments on commit f542bcb

Please sign in to comment.