From c4700af83261061e3634c33f8825f38e6e8f2ba9 Mon Sep 17 00:00:00 2001 From: dirgantarasiahaan Date: Thu, 25 May 2023 15:39:36 +0700 Subject: [PATCH] add api context and logout --- .../controller/AuthenticationController.java | 14 +- .../smartproc/helper/context/ApiContext.java | 50 +++++ .../helper/context/ApiContextFactory.java | 184 ++++++++++++++++++ .../helper/context/AutoConfiguration.java | 21 ++ .../helper/context/ContextProvider.java | 30 +++ .../context/HttpHeadersInterceptor.java | 33 ++++ .../repository/TokenManagementRepository.java | 2 + .../service/authentication/LogoutService.java | 44 +++++ 8 files changed, 377 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/iconplus/smartproc/helper/context/ApiContext.java create mode 100644 src/main/java/com/iconplus/smartproc/helper/context/ApiContextFactory.java create mode 100644 src/main/java/com/iconplus/smartproc/helper/context/AutoConfiguration.java create mode 100644 src/main/java/com/iconplus/smartproc/helper/context/ContextProvider.java create mode 100644 src/main/java/com/iconplus/smartproc/helper/context/HttpHeadersInterceptor.java create mode 100644 src/main/java/com/iconplus/smartproc/service/authentication/LogoutService.java diff --git a/src/main/java/com/iconplus/smartproc/controller/AuthenticationController.java b/src/main/java/com/iconplus/smartproc/controller/AuthenticationController.java index c14e678..438ddd2 100644 --- a/src/main/java/com/iconplus/smartproc/controller/AuthenticationController.java +++ b/src/main/java/com/iconplus/smartproc/controller/AuthenticationController.java @@ -1,10 +1,13 @@ package com.iconplus.smartproc.controller; +import com.iconplus.smartproc.helper.model.EmptyRequest; +import com.iconplus.smartproc.helper.model.EmptyResponse; import com.iconplus.smartproc.model.request.LoginRequest; import com.iconplus.smartproc.model.request.RefreshTokenRequest; import com.iconplus.smartproc.model.response.LoginResponse; import com.iconplus.smartproc.model.response.RefreshTokenResponse; import com.iconplus.smartproc.service.authentication.LoginService; +import com.iconplus.smartproc.service.authentication.LogoutService; import com.iconplus.smartproc.service.authentication.TokenManagementService; import org.springframework.web.bind.annotation.*; @@ -15,11 +18,14 @@ public class AuthenticationController { private LoginService loginService; private TokenManagementService tokenManagementService; + private LogoutService logoutService; public AuthenticationController(LoginService loginService, - TokenManagementService tokenManagementService) { + TokenManagementService tokenManagementService, + LogoutService logoutService) { this.loginService = loginService; this.tokenManagementService = tokenManagementService; + this.logoutService = logoutService; } @PostMapping("/login") @@ -32,4 +38,10 @@ public class AuthenticationController { return tokenManagementService.execute(refreshTokenRequest); } + @PostMapping("/logout") + public EmptyResponse logoutUser(EmptyRequest request) { + return logoutService.execute(request); + + } + } diff --git a/src/main/java/com/iconplus/smartproc/helper/context/ApiContext.java b/src/main/java/com/iconplus/smartproc/helper/context/ApiContext.java new file mode 100644 index 0000000..aa7bd6d --- /dev/null +++ b/src/main/java/com/iconplus/smartproc/helper/context/ApiContext.java @@ -0,0 +1,50 @@ + +package com.iconplus.smartproc.helper.context; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.http.HttpHeaders; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ApiContext { + + private HttpHeaders httpHeaders; + private String userRefId; + private String customerId; + private String otpMobileNo; + private String cid; + private String passportNo; + private String tokenScope; + private String deviceId; + private String boUserFullName; + private String boUserId; + private String authorization; + private String language; + private String requestId; + private String correlationId; + private String forwardedFor; + private String userAgent; + private String platform; + private String clientVersion; + private String channelId; + private String apiKey; + private String sleuthId; + private String userName; + private String userId; + private String smUniversalId; + private String clientId; + private String clientSecret; + private String secretKey; + private String timestamp; + private String mandiriKey; + private String signature; + private String clientIp; + private String releaseId; + private List stackTrace; + private String snapshot; +} diff --git a/src/main/java/com/iconplus/smartproc/helper/context/ApiContextFactory.java b/src/main/java/com/iconplus/smartproc/helper/context/ApiContextFactory.java new file mode 100644 index 0000000..ede7786 --- /dev/null +++ b/src/main/java/com/iconplus/smartproc/helper/context/ApiContextFactory.java @@ -0,0 +1,184 @@ +package com.iconplus.smartproc.helper.context; + +import brave.internal.Platform; +import com.iconplus.smartproc.helper.service.TokenUtils; +import lombok.extern.log4j.Log4j2; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.util.StringUtils; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Log4j2 +public class ApiContextFactory { + + private ApiContextFactory() { + } + + private static final String DEFAULT_LANGUAGE = "id-id"; + private static final String HEADER_AUTHORIZATION = "Authorization"; + private static final String HEADER_ACCEPT_LANGUAGE = HttpHeaders.ACCEPT_LANGUAGE; + private static final String HEADER_REQUEST_ID = "X-Request-ID"; + private static final String HEADER_CORRELATION_ID = "X-Correlation-ID"; + private static final String HEADER_FORWARDED_FOR = "X-Forwarded-For"; + private static final String HEADER_USER_AGENT = "User-Agent"; + private static final String HEADER_PLATFORM = "X-Platform"; + private static final String HEADER_CLIENT_VERSION = "X-Client-Version"; + private static final String HEADER_CHANNEL_ID = "X-Channel-ID"; + private static final String HEADER_API_KEY = "X-API-Key"; + private static final String HEADER_SLEUTH_ID = "X-Sleuth-ID"; + private static final String HEADER_USER_NAME = "X-User-Name"; + private static final String HEADER_USER_ID = "X-User-ID"; + private static final String HEADER_SM_UNIVERSAL_ID = "sm_universalId"; + private static final String HEADER_CUSTOMER_ID = "X-Customer-ID"; + private static final String HEADER_CLIENT_ID = "X-Client-ID"; + private static final String HEADER_CLIENT_SECRET = "X-Client-Secret"; + private static final String HEADER_SECRET_KEY = "X-Secret-Key"; + private static final String HEADER_TIMESTAMP = "X-TIMESTAMP"; + private static final String HEADER_MANDIRI_KEY = "X-MANDIRI-KEY"; + private static final String HEADER_SIGNATURE = "X-SIGNATURE"; + private static final String HEADER_RELEASE_ID = "X-Release-ID"; + + private static final String JWT_USER_ID = "user_id"; + private static final String JWT_CUSTOMER_ID = "customer_id"; + private static final String JWT_OTP_MOBILE_NO = "otpMobileNo"; + private static final String JWT_CID = "cid"; + private static final String JWT_PASSPORT_NO = "passportNo"; + private static final String JWT_LOGIN_SCOPE = "scope"; + private static final String JWT_DEVICE_ID = "device_id"; + private static final String JWT_CLIENT_ID = "clientId"; + private static final String JWT_BO_USER_FULL_NAME = "boUserFullName"; + private static final String JWT_BO_USER_ID = "boUserId"; + + private static final String X_FORWARDED_FOR = "x-forwarded-for"; + private static final String PROXY_CLIENT_IP = "proxy-client-ip"; + private static final String WL_PROXY_CLIENT_IP = "wl-proxy-client-ip"; + private static final String HTTP_CLIENT_IP = "http_client_ip"; + private static final String HTTP_X_FORWARDED_FOR = "http_x_forwarded_for"; + private static final String UNKNOWN = "unknown"; + + private static final String CLIENT_IP = "clientIp"; + + private static final List ALLOW_HEADER_LIST = List + .of(HEADER_AUTHORIZATION, HEADER_ACCEPT_LANGUAGE, HEADER_REQUEST_ID, + HEADER_CORRELATION_ID, HEADER_FORWARDED_FOR, HEADER_USER_AGENT, HEADER_PLATFORM, + HEADER_CLIENT_VERSION, HEADER_CHANNEL_ID, HEADER_API_KEY, JWT_DEVICE_ID, HEADER_SLEUTH_ID, HEADER_USER_NAME, + HEADER_USER_ID, HEADER_SM_UNIVERSAL_ID, HEADER_CUSTOMER_ID, HEADER_CLIENT_ID, HEADER_CLIENT_SECRET, HEADER_SECRET_KEY, + HEADER_TIMESTAMP, HEADER_MANDIRI_KEY, HEADER_SIGNATURE, HEADER_RELEASE_ID).stream() + .map(String::toLowerCase).collect(Collectors.toList()); + + public static ApiContext generateApiContext() throws IOException { + ApiContext apiContext = new ApiContext(); + + HttpHeaders httpHeaders = constructHttpHeaders(); + String platform = httpHeaders.getFirst(HEADER_PLATFORM); + String authentication = httpHeaders.getFirst(HEADER_AUTHORIZATION); + + if (authentication != null) { + authentication = authentication.replace("Bearer", ""); + authentication = authentication.trim(); + } + + + if (authentication != null) { + Map jwtBodyMap = TokenUtils.decodeToken(authentication); + apiContext.setUserId(jwtBodyMap.getOrDefault(JWT_USER_ID, "")); + apiContext.setCustomerId(jwtBodyMap.getOrDefault(JWT_CUSTOMER_ID, "")); + apiContext.setDeviceId(jwtBodyMap.getOrDefault(JWT_DEVICE_ID, "")); + apiContext.setTokenScope(jwtBodyMap.getOrDefault(JWT_LOGIN_SCOPE, "")); + apiContext.setClientId(jwtBodyMap.getOrDefault(JWT_CLIENT_ID, "")); + } + + if(StringUtils.isEmpty(apiContext.getClientId())) { + apiContext.setClientId(httpHeaders.getFirst(HEADER_CLIENT_ID)); + } + + apiContext.setHttpHeaders(httpHeaders); + apiContext.setAuthorization(authentication); + apiContext.setLanguage(httpHeaders.getFirst(HEADER_ACCEPT_LANGUAGE)); + apiContext.setRequestId(httpHeaders.getFirst(HEADER_REQUEST_ID)); + apiContext.setCorrelationId(httpHeaders.getFirst(HEADER_CORRELATION_ID)); + apiContext.setForwardedFor(httpHeaders.getFirst(HEADER_FORWARDED_FOR)); + apiContext.setUserAgent(httpHeaders.getFirst(HEADER_USER_AGENT)); + apiContext.setPlatform(httpHeaders.getFirst(HEADER_PLATFORM)); + apiContext.setClientVersion(httpHeaders.getFirst(HEADER_CLIENT_VERSION)); + apiContext.setChannelId(httpHeaders.getFirst(HEADER_CHANNEL_ID)); + apiContext.setSleuthId(httpHeaders.getFirst(HEADER_SLEUTH_ID)); + apiContext.setApiKey(httpHeaders.getFirst(HEADER_API_KEY)); + apiContext.setClientSecret(httpHeaders.getFirst(HEADER_CLIENT_SECRET)); + apiContext.setSecretKey(httpHeaders.getFirst(HEADER_SECRET_KEY)); + apiContext.setTimestamp(httpHeaders.getFirst(HEADER_TIMESTAMP)); + apiContext.setMandiriKey(httpHeaders.getFirst(HEADER_MANDIRI_KEY)); + apiContext.setSignature(httpHeaders.getFirst(HEADER_SIGNATURE)); + apiContext.setClientIp(httpHeaders.getFirst(CLIENT_IP)); + apiContext.setReleaseId(httpHeaders.getFirst(HEADER_RELEASE_ID)); + + //fill the username with user id if its empty, because AD changed the design and they remove the x-user-name + String userName = httpHeaders.getFirst(HEADER_USER_NAME); + if (userName == null || userName.equals("")) { + userName = httpHeaders.getFirst(HEADER_USER_ID); + } + apiContext.setUserName(userName); + + return apiContext; + } + + public static HttpHeaders constructHttpHeaders() throws IOException { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + HttpServletRequest curRequest = ((ServletRequestAttributes) RequestContextHolder + .currentRequestAttributes()).getRequest(); + Enumeration headerNames = curRequest.getHeaderNames(); + + if (headerNames != null) { + while (headerNames.hasMoreElements()) { + String header = headerNames.nextElement(); + String value = curRequest.getHeader(header); + if (ALLOW_HEADER_LIST.contains(header)) { + log.debug("Adding header {} with value {}", header, value); + httpHeaders.add(header, value); + } else { + log.debug("Header {} with value {} is not required to be copied", header, + value); + } + } + } + + if (!httpHeaders.containsKey(HEADER_ACCEPT_LANGUAGE) || StringUtils + .isEmpty(httpHeaders.getFirst( + HEADER_ACCEPT_LANGUAGE))) { + httpHeaders.set(HEADER_ACCEPT_LANGUAGE, DEFAULT_LANGUAGE); + } + + httpHeaders.add(CLIENT_IP, getClientIp(httpHeaders, curRequest.getRemoteAddr())); + + return httpHeaders; + } + + public static String getClientIp(HttpHeaders httpHeaders, String remoteAddr) { + String ip = httpHeaders.getFirst(X_FORWARDED_FOR); + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = httpHeaders.getFirst(PROXY_CLIENT_IP); + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = httpHeaders.getFirst(WL_PROXY_CLIENT_IP); + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = httpHeaders.getFirst(HTTP_CLIENT_IP); + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = httpHeaders.getFirst(HTTP_X_FORWARDED_FOR); + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = remoteAddr; + } + return ip; + } +} diff --git a/src/main/java/com/iconplus/smartproc/helper/context/AutoConfiguration.java b/src/main/java/com/iconplus/smartproc/helper/context/AutoConfiguration.java new file mode 100644 index 0000000..e91c5af --- /dev/null +++ b/src/main/java/com/iconplus/smartproc/helper/context/AutoConfiguration.java @@ -0,0 +1,21 @@ +package com.iconplus.smartproc.helper.context; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.context.annotation.RequestScope; + +import java.io.IOException; + +@Configuration +public class AutoConfiguration { + + @RequestScope + @Bean + @ConditionalOnMissingBean(ApiContext.class) + public ApiContext apiContext() throws IOException { + return ApiContextFactory.generateApiContext(); + } + + +} diff --git a/src/main/java/com/iconplus/smartproc/helper/context/ContextProvider.java b/src/main/java/com/iconplus/smartproc/helper/context/ContextProvider.java new file mode 100644 index 0000000..599c9fc --- /dev/null +++ b/src/main/java/com/iconplus/smartproc/helper/context/ContextProvider.java @@ -0,0 +1,30 @@ +package com.iconplus.smartproc.helper.context; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +public class ContextProvider implements ApplicationContextAware { + + private static ApplicationContext CONTEXT; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + CONTEXT = applicationContext; + } + + /** + * Get a Spring bean by type. + **/ + public static T getBean(Class beanClass) { + return CONTEXT.getBean(beanClass); + } + + /** + * Get a Spring bean by name. + **/ + public static Object getBean(String beanName) { + return CONTEXT.getBean(beanName); + } + +} \ No newline at end of file diff --git a/src/main/java/com/iconplus/smartproc/helper/context/HttpHeadersInterceptor.java b/src/main/java/com/iconplus/smartproc/helper/context/HttpHeadersInterceptor.java new file mode 100644 index 0000000..9cdc799 --- /dev/null +++ b/src/main/java/com/iconplus/smartproc/helper/context/HttpHeadersInterceptor.java @@ -0,0 +1,33 @@ +package com.iconplus.smartproc.helper.context; + +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Log4j2 +@Component +public class HttpHeadersInterceptor implements ClientHttpRequestInterceptor { + + @Autowired + private ApiContext apiContext; + + public HttpHeadersInterceptor(ApiContext apiContext) { + this.apiContext = apiContext; + } + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, + ClientHttpRequestExecution execution) throws IOException { + apiContext.getHttpHeaders().entrySet().stream() + .filter(x -> x.getKey() != null) + .forEach(x -> request.getHeaders().add(x.getKey(), x.getValue().get(0))); + return execution.execute(request, body); + } + +} diff --git a/src/main/java/com/iconplus/smartproc/repository/TokenManagementRepository.java b/src/main/java/com/iconplus/smartproc/repository/TokenManagementRepository.java index a0d1c86..c598f8b 100644 --- a/src/main/java/com/iconplus/smartproc/repository/TokenManagementRepository.java +++ b/src/main/java/com/iconplus/smartproc/repository/TokenManagementRepository.java @@ -23,5 +23,7 @@ public interface TokenManagementRepository extends JpaRepository findByRefreshToken(String refreshToken); + Optional findByAccessTokenAndIsDeleteFalse(String accessToken); + Optional findByUserId(Long id); } diff --git a/src/main/java/com/iconplus/smartproc/service/authentication/LogoutService.java b/src/main/java/com/iconplus/smartproc/service/authentication/LogoutService.java new file mode 100644 index 0000000..823b5a5 --- /dev/null +++ b/src/main/java/com/iconplus/smartproc/service/authentication/LogoutService.java @@ -0,0 +1,44 @@ +package com.iconplus.smartproc.service.authentication; + +import com.iconplus.smartproc.exception.BusinessException; +import com.iconplus.smartproc.helper.context.ApiContext; +import com.iconplus.smartproc.helper.model.EmptyRequest; +import com.iconplus.smartproc.helper.model.EmptyResponse; +import com.iconplus.smartproc.helper.service.BaseService; +import com.iconplus.smartproc.model.entity.TokenManagement; +import com.iconplus.smartproc.repository.TokenManagementRepository; +import com.iconplus.smartproc.repository.UsersRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class LogoutService implements BaseService { + + private ApiContext apiContext; + private TokenManagementRepository tokenManagementRepository; + private LogoutService(ApiContext apiContext, + TokenManagementRepository tokenManagementRepository) { + this.apiContext = apiContext; + this.tokenManagementRepository = tokenManagementRepository; + } + + @Override + public EmptyResponse execute(EmptyRequest input) { + + String accessToken = apiContext.getAuthorization(); + TokenManagement tokenManagement = getTokenManagement(accessToken); + tokenManagement.setIsDelete(true); + tokenManagementRepository.save(tokenManagement); + return new EmptyResponse(); + } + + private TokenManagement getTokenManagement(String accessToken) { + var tokenManagement = tokenManagementRepository.findByAccessTokenAndIsDeleteFalse(accessToken); + if (tokenManagement.isEmpty()) { + throw new BusinessException("err", "err", "err"); + } + return tokenManagement.get(); + } +}