초기소스
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
package com.wb.domainserver.realm;
|
||||
|
||||
public class RealmContextHolder {
|
||||
private static ThreadLocal<String> currentRealm = new ThreadLocal<>();
|
||||
private static ThreadLocal<String> sopmalCdRealm = new ThreadLocal<>();
|
||||
private static ThreadLocal<String> originalHostRealm = new ThreadLocal<>();
|
||||
private static ThreadLocal<String> sysDvCdRealm = new ThreadLocal<>();
|
||||
|
||||
public static void setRealm(String tenant) {
|
||||
currentRealm.set(tenant);
|
||||
}
|
||||
|
||||
public static String getRealm() {
|
||||
return currentRealm.get();
|
||||
}
|
||||
|
||||
public static void setSopmalCdRealm(String tenant) {
|
||||
sopmalCdRealm.set(tenant);
|
||||
}
|
||||
|
||||
public static String getSopmalCdRealm() {
|
||||
return sopmalCdRealm.get();
|
||||
}
|
||||
|
||||
public static void setOriginalHostRealm(String tenant) {
|
||||
originalHostRealm.set(tenant);
|
||||
}
|
||||
|
||||
public static String getOriginalHostRealm() {
|
||||
return originalHostRealm.get();
|
||||
}
|
||||
|
||||
public static void setSysDvCdRealm(String tenant) {
|
||||
sysDvCdRealm.set(tenant);
|
||||
}
|
||||
|
||||
public static String getSysDvCdRealm() {
|
||||
return sysDvCdRealm.get();
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
currentRealm.remove();
|
||||
sopmalCdRealm.remove();
|
||||
originalHostRealm.remove();
|
||||
sysDvCdRealm.remove();
|
||||
}
|
||||
}
|
||||
98
src/main/java/com/wb/domainserver/realm/RealmResolver.java
Normal file
98
src/main/java/com/wb/domainserver/realm/RealmResolver.java
Normal file
@@ -0,0 +1,98 @@
|
||||
package com.wb.domainserver.realm;
|
||||
|
||||
import com.wb.domainserver.exception.WnbtRestException;
|
||||
import com.wb.domainserver.realm.entity.Site;
|
||||
import com.wb.domainserver.realm.exception.RealmNotFoundException;
|
||||
import com.wb.domainserver.realm.repository.SiteRepository;
|
||||
import com.wb.domainserver.realm.util.UrlExtractor;
|
||||
import com.wb.domainserver.util.string.StringUtil;
|
||||
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@Component
|
||||
public class RealmResolver {
|
||||
private static final Logger logger = LoggerFactory.getLogger(RealmResolver.class);
|
||||
|
||||
private final SiteRepository siteRepository;
|
||||
private final ApplicationContext applicationContext;
|
||||
private final ExecutorService executorService = Executors.newCachedThreadPool();
|
||||
|
||||
@Autowired
|
||||
public RealmResolver(SiteRepository siteRepository, ApplicationContext applicationContext) {
|
||||
this.siteRepository = siteRepository;
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public CompletableFuture<Object> resolveRealm(String host) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
String hostUrl;
|
||||
try {
|
||||
hostUrl = Objects.requireNonNull(host).replaceAll("/<unresolved>", "");
|
||||
logger.info("Resolving tenant for host URL: {}", hostUrl);
|
||||
return resolveRealmInternal(hostUrl); // 동기 호출
|
||||
} catch (NullPointerException e) {
|
||||
logger.error("Failed to retrieve host URL from request", e);
|
||||
throw new RealmNotFoundException("Failed to retrieve host URL from request", e);
|
||||
}
|
||||
}, executorService);
|
||||
}
|
||||
|
||||
@Cacheable(cacheNames = "realmCache", key = "#hostUrl")
|
||||
public Object resolveRealmInternal(String hostUrl) {
|
||||
return resolveRealmInternalDirect(hostUrl).join();
|
||||
}
|
||||
|
||||
public CompletableFuture<Object> resolveRealmInternalDirect(String hostUrl) {
|
||||
//url 정제 (로컬, 개발 url 경우 필요)
|
||||
logger.debug("RealmResolver#hostUrl: {}", hostUrl);
|
||||
String finalHostUrl = UrlExtractor.getOriginalHost(hostUrl);
|
||||
logger.debug("RealmResolver#finalHostUrl: {}", finalHostUrl);
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
Optional<Site> siteOptional = siteRepository.findByUrl(finalHostUrl);
|
||||
if (siteOptional.isPresent()) {
|
||||
Site site = siteOptional.get();
|
||||
if(site != null){
|
||||
if (site.getRealm() == null) {
|
||||
logger.error("Tenant not found for site with URL: {}", finalHostUrl);
|
||||
throw new RealmNotFoundException("Tenant not found for site with URL: " + finalHostUrl);
|
||||
}
|
||||
String tenantName = site.getRealm().getName();
|
||||
logger.info("Resolved tenant name: {}, sysDvCd: {}", tenantName, site.getSysDvCd());
|
||||
|
||||
//return tenantName.concat(":").concat(site.getSiteCode());
|
||||
return tenantName.concat(":")
|
||||
.concat(site.getSiteCode())
|
||||
.concat(":")
|
||||
.concat(site.getSysDvCd());
|
||||
}else{
|
||||
throw new RealmNotFoundException("Tenant not found for site with URL: " + finalHostUrl);
|
||||
}
|
||||
} else {
|
||||
logger.error("No site found for URL: {}", finalHostUrl);
|
||||
throw new RealmNotFoundException("No site found for URL: " + finalHostUrl);
|
||||
}
|
||||
} catch (WnbtRestException wre) {
|
||||
throw new WnbtRestException(wre.getMessage());
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to resolve tenant for host URL: {}", finalHostUrl, e);
|
||||
throw new RealmNotFoundException("Failed to resolve tenant for host URL: " + finalHostUrl, e);
|
||||
}
|
||||
}, executorService);
|
||||
}
|
||||
}
|
||||
24
src/main/java/com/wb/domainserver/realm/entity/Realm.java
Normal file
24
src/main/java/com/wb/domainserver/realm/entity/Realm.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package com.wb.domainserver.realm.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Data
|
||||
@Entity
|
||||
public class Realm {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(unique = true)
|
||||
private String name;
|
||||
|
||||
@OneToMany(mappedBy = "realm")
|
||||
private Set<Site> sites;
|
||||
|
||||
// getters, setters
|
||||
}
|
||||
|
||||
|
||||
30
src/main/java/com/wb/domainserver/realm/entity/Site.java
Normal file
30
src/main/java/com/wb/domainserver/realm/entity/Site.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package com.wb.domainserver.realm.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Data
|
||||
@Entity
|
||||
public class Site {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(unique = true)
|
||||
private String siteCode; // This will act as the DISCRIMINATOR
|
||||
|
||||
private String url;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "realm_id")
|
||||
private Realm realm;
|
||||
|
||||
@OneToMany(mappedBy = "site")
|
||||
private Set<User> users;
|
||||
|
||||
private String sysDvCd;
|
||||
|
||||
// getters, setters
|
||||
}
|
||||
21
src/main/java/com/wb/domainserver/realm/entity/User.java
Normal file
21
src/main/java/com/wb/domainserver/realm/entity/User.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.wb.domainserver.realm.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Entity
|
||||
public class User {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "site_id")
|
||||
private Site site;
|
||||
|
||||
// getters, setters
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.wb.domainserver.realm.exception;
|
||||
|
||||
public class MissingRealmException extends RuntimeException{
|
||||
public MissingRealmException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.wb.domainserver.realm.exception;
|
||||
|
||||
public class RealmNotFoundException extends RuntimeException {
|
||||
public RealmNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
public RealmNotFoundException(String message, Exception e) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
103
src/main/java/com/wb/domainserver/realm/filter/RealmFilter.java
Normal file
103
src/main/java/com/wb/domainserver/realm/filter/RealmFilter.java
Normal file
@@ -0,0 +1,103 @@
|
||||
package com.wb.domainserver.realm.filter;
|
||||
|
||||
import com.wb.domainserver.exception.WnbtException;
|
||||
import com.wb.domainserver.exception.code.ErrCode;
|
||||
import com.wb.domainserver.realm.RealmContextHolder;
|
||||
import com.wb.domainserver.realm.RealmResolver;
|
||||
import com.wb.domainserver.realm.util.UrlExtractor;
|
||||
import com.wb.domainserver.util.string.StringUtil;
|
||||
import jakarta.servlet.*;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
@Slf4j
|
||||
public class RealmFilter implements Filter {
|
||||
private static final Logger logger = LoggerFactory.getLogger(RealmFilter.class);
|
||||
|
||||
private final RealmResolver realmResolver;
|
||||
|
||||
public RealmFilter(RealmResolver realmResolver) {
|
||||
this.realmResolver = realmResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) servletRequest;
|
||||
HttpServletResponse response = (HttpServletResponse)servletResponse;
|
||||
logger.info("X-Original-Host: {}", request.getHeader("X-Original-Host"));
|
||||
|
||||
String ExtractorHost = UrlExtractor.getOriginalHost(request);
|
||||
logger.info("Extractor Host: {}", ExtractorHost);
|
||||
|
||||
|
||||
String host = "";
|
||||
if(request.getHeader("X-Original-Host") != null){
|
||||
host = request.getHeader("X-Original-Host");
|
||||
}else{
|
||||
host = ExtractorHost;
|
||||
|
||||
|
||||
response.setHeader("X-Original-Host", ExtractorHost);
|
||||
|
||||
}
|
||||
logger.info("RealmFilter ::: Host ::: {}", host);
|
||||
|
||||
if(!StringUtil.isEmpty(host)) {
|
||||
try {
|
||||
String resolve = (String) realmResolver.resolveRealm(host).get();
|
||||
logger.debug("Resolved realm: {}", resolve);
|
||||
String[] parts = resolve.split(":");
|
||||
String realm = parts[0];
|
||||
String sopmalCd = parts[1];
|
||||
String sysDvCd = parts[2];
|
||||
|
||||
|
||||
//root context 접근 할 경우 처리
|
||||
String reqUri = request.getRequestURI();
|
||||
if(reqUri == null || "/".equals(reqUri) || "/sopmal".equals(reqUri)){
|
||||
|
||||
//사용자 일 경우
|
||||
if("01".contains(sysDvCd)){
|
||||
|
||||
reqUri = "/sopmal/index";
|
||||
|
||||
}else{
|
||||
//관리자 일 경우
|
||||
reqUri = "/mngr";
|
||||
}
|
||||
|
||||
log.debug("mngr redirect from root context ::by realmFilter");
|
||||
request.getRequestDispatcher(reqUri).forward(request, response);
|
||||
|
||||
}
|
||||
|
||||
|
||||
logger.debug("final Resolved realm: {} sopmalCd : {} sysDvCD : {}", realm, sopmalCd, sysDvCd);
|
||||
|
||||
RealmContextHolder.setRealm(realm);
|
||||
RealmContextHolder.setSopmalCdRealm(sopmalCd);
|
||||
RealmContextHolder.setOriginalHostRealm(host);
|
||||
RealmContextHolder.setSysDvCdRealm(sysDvCd);
|
||||
}
|
||||
catch (InterruptedException | ExecutionException e) {
|
||||
logger.error("Error occurred while resolving realm {}", e.getMessage());
|
||||
throw new WnbtException(ErrCode.INTERNAL_SEVER_ERROR);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
// 필터 종료 작업이 필요하면 여기서 수행
|
||||
RealmContextHolder.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.wb.domainserver.realm.interceptor;
|
||||
|
||||
import com.wb.domainserver.realm.RealmContextHolder;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class RealmJwtInterceptor implements HandlerInterceptor {
|
||||
private static final Logger logger = LoggerFactory.getLogger(RealmJwtInterceptor.class);
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
|
||||
logger.info("X-Original-Host: {}", request.getHeader("X-Original-Host"));
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication != null && authentication.getPrincipal() instanceof Jwt jwt) {
|
||||
logger.info("All of claims from JWT: {}", jwt.getClaims());
|
||||
// 요청 속성에 클레임 추가
|
||||
request.setAttribute("claims", jwt.getClaims());
|
||||
} else {
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid token");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
RealmContextHolder.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.wb.domainserver.realm.repository;
|
||||
|
||||
|
||||
import com.wb.domainserver.realm.entity.Realm;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface RealmRepository extends JpaRepository<Realm, Long> {
|
||||
Realm findByName(String name);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.wb.domainserver.realm.repository;
|
||||
|
||||
|
||||
import com.wb.domainserver.realm.entity.Realm;
|
||||
import com.wb.domainserver.realm.entity.Site;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface SiteRepository extends JpaRepository<Site, Long> {
|
||||
// siteCode를 기반으로 tenant를 조회
|
||||
@Query("SELECT s.realm FROM Site s WHERE s.siteCode = :siteCode")
|
||||
Optional<Realm> findRealmBySiteCode(@Param("siteCode") String siteCode);
|
||||
|
||||
@Query("SELECT s.realm FROM Site s WHERE s.url = :url")
|
||||
Optional<Realm> findRealmBySiteUrl(@Param("url") String url);
|
||||
|
||||
@Query("SELECT s FROM Site s WHERE :hostUrl LIKE CONCAT('%', s.url, '%')")
|
||||
Optional<Site> findByUrl(@Param("hostUrl")String hostUrl);
|
||||
}
|
||||
118
src/main/java/com/wb/domainserver/realm/util/UrlExtractor.java
Normal file
118
src/main/java/com/wb/domainserver/realm/util/UrlExtractor.java
Normal file
@@ -0,0 +1,118 @@
|
||||
package com.wb.domainserver.realm.util;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class UrlExtractor {
|
||||
|
||||
public static String redirect(HttpServletRequest request, String url) {
|
||||
return "redirect:" + getOriginalURL(request) + url;
|
||||
}
|
||||
|
||||
public static String getOriginalHost(HttpServletRequest request) {
|
||||
|
||||
// 헤더에서 원래 요청 정보를 추출
|
||||
String scheme = request.getHeader("X-Forwarded-Proto");
|
||||
if (scheme == null) {
|
||||
scheme = request.getScheme(); // 기본값으로 현재 요청의 프로토콜 사용
|
||||
}
|
||||
|
||||
String host = request.getHeader("X-Forwarded-Host");
|
||||
if (host == null) {
|
||||
host = request.getServerName(); // 기본값으로 현재 요청의 호스트명 사용
|
||||
}
|
||||
|
||||
int port = 80;
|
||||
if (request.getHeader("X-Forwarded-Port") == null) {
|
||||
port = request.getServerPort(); // 기본값으로 현재 요청의 포트 사용
|
||||
}
|
||||
|
||||
String portPart = (port != -1 && port != 80 && port != 443) ? ":" + port : "";
|
||||
|
||||
|
||||
String fullUri = scheme + "://" + host;
|
||||
log.info("UrlExtractUtil - fullUri : {}",fullUri);
|
||||
// if(fullUri.contains("it.gcone")) fullUri = fullUri.concat("/admin");
|
||||
|
||||
String returnVal;
|
||||
|
||||
if(fullUri.contains("loc.")){
|
||||
returnVal = scheme + "://" + host + portPart;
|
||||
}else{
|
||||
returnVal = scheme + "://" + host;
|
||||
}
|
||||
|
||||
// 전체 URI 조합
|
||||
returnVal = returnVal.replace(":443", "");
|
||||
log.info("UrlExtractUtil - returnVal : {}",returnVal);
|
||||
|
||||
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
public static String getOriginalURL(HttpServletRequest request) {
|
||||
// 헤더에서 원래 요청 정보를 추출
|
||||
String scheme = request.getHeader("X-Forwarded-Proto");
|
||||
if (scheme == null) {
|
||||
scheme = request.getScheme(); // 기본값으로 현재 요청의 프로토콜 사용
|
||||
}
|
||||
|
||||
String uri = request.getHeader("X-Forwarded-Uri");
|
||||
if (uri == null) {
|
||||
uri = request.getRequestURI(); // 기본값으로 현재 요청의 URI 사용
|
||||
}
|
||||
|
||||
String queryString = request.getHeader("X-Forwarded-Query");
|
||||
if (queryString == null) {
|
||||
queryString = request.getQueryString(); // 기본값으로 현재 요청의 쿼리 스트링 사용
|
||||
}
|
||||
|
||||
|
||||
String url = getOriginalHost(request) + (uri.equals("/") ? "" : uri);
|
||||
|
||||
if (queryString != null) {
|
||||
url += "?" + queryString;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
public static String getOriginalHost(String url) {
|
||||
if (url == null || url.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// 스키마 제거 (http:// or https://)
|
||||
String host = url.replaceFirst("https?://", "");
|
||||
|
||||
// 로컬 혹은 개발 서브도메인 제거
|
||||
if (host.startsWith("loc.")) {
|
||||
host = host.replaceFirst("loc\\.", "");
|
||||
}
|
||||
if (host.startsWith("dev.")) {
|
||||
host = host.replaceFirst("dev\\.", "");
|
||||
}
|
||||
|
||||
// 포트번호 제거
|
||||
int portIndex = host.indexOf(":");
|
||||
if (portIndex != -1) {
|
||||
host = host.substring(0, portIndex);
|
||||
}
|
||||
|
||||
return host;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static String getOriginalHostToHttps(HttpServletRequest request) {
|
||||
String url = getOriginalHost(request);
|
||||
// 스키마 제거 (http:// or https://)
|
||||
String host = url.replaceFirst("https?://", "");
|
||||
String httpsHost = "https://" + host;
|
||||
|
||||
return httpsHost;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user