초기소스

This commit is contained in:
jazz
2025-10-29 17:08:05 +09:00
parent e531aa9540
commit 439a379a02
7551 changed files with 1234427 additions and 3 deletions

View File

@@ -0,0 +1,27 @@
package com.wb.domainserver.security.authentication;
import lombok.Data;
@Data
public class AnonymousUser{
private String id;
private String mbrId;
private String sopmalCd;
private String mbrDvCd;
private int userInnb = 0;
private String mbrNm;
private String mbtlnb;
private String accessUrl;
private String clientId;
private String originalHost;
private String sysDvCd;
private String mngrDv;
// 인트라넷
private String tentGroup; // 입점 그룹
private String reprsntBrand; // 대표브랜드
private String strOperDv; // 대표브랜드
private String tentcpnNm; // 입점사명
private String itrntDv; //인트라넷 구분
}

View File

@@ -0,0 +1,25 @@
package com.wb.domainserver.security.authentication;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
@Slf4j
public class CustomAccessDeniedEntryPoint implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {
// 인증된 사용자가 접근했을 때 루트 페이지로 리다이렉트
response.sendRedirect("/");
}
}

View File

@@ -0,0 +1,39 @@
package com.wb.domainserver.security.authentication;
import com.nimbusds.jose.util.StandardCharset;
import com.wb.domainserver.realm.RealmContextHolder;
import com.wb.domainserver.realm.util.UrlExtractor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.net.URLEncoder;
@Slf4j
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException{
log.debug("authException.getMessage() {}", authException.getMessage());
log.debug("request.getRequestURI() {}", request.getRequestURI());
log.debug("getAuthentication() {}", SecurityContextHolder.getContext().getAuthentication());
String realm = RealmContextHolder.getRealm().toLowerCase();
//엑세스토큰 만료시에 refresh 토큰 검증 요청
if(!request.getRequestURI().contains("/tokens/refresh")){
response.sendRedirect("/"+realm+"/tokens/refresh?redirect_uri=%s".formatted(URLEncoder.encode(UrlExtractor.getOriginalURL(request), StandardCharset.UTF_8)));
}
}
}

View File

@@ -0,0 +1,12 @@
package com.wb.domainserver.security.authentication;
//@Component
//public class MemberJwtAuthenticationConverter
// extends SimpleAuthenticationConverter
//{
// @Override
// public AbstractAuthenticationToken convert(Jwt jwt, Collection<GrantedAuthority> authorities) {
// return new MemberJwtToken(jwt, authorities);
// }
//}

View File

@@ -0,0 +1,51 @@
package com.wb.domainserver.security.authentication;
import com.wb.domainserver.realm.RealmContextHolder;
import com.wb.domainserver.security.user.CustomAuthenticationToken;
import com.wb.domainserver.security.user.SecurityUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.context.SecurityContextHolder;
@Slf4j
public class MemberJwtContextHolder {
public static AnonymousUser getMemberInfo() {
AnonymousUser vo = new AnonymousUser();
var authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication instanceof CustomAuthenticationToken customAuthenticationToken) {
SecurityUser users = (SecurityUser) customAuthenticationToken.getPrincipal();
vo.setId(users.getId());
vo.setMbrId(users.getMbrId());
vo.setSopmalCd(users.getSopmalCd());
vo.setMbrDvCd(users.getMbrDvCd());
vo.setUserInnb((int) users.getUserInnb());
vo.setMbrNm(users.getUsername());
vo.setMbtlnb(users.getMbtlnb());
vo.setAccessUrl(users.getAccessUrl());
vo.setClientId(users.getClientId());
vo.setOriginalHost(RealmContextHolder.getOriginalHostRealm());
vo.setSysDvCd(RealmContextHolder.getSysDvCdRealm());
vo.setMngrDv(users.getMngrDv());
// 인트라넷
vo.setTentGroup(users.getTentGroup());
vo.setReprsntBrand(users.getReprsntBrand());
vo.setStrOperDv(users.getStrOperDv());
vo.setTentcpnNm(users.getTentcpnNm());
vo.setItrntDv(users.getItrntDv());
}else {
vo.setSopmalCd(RealmContextHolder.getSopmalCdRealm());
vo.setOriginalHost(RealmContextHolder.getOriginalHostRealm());
vo.setSysDvCd(RealmContextHolder.getSysDvCdRealm());
}
return vo;
}
}

View File

@@ -0,0 +1,57 @@
package com.wb.domainserver.security.authentication;
import io.jsonwebtoken.Claims;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
@Getter
public class MemberJwtToken {
private final String id; //f:300c3290-6f54-418d-b35e-3ad39b674937:2041:02:it.gcone.co.kr
private final String mbrId;
private final String sopmalCd;
private final String mbrDvCd;
private final int userInnb;
private final String mbrNm;
private final String mbtlnb;
private final String accessUrl; //it.gcone.co.kr
private final String clientId; //account
private final String sysDvCd;
private final String mngrDv;
private final List<String> roleType;
//인트라넷 정보
private final String tentGroup;
private final String reprsntBrand;
private final String strOperDv;
private final String tentcpnNm;
private final String itrntDv;
public MemberJwtToken(Claims jwt) {
this.id = jwt.get("id", String.class);
this.mbrId = jwt.get("mbrId", String.class);
this.sopmalCd = jwt.get("sopmalCd", String.class);
this.mbrDvCd = jwt.get("mbrDvCd", String.class);
this.userInnb = Integer.parseInt(jwt.get("userInnb", String.class));
this.mbrNm = jwt.get("mbrNm", String.class);
this.mbtlnb = jwt.get("mbtlnb", String.class);
this.accessUrl = jwt.get("accessUrl", String.class);
this.clientId = jwt.get("clientId", String.class);
this.sysDvCd = jwt.get("sysDvCd", String.class);
this.roleType = jwt.get("roleType", List.class);
this.mngrDv = jwt.get("mngrDv", String.class);
// 인트라넷 정보
this.tentGroup = jwt.get("tentGroup", String.class);
this.reprsntBrand = jwt.get("reprsntBrand", String.class);
this.strOperDv = jwt.get("strOperDv", String.class);
this.tentcpnNm = jwt.get("tentcpnNm", String.class);
this.itrntDv = jwt.get("itrntDv", String.class);
}
}

View File

@@ -0,0 +1,19 @@
package com.wb.domainserver.security.authentication;
//import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
//@Slf4j
//public abstract class SimpleAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {
// private final JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
//
// @Override
// public AbstractAuthenticationToken convert(@NonNull Jwt jwt) {
// AbstractAuthenticationToken token = jwtAuthenticationConverter.convert(jwt);
// Collection<GrantedAuthority> authorities = token.getAuthorities();
//
// return convert(jwt, authorities);
// }
//
// public abstract AbstractAuthenticationToken convert(Jwt jwt, Collection<GrantedAuthority> authorities);
//}

View File

@@ -0,0 +1,90 @@
package com.wb.domainserver.security.authorization;
import com.wb.domainserver.security.authentication.AnonymousUser;
import com.wb.domainserver.security.authentication.MemberJwtContextHolder;
import com.wb.domainserver.security.authorization.annotation.AuthPolicy;
import com.wb.domainserver.security.authorization.annotation.Policy;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Optional;
import java.util.function.Supplier;
@Component
@Slf4j
public class WnbtAuthorizationManager<T> implements AuthorizationManager<MethodInvocation> {
private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation object) {
boolean grantedFlag = true;
if (hasAuthentication(authentication.get())) {
Policy policyAnnotation = AnnotationUtils.findAnnotation(object.getMethod(), Policy.class);
grantedFlag = Optional.ofNullable(policyAnnotation)
.map(Policy::value).filter(
policy -> (policy == AuthPolicy.PUBLIC) || (policy == AuthPolicy.RESTRICTED && hasAccessToRestrictedPolicy(MemberJwtContextHolder.getMemberInfo(), object))
).isPresent();
}
log.debug("# [WnbtAuthorizationManager] AuthorizationDecision check ::: {}", grantedFlag);
return new AuthorizationDecision(grantedFlag);
}
private boolean hasAuthentication(Authentication authentication) {
return authentication != null && isNotAnonymous(authentication) && authentication.isAuthenticated();
}
private boolean isNotAnonymous(Authentication authentication) {
return !this.trustResolver.isAnonymous(authentication);
}
private boolean hasAccessToRestrictedPolicy(AnonymousUser tokenClaim, MethodInvocation object){
RequestMapping classRequestMapping = object.getMethod().getDeclaringClass().getAnnotation(RequestMapping.class);
var classPath = Optional.ofNullable(classRequestMapping).map(RequestMapping::value).orElseGet(()->new String[0]);
GetMapping getMapping = object.getMethod().getAnnotation(GetMapping.class);
PostMapping postMapping = object.getMethod().getAnnotation(PostMapping.class);
RequestMapping requestMapping = object.getMethod().getAnnotation(RequestMapping.class);
var path = Optional.ofNullable(getMapping).map(GetMapping::value).orElse(
Optional.ofNullable(postMapping).map(PostMapping::value).orElse(
Optional.ofNullable(requestMapping).map(RequestMapping::value).orElseGet(()->new String[0])));
log.debug("###################################################################################");
log.debug("# [hasAccessToRestrictedPolicy] user's Authority ::: {} ", tokenClaim.getUserInnb());
log.debug("# [hasAccessToRestrictedPolicy] user's Authority ::: {} ", tokenClaim.getMbrDvCd());
log.debug("# [hasAccessToRestrictedPolicy] user's Authority ::: {} ", tokenClaim.getMbrId());
log.debug("# [hasAccessToRestrictedPolicy] user's Authority ::: {} ", tokenClaim.getMbrNm());
log.debug("# [hasAccessToRestrictedPolicy] requestMapping path ::: {} ", (Object) path);
log.debug("# [hasAccessToRestrictedPolicy] classpath ::: {} ", (Object) classPath);
log.debug("###################################################################################");
// List<String> pathList = securityUserDetailService.getAvailablePathList(user);
// log.debug("##########pathList######## = {}",pathList);
// if(!pathList.isEmpty()) {
// for (Object pa : pathList) {
// String test = "/mng/goods/realgoods/goodsList34.do";
// // test -> path 로 변경
// if(pa.equals(test)){
// log.debug("test 통과@#@@#!#@!#@!#!@##@!");
// return true;
// }
// }
// }
// return false;
return true;
}
}

View File

@@ -0,0 +1,5 @@
package com.wb.domainserver.security.authorization.annotation;
public enum AuthPolicy {
RESTRICTED, PUBLIC
}

View File

@@ -0,0 +1,13 @@
package com.wb.domainserver.security.authorization.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
@Target(METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Policy {
AuthPolicy value();
}

View File

@@ -0,0 +1,139 @@
package com.wb.domainserver.security.config;
import com.nhncorp.psinfra.toolkit.SimpleCryptLib;
import com.wb.domainserver.interceptor.mngr.MngrAuthInterceptor;
import com.wb.domainserver.interceptor.mngr.MngrLoggingInterceptor;
import com.wb.domainserver.interceptor.mngr.MngrMenuInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {
"com.wb.domainserver.domain.mngr"
, "com.wb.domainserver.domain.admin"
, "com.wb.domainserver.interceptor.mngr"
, "com.wb.domainserver.util.file"
// , "com.wb.domainserver.domain.mq.controller" /*ERP batch Controller 위치*/
// , "com.wb.domainserver.batch.controller" /*batch Controller*/
})
public class AdminWebMvcConfig implements WebMvcConfigurer {
private static final String[] ADMIN_INTERCEPTOR_PATH = {"/mngr/**", "/admin/**","/mngr","/admin"};
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if(!registry.hasMappingForPattern("/webjars/**")){
registry.addResourceHandler("/webjars/**")
.addResourceLocations("/webjars/").resourceChain(false);
}
if(!registry.hasMappingForPattern("/**")){
registry.addResourceHandler("/**")
.addResourceLocations(
"classpath:/static",
"classpath:/static/admin",
"classpath:/static/assets",
"classpath:/static/ckeditor",
"classpath:/static/crm",
"classpath:/static/daumeditor",
"classpath:/static/intranet",
"classpath:/static/itrnt",
"classpath:/static/jchartfx",
"classpath:/static/js",
"classpath:/static/login",
"classpath:/static/mngr",
"classpath:/static/css"
).resourceChain(false);
}
if(!registry.hasMappingForPattern("/**.html")){
registry.addResourceHandler("/**.html")
.addResourceLocations(
"classpath:/templates/"
).resourceChain(false);
}
}
@Bean
public SimpleCryptLib simpleCryptLib() {
return new SimpleCryptLib();
}
/**
* 메뉴 처리 bean 선언
*/
@Bean
public MngrMenuInterceptor mngrMenuInterceptor() {
return new MngrMenuInterceptor();
}
/**
* logging 처리 bean 선언
*/
@Bean
public MngrLoggingInterceptor mngrLoggingInterceptor() {
return new MngrLoggingInterceptor();
}
/**
* auth 처리 bean 선언
*/
@Bean
public MngrAuthInterceptor mngrAuthInterceptor() {
return new MngrAuthInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
MngrLoggingInterceptor loggingInterceptor = mngrLoggingInterceptor();
registry.addInterceptor(loggingInterceptor).addPathPatterns(ADMIN_INTERCEPTOR_PATH)
.order(1)
.excludePathPatterns("/itrnt/mbr/selectMbrJoinForm")
.excludePathPatterns("/itrnt/ntcn/mssage/insertPosMbrSend")
.excludePathPatterns("/order/tm_settle")
.excludePathPatterns("/order/insertTmOrder")
.excludePathPatterns("/order/selectTmOrdrInfo")
.excludePathPatterns("/crm/ordr/insertTossKeyInSetleTm")
.excludePathPatterns("/crm/ordr/insertKcpSetleTm")
.excludePathPatterns("/crm/ordr/insertKcpKeyInSetleTm")
.excludePathPatterns("/crm/ordr/insertTossSetleTm")
.excludePathPatterns("/crm/ordr/insertSetleTm")
.excludePathPatterns("/comm/itrnt/include", "/WEB-INF/views/**", "/**/css/**", "/**/images/**", "/**/js/**", "/**/fonts/**", "/**/*.js", "/**/*.css", "/error", "/**/*.jpg", "/**/*.png", "/**/*.gif", "/favicon.ico");
MngrAuthInterceptor authInterceptor = mngrAuthInterceptor();
registry.addInterceptor(authInterceptor).addPathPatterns(ADMIN_INTERCEPTOR_PATH)
.order(2)
.excludePathPatterns("/itrnt/mbr/selectMbrJoinForm")
.excludePathPatterns("/itrnt/ntcn/mssage/insertPosMbrSend")
.excludePathPatterns("/order/tm_settle")
.excludePathPatterns("/order/insertTmOrder")
.excludePathPatterns("/order/selectTmOrdrInfo")
.excludePathPatterns("/crm/ordr/insertTossKeyInSetleTm")
.excludePathPatterns("/crm/ordr/insertKcpSetleTm")
.excludePathPatterns("/crm/ordr/insertKcpKeyInSetleTm")
.excludePathPatterns("/crm/ordr/insertTossSetleTm")
.excludePathPatterns("/crm/ordr/insertSetleTm")
.excludePathPatterns("/mngr", "/mngr/login", "/mngr/tokens/refresh", "/mngr/login/page/mngr","/mngr/login/loginCheck","/mngr/login/mngr","/mngr/login/insertPhoneAuth" , "/mngr/login/selectPhoneAuth", "/mngr/login/loginCheck")
.excludePathPatterns("/admin", "/admin/login", "/admin/tokens/refresh", "/admin/login/page/admin","/admin/login/loginCheck","/admin/login/admin","/admin/login/insertPhoneAuth" , "/admin/login/selectPhoneAuth", "/admin/login/loginCheck")
.excludePathPatterns("/comm/itrnt/include", "/WEB-INF/views/**", "/**/css/**", "/**/images/**", "/**/js/**", "/**/fonts/**", "/**/*.js", "/**/*.css", "/error", "/**/*.jpg", "/**/*.png", "/**/*.gif", "/favicon.ico", "/mngr/vendor/chart.js/chart.umd.js.map", "/ckeditor/ckeditor.js.map");
MngrMenuInterceptor menuInterceptor = mngrMenuInterceptor();
registry.addInterceptor(menuInterceptor).addPathPatterns(ADMIN_INTERCEPTOR_PATH)
.order(3)
.excludePathPatterns("/itrnt/mbr/selectMbrJoinForm")
.excludePathPatterns("/itrnt/ntcn/mssage/insertPosMbrSend")
.excludePathPatterns("/order/tm_settle")
.excludePathPatterns("/order/insertTmOrder")
.excludePathPatterns("/order/selectTmOrdrInfo")
.excludePathPatterns("/crm/ordr/insertTossKeyInSetleTm")
.excludePathPatterns("/crm/ordr/insertKcpSetleTm")
.excludePathPatterns("/crm/ordr/insertKcpKeyInSetleTm")
.excludePathPatterns("/crm/ordr/insertTossSetleTm")
.excludePathPatterns("/crm/ordr/insertSetleTm")
.excludePathPatterns("/comm/itrnt/include", "/WEB-INF/views/**", "/**/css/**", "/**/images/**", "/**/js/**", "/**/fonts/**", "/**/*.js", "/**/*.css", "/error", "/**/*.jpg", "/**/*.png", "/**/*.gif", "/favicon.ico");
}
}

View File

@@ -0,0 +1,63 @@
package com.wb.domainserver.security.config;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.wb.domainserver.realm.RealmResolver;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableCaching
public class CacheConfig {
private final ApplicationContext applicationContext;
public CacheConfig(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Bean
public CaffeineCacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(caffeineCacheBuilder());
cacheManager.setCacheLoader(cacheLoader());
return cacheManager;
}
@Bean
public Caffeine<Object, Object> caffeineCacheBuilder() {
return Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(100);
}
@Bean
public CacheLoader<Object, Object> cacheLoader() {
return new CacheLoader<>() {
@Override
public Object load(Object key) {
return loadAsync(key).join();
}
@Override
public CompletableFuture<Object> asyncReload(Object key, Object oldValue, Executor executor) {
return loadAsync(key);
}
private CompletableFuture<Object> loadAsync(Object key) {
String hostUrl = (String) key;
RealmResolver realmResolver = applicationContext.getBean(RealmResolver.class);
return realmResolver.resolveRealmInternalDirect(hostUrl);
}
};
}
}

View File

@@ -0,0 +1,71 @@
package com.wb.domainserver.security.config;
import com.wb.domainserver.common.msg.GconeMessageSource;
import com.wb.domainserver.common.ui.pagination.DefaultPaginationManager;
import com.wb.domainserver.common.ui.pagination.ImagePaginationRenderer;
import com.wb.domainserver.common.ui.pagination.PaginationRenderer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class CommWebMvcConfig {
/**
* image pagination 처리를 위한 renderer set
* @return ImagePaginationRenderer image pagination renderer
*/
@Bean
public ImagePaginationRenderer imageRenderer() {
return new ImagePaginationRenderer();
}
/**
* 기본 페이징 처리 bean 선언
* @return DefaultPaginationManager 기본 페이징 처리 bean
*/
@Bean
public DefaultPaginationManager paginationManager() {
DefaultPaginationManager defaultPaginationManager = new DefaultPaginationManager();
Map<String, PaginationRenderer> rendererType = new HashMap<>();
rendererType.put("image", imageRenderer());
defaultPaginationManager.setRendererType(rendererType);
return defaultPaginationManager;
}
@Bean
public ViewResolver getViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setViewClass(JstlView.class);
return resolver;
}
@Bean
public ReloadableResourceBundleMessageSource messageSource() {
ReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource = new ReloadableResourceBundleMessageSource();
reloadableResourceBundleMessageSource.setBasenames(
"classpath:/message/messages");
reloadableResourceBundleMessageSource.setCacheSeconds(60);
return reloadableResourceBundleMessageSource;
}
@Bean
public GconeMessageSource gconeMessageSource() {
GconeMessageSource gconeMessageSource = new GconeMessageSource();
gconeMessageSource.setReloadableResourceBundleMessageSource(messageSource());
return gconeMessageSource;
}
}

View File

@@ -0,0 +1,26 @@
package com.wb.domainserver.security.config;
import com.wb.domainserver.realm.RealmResolver;
import com.wb.domainserver.realm.filter.RealmFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
@Configuration
public class RealmFilterConfig {
@Autowired
RealmResolver realmResolver;
@Bean
public FilterRegistrationBean<RealmFilter> realmFilter() {
FilterRegistrationBean<RealmFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new RealmFilter(realmResolver));
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registrationBean;
}
}

View File

@@ -0,0 +1,224 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wb.domainserver.security.config;
import com.wb.domainserver.domain.sopmal.oauth.service.CustomOAuth2UserService;
import com.wb.domainserver.exception.response.CustomOauthFailureException;
import com.wb.domainserver.realm.RealmResolver;
import com.wb.domainserver.realm.filter.RealmFilter;
import com.wb.domainserver.response.CustomAuthenticationSuccessHandler;
import com.wb.domainserver.security.authentication.CustomAccessDeniedEntryPoint;
import com.wb.domainserver.security.authentication.CustomAuthenticationEntryPoint;
import com.wb.domainserver.security.authorization.WnbtAuthorizationManager;
import com.wb.domainserver.security.filter.CustomXssFilter;
import com.wb.domainserver.security.filter.JwtAuthenticationFilter;
import com.wb.domainserver.security.handler.CustomLogoutSuccessHandler;
import com.wb.domainserver.security.user.SecurityUserDetailService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.Advisor;
import org.springframework.aop.support.JdkRegexpMethodPointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.core.annotation.Order;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import java.util.List;
import java.util.stream.Collectors;
import static org.springframework.beans.factory.config.BeanDefinition.ROLE_INFRASTRUCTURE;
@EnableWebSecurity(debug = true)
@Configuration
//@EnableMethodSecurity
public class ResourceServerConfig {
private static final Logger logger = LoggerFactory.getLogger(ResourceServerConfig.class);
@Autowired
private RealmResolver realmResolver;
@Autowired
private SecurityUserDetailService userDetailService;
@Autowired
private CustomAuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private CustomAccessDeniedEntryPoint accessDeniedEntryPoint;
@Autowired
private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
@Autowired
private CustomOAuth2UserService customOAuth2UserService;
@Bean
@Role(ROLE_INFRASTRUCTURE)
public Advisor authorizationManagerBeforeMethodInterception(WnbtAuthorizationManager<?> authorizationManager) {
JdkRegexpMethodPointcut pattern = new JdkRegexpMethodPointcut();
pattern.setPattern("com.wb.domainserver.*.controller.*");
return new AuthorizationManagerBeforeMethodInterceptor(pattern, authorizationManager);
}
@Bean
@Order(1)
SecurityFilterChain sopmalSecurityFilterChain(HttpSecurity http, SecurityPathProperties securityProperties) throws Exception {
RequestMatcher logoutRequestMatcher = new OrRequestMatcher(
new AntPathRequestMatcher("/sopmal/logout")
);
//사용자 권한
List<RequestMatcher> anonymousPath = securityProperties.getSolmal().getAnonmymous().stream()
.map(AntPathRequestMatcher::new)
.collect(Collectors.toList());
List<RequestMatcher> authPath = securityProperties.getSolmal().getAuthenticated().stream()
.map(AntPathRequestMatcher::new)
.collect(Collectors.toList());
List<RequestMatcher> resourcePath = securityProperties.getCommon().getResources().stream()
.map(AntPathRequestMatcher::new)
.collect(Collectors.toList());
OrRequestMatcher anonymoushMatchers = new OrRequestMatcher(anonymousPath);
OrRequestMatcher authMatchers = new OrRequestMatcher(authPath);
OrRequestMatcher resourceMatchers = new OrRequestMatcher(resourcePath);
return http
.csrf(AbstractHttpConfigurer::disable)
.securityMatcher("/sopmal/**", "/oauth2/**", "/login/oauth2/**")
.authorizeHttpRequests(authorize ->
authorize
.requestMatchers(authMatchers).authenticated()
.requestMatchers(anonymoushMatchers).anonymous()
.requestMatchers(resourceMatchers).permitAll()
.requestMatchers(
new AntPathRequestMatcher("/oauth2/**"),
new AntPathRequestMatcher("/login/oauth2/**")
).permitAll()
.anyRequest().permitAll()
)
.oauth2Login(oauth2 -> oauth2 //sns oauth
.loginPage("/sopmal/login")
.successHandler(customAuthenticationSuccessHandler) //인증성공시 로그인 혹은 연동처리
.userInfoEndpoint(userInfo -> userInfo //인증처리
.userService(customOAuth2UserService)
)
.failureHandler(new CustomOauthFailureException()) //인증 실패시
)
.addFilterAfter(new CustomXssFilter(), RequestCacheAwareFilter.class)
.addFilterBefore(new JwtAuthenticationFilter(userDetailService), LogoutFilter.class)
.addFilterAfter(new RealmFilter(realmResolver), HeaderWriterFilter.class)
.exceptionHandling(exceptions -> exceptions
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedEntryPoint)
).logout(logout
-> logout.logoutRequestMatcher(logoutRequestMatcher)
.deleteCookies("JSESSIONID")
.logoutSuccessHandler(new CustomLogoutSuccessHandler())
)
.build();
}
@Bean
@Order(2)
SecurityFilterChain adminSecurityFilterChain(HttpSecurity http, SecurityPathProperties securityProperties) throws Exception {
// 설정된 로그아웃 URL
RequestMatcher logoutRequestMatcher = new OrRequestMatcher(
new AntPathRequestMatcher("/mngr/logout"),
new AntPathRequestMatcher("/admin/logout"),
new AntPathRequestMatcher("/crm/logout")
);
List<RequestMatcher> anonmyPath = securityProperties.getAdmin().getAnonmymous().stream()
.map(AntPathRequestMatcher::new)
.collect(Collectors.toList());
List<RequestMatcher> authPath = securityProperties.getAdmin().getAuthenticated().stream()
.map(AntPathRequestMatcher::new)
.collect(Collectors.toList());
List<RequestMatcher> resourcePath = securityProperties.getCommon().getResources().stream()
.map(AntPathRequestMatcher::new)
.collect(Collectors.toList());
OrRequestMatcher authMatchers = new OrRequestMatcher(authPath);
OrRequestMatcher resourceMatchers = new OrRequestMatcher(resourcePath);
return http
.csrf(AbstractHttpConfigurer::disable)
.securityMatcher("/mngr/**","/mngr","/admin/**","/admin","/admin/**","/crm/**","/itrnt/**", logoutRequestMatcher.toString()) //"/*/mq/**", "/api/batch/schedulerloader/**", 제외
.authorizeHttpRequests(authorize ->
authorize
// .requestMatchers(AntPathRequestMatcher.antMatcher("/*/mq/**")).anonymous() //ERP 호출 허용을 위해
// .requestMatchers(AntPathRequestMatcher.antMatcher("/api/batch/schedulerloader/**")).anonymous() //batch 스케줄 동적기동
.requestMatchers(AntPathRequestMatcher.antMatcher("/admin")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/mngr")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/mngr/login/**")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/admin/login/**")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/mngr/tokens/**")).anonymous()
.requestMatchers(AntPathRequestMatcher.antMatcher("/admin/tokens/**")).anonymous()
.requestMatchers(resourceMatchers).permitAll()
.requestMatchers(authMatchers).authenticated()
.anyRequest().permitAll()
)
.addFilterBefore(new JwtAuthenticationFilter(userDetailService), LogoutFilter.class)
.addFilterAfter(new RealmFilter(realmResolver), HeaderWriterFilter.class)
.exceptionHandling(exceptions -> exceptions
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedEntryPoint)
).logout(logout
-> logout.logoutRequestMatcher(logoutRequestMatcher)
.deleteCookies("JSESSIONID")
.logoutSuccessHandler(new CustomLogoutSuccessHandler())
)
.build();
}
//
// private String customBearerTokenResolver(HttpServletRequest request) {
// String authorization = request.getHeader("Authorization");
// if (StringUtils.hasText(authorization) && authorization.startsWith("Bearer ")) {
// return authorization.substring(7);
// }
// return null;
// }
//
// private String determineIssuerUri(String token) {
// return String.format(issuerUri+"/%s", RealmContextHolder.getRealm());
// }
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@@ -0,0 +1,43 @@
package com.wb.domainserver.security.config;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import java.util.List;
@Getter
@Setter
@Component
@PropertySource(value = "classpath:/secured-config.yml", factory = SecurityPathPropertySourceFactory.class)
@ConfigurationProperties(prefix = "secured.path")
public class SecurityPathProperties {
private Solmal solmal;
private Admin admin;
private Common common;
@Getter
@Setter
public static class Solmal {
private List<String> anonmymous;
private List<String> authenticated;
}
@Getter
@Setter
public static class Admin {
private List<String> anonmymous;
private List<String> authenticated;
}
@Getter
@Setter
public static class Common {
private List<String> resources;
}
}

View File

@@ -0,0 +1,35 @@
package com.wb.domainserver.security.config;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;
public class SecurityPathPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
Properties propertiesFromYaml = loadYamlIntoProperties(resource);
String sourceName = name != null ? name : resource.getResource().getFilename();
return new PropertiesPropertySource(sourceName, propertiesFromYaml);
}
private Properties loadYamlIntoProperties(EncodedResource resource) throws FileNotFoundException {
try {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return factory.getObject();
} catch (IllegalStateException e) {
// for ignoreResourceNotFound
Throwable cause = e.getCause();
if (cause instanceof FileNotFoundException)
throw (FileNotFoundException) e.getCause();
throw e;
}
}
}

View File

@@ -0,0 +1,14 @@
package com.wb.domainserver.security.config;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TomcatWebCustomConfig implements WebServerFactoryCustomizer<TomcatServletWebServerFactory>{
@Override
public void customize(TomcatServletWebServerFactory factory) {
factory.addConnectorCustomizers(connector -> connector.setProperty("relaxedQueryChars", "[]"));
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2020-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wb.domainserver.security.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient(WebClient.Builder builder) {
builder.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(-1));
return builder.build();
}
}

View File

@@ -0,0 +1,17 @@
package com.wb.domainserver.security.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowedHeaders("*")
.exposedHeaders("Authorization");
}
}

View File

@@ -0,0 +1,132 @@
package com.wb.domainserver.security.config;
import com.wb.domainserver.interceptor.sopmal.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {
"com.wb.domainserver.domain.sopmal"
, "com.wb.domainserver.domain.log"
, "com.wb.domainserver.interceptor.sopmal"})
public class WebMvcConfig implements WebMvcConfigurer {
private static final String[] SOPMAL_INTERCEPTOR_PATH = {"/","/index","/sopmal/**"};
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if(!registry.hasMappingForPattern("/webjars/**")){
registry.addResourceHandler("/webjars/**")
.addResourceLocations("/webjars/").resourceChain(false);
}
if(!registry.hasMappingForPattern("/**")){
registry.addResourceHandler("/**")
.addResourceLocations(
"classpath:/static",
"classpath:/static/js",
"classpath:/static/img",
"classpath:/static/login",
"classpath:/static/css").resourceChain(false);
}
if(!registry.hasMappingForPattern("/**.html")){
registry.addResourceHandler("/**.html")
.addResourceLocations(
"classpath:/templates/"
).resourceChain(false);
}
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
DeviceHandlerInterceptor deviceHandlerInterceptor = deviceHandlerInterceptor();
registry.addInterceptor(deviceHandlerInterceptor).addPathPatterns();
LoggingInterceptor loggingInterceptor = loggingInterceptor();
registry.addInterceptor(loggingInterceptor).addPathPatterns(SOPMAL_INTERCEPTOR_PATH)
.order(1)
.excludePathPatterns("/**/css/**", "/**/images/**", "/**/js/**", "/**/fonts/**", "/**/*.js", "/**/*.css", "/error", "/**/*.jpg", "/**/*.png", "/**/*.gif");
OperInterceptor operInterceptor = operInterceptor();
registry.addInterceptor(operInterceptor).addPathPatterns(SOPMAL_INTERCEPTOR_PATH)
.order(2)
.excludePathPatterns("/**/css/**", "/**/images/**", "/**/js/**", "/**/fonts/**", "/**/*.js", "/**/*.css", "/error", "/**/*.jpg", "/**/*.png", "/**/*.gif", "/oper");
LoginChkInterceptor loginChkInterceptor = loginChkInterceptor();
registry.addInterceptor(loginChkInterceptor).addPathPatterns(SOPMAL_INTERCEPTOR_PATH)
.order(3)
.excludePathPatterns("/**/css/**", "/**/images/**", "/**/js/**", "/**/fonts/**", "/**/*.js", "/**/*.css", "/error", "/**/*.jpg", "/**/*.png", "/**/*.gif");
AuthInterceptor authInterceptor = authInterceptor();
registry.addInterceptor(authInterceptor).addPathPatterns(SOPMAL_INTERCEPTOR_PATH)
.order(4)
.excludePathPatterns("/**/css/**", "/**/images/**", "/**/js/**", "/**/fonts/**", "/**/*.js", "/**/*.css", "/error", "/**/*.jpg", "/**/*.png", "/**/*.gif");
MenuInterceptor menuInterceptor = menuInterceptor();
registry.addInterceptor(menuInterceptor).addPathPatterns(SOPMAL_INTERCEPTOR_PATH)
.order(5)
.excludePathPatterns("/**/css/**", "/**/images/**", "/**/js/**", "/**/fonts/**", "/**/*.js", "/**/*.css", "/error", "/**/*.jpg", "/**/*.png", "/**/*.gif");
FooterInterceptor footerInterceptor = footerInterceptor();
registry.addInterceptor(footerInterceptor).addPathPatterns(SOPMAL_INTERCEPTOR_PATH)
.order(6)
.excludePathPatterns("/**/css/**", "/**/images/**", "/**/js/**", "/**/fonts/**", "/**/*.js", "/**/*.css", "/error", "/**/*.jpg", "/**/*.png", "/**/*.gif");
}
@Bean
public DeviceHandlerInterceptor deviceHandlerInterceptor() {
return new DeviceHandlerInterceptor();
}
/**
* 메뉴 처리 bean 선언
*/
@Bean
public MenuInterceptor menuInterceptor() {
return new MenuInterceptor();
}
/**
* footer 처리 bean 선언
*/
@Bean
public FooterInterceptor footerInterceptor() {
return new FooterInterceptor();
}
// /**
// * logging 처리 bean 선언
// */
@Bean
public LoggingInterceptor loggingInterceptor() {
return new LoggingInterceptor();
}
// /**
// * 사용자 권한 처리 bean 선언
// */
@Bean
public AuthInterceptor authInterceptor() {
return new AuthInterceptor();
}
// /**
// *쇼핑몰 운영 처리 bean 선언
// */
@Bean
public OperInterceptor operInterceptor() {
return new OperInterceptor();
}
// /**
// * 로그인 확인 bean 선언
// */
@Bean
public LoginChkInterceptor loginChkInterceptor() {
return new LoginChkInterceptor();
}
}

View File

@@ -0,0 +1,25 @@
package com.wb.domainserver.security.filter;
import com.wb.domainserver.security.filter.wrapper.XssServletWrapper;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.util.ContentCachingResponseWrapper;
import java.io.IOException;
@Slf4j
public class CustomXssFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//들어온 HttpRequest 분해하여 xss 필터링
HttpServletRequestWrapper requestWrapper = new XssServletWrapper((HttpServletRequest) request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper((HttpServletResponse) response);
chain.doFilter(requestWrapper, responseWrapper);
responseWrapper.copyBodyToResponse();
}
}

View File

@@ -0,0 +1,122 @@
package com.wb.domainserver.security.filter;
import com.nimbusds.jose.util.StandardCharset;
import com.wb.domainserver.realm.RealmContextHolder;
import com.wb.domainserver.realm.util.UrlExtractor;
import com.wb.domainserver.security.user.CustomAuthenticationToken;
import com.wb.domainserver.security.user.SecurityUserDetailService;
import com.wb.domainserver.util.CookieUtils;
import io.jsonwebtoken.JwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final SecurityUserDetailService customUserDetailService;
public JwtAuthenticationFilter(SecurityUserDetailService customUserDetailService) {
this.customUserDetailService = customUserDetailService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, AuthenticationException {
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
String realm = RealmContextHolder.getRealm().toLowerCase();
try {
String requestUri = request.getRequestURI();
log.debug("request uri: {}", requestUri);
String accessToken = extractToken(request);
String sysDvCd = RealmContextHolder.getSysDvCdRealm();
String expireChk = request.getParameter("expire");
String tokenExpireCookie = CookieUtils.getCookieValue("token_expire", request);
//사용자이면서 ACT가 없는데 tokenExpire는 있을 경우
//로그인된 사용자지만 엑세스 토큰이 만료되어 다시 재발급 받아야 하는 대상
//expire값은 루프 요청 방지
if("01".equals(sysDvCd) && !StringUtils.hasText(accessToken) && StringUtils.hasText(tokenExpireCookie) && !"p".equals(expireChk)){
response.sendRedirect("/"+realm+"/tokens/refresh?redirect_uri=%s&expire=%s".formatted(URLEncoder.encode(UrlExtractor.getOriginalURL(request), StandardCharset.UTF_8), "p"));
return;
}
// accessToken이 있고 refresh Token 요청이 아닌경우
if (StringUtils.hasText(accessToken) && !requestUri.contains("/tokens/refresh")) {
UserDetails userDetails = customUserDetailService.loadUserByUsername(accessToken);
if (userDetails != null) {
log.debug("user details: {}", userDetails);
log.debug("user details authorizeList: {}", userDetails.getAuthorities());
setAuthentication(userDetails);
}
}
filterChain.doFilter(request, response);
} catch (JwtException je){
log.error("jwt access token error: {}", je.getMessage());
Map<String, String> loginCookiePath = CookieUtils.loginCookiePath();
//로그인 관련 쿠키 삭제
CookieUtils.deleteCookie("ACT", loginCookiePath.get("ACTPath"), request, response);
CookieUtils.deleteCookie("RFT", loginCookiePath.get("RFTPath"), request, response);
CookieUtils.deleteCookie("token_expire", loginCookiePath.get("ACTPath"), request, response);
response.sendRedirect("/"+ realm + "/logout");
}
}
private String extractToken(HttpServletRequest request) {
String accessToken = "";
if(request.getHeader(HttpHeaders.AUTHORIZATION) != null){
accessToken = request.getHeader(HttpHeaders.AUTHORIZATION).replaceFirst("Bearer ", "");
}
else{
if(request.getCookies() != null){
for(Cookie cookie : request.getCookies()){
if("ACT".equals(cookie.getName()) && StringUtils.hasText(cookie.getValue())){
accessToken = cookie.getValue();
}
}
}
}
return accessToken;
}
private void setAuthentication(UserDetails userDetails) {
SecurityContextHolder.getContext().setAuthentication(
new CustomAuthenticationToken(userDetails, userDetails.getAuthorities())
);
}
}

View File

@@ -0,0 +1,23 @@
package com.wb.domainserver.security.filter;
import com.wb.domainserver.interceptor.wrapper.MultipleReadableRequestWrapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingResponseWrapper;
import java.io.IOException;
@Component
public class LoggingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
MultipleReadableRequestWrapper requestWrapper = new MultipleReadableRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
filterChain.doFilter(requestWrapper, responseWrapper);
responseWrapper.copyBodyToResponse();
}
}

View File

@@ -0,0 +1,171 @@
package com.wb.domainserver.security.filter.wrapper;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.util.IOUtils;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.regex.Pattern;
@Slf4j
public class XssServletWrapper extends HttpServletRequestWrapper {
private byte[] rawData;
public XssServletWrapper(HttpServletRequest request) {
super(request);
// log.info("xssServletWrapper start");
try {
//reqeust를 inputStream으로 바이트 변환하여 XSS 필터링
if(request.getMethod().equalsIgnoreCase("post")) {
// log.info("xssServletWrapper ing");
InputStream is = request.getInputStream();
this.rawData = replaceXSS(IOUtils.toByteArray(is));
}
// log.info("xssServletWrapper end");
} catch (IOException e) {
log.error("xssServletWrapper err {}",e.getMessage());
}
}
//바이트 단위 script 태그 치환
private byte[] replaceXSS(byte[] data) {
if(data != null) {
// log.info("xssFilter replace Byte IN ");
String strData = new String(data);
data = replaceStrXss(strData).getBytes();
}
return data;
}
//문자열 단위 script 태그 치환
private String replaceXSS(String value) {
if(value != null) {
// log.info("xssFilter replace String IN ");
value = replaceStrXss(value);
}
// log.debug("returnValue :: {}", value);
return value;
}
@Override
public ServletInputStream getInputStream() throws IOException {
if(this.rawData == null) {
return super.getInputStream();
}
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.rawData);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public boolean isReady() {
return false;
}
@Override
public boolean isFinished() {
return false;
}
};
}
@Override
public String getQueryString() {
return replaceXSS(super.getQueryString());
}
@Override
public String getParameter(String name) {
return replaceXSS(super.getParameter(name));
}
@Override
public Map<String, String[]> getParameterMap() {
Map<String, String[]> params = super.getParameterMap();
if(params != null) {
params.forEach((key, value) -> {
for(int i=0; i<value.length; i++) {
value[i] = replaceXSS(value[i]);
}
});
}
return params;
}
@Override
public String[] getParameterValues(String name) {
String[] params = super.getParameterValues(name);
if(params != null) {
for(int i=0; i<params.length; i++) {
params[i] = replaceXSS(params[i]);
}
}
return params;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream(), StandardCharsets.UTF_8));
}
private String replaceStrXss(String value){
// Avoid anything between script tags
Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid anything in a src='...' type of expression
scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
// Remove any lonesome </script> tag
scriptPattern = Pattern.compile("</script>", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
// Remove any lonesome <script ...> tag
scriptPattern = Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid eval(...) expressions
scriptPattern = Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid expression(...) expressions
scriptPattern = Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid javascript:... expressions
scriptPattern = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid vbscript:... expressions
scriptPattern = Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid onload= expressions
scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
return value;
}
}

View File

@@ -0,0 +1,25 @@
package com.wb.domainserver.security.handler;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE); // 1
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.getWriter().write("권한 불충분 %s".formatted(accessDeniedException.getMessage())); // 2
response.getWriter().flush();
}
}

View File

@@ -0,0 +1,84 @@
package com.wb.domainserver.security.handler;
import com.wb.domainserver.realm.RealmContextHolder;
import com.wb.domainserver.realm.util.UrlExtractor;
import com.wb.domainserver.util.CookieUtils;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import java.io.IOException;
import java.util.Map;
@Slf4j
public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
String userRole = "";
//엑세스토큰 유효한 상태에서의 로그아웃
if(authentication != null){
userRole = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).findFirst().orElse("");
}
//엑세스토큰 유효하지 않은 상태의 로그아웃
else{
userRole = switch (RealmContextHolder.getSysDvCdRealm()) {
case "02" -> "ROLE_ADMIN";
case "03" -> "ROLE_MNGR";
default -> "ROLE_USER";
};
}
String originUrlHost = UrlExtractor.getOriginalHost(RealmContextHolder.getOriginalHostRealm());
log.debug("logout success. userRole : {}", userRole);
log.debug("logout success. userRole getRealm : {}", RealmContextHolder.getRealm());
log.debug("logout success. userRole getSysDvCdRealm: {}", RealmContextHolder.getSysDvCdRealm());
log.debug("logout success. userRole getOriginalHostRealm: {}", originUrlHost);
// 세션 만료 처리
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
// SecurityContextHolder 클리어
SecurityContextHolder.clearContext();
//로그인 쿠키 경로
Map<String, String> cookiePath = CookieUtils.loginCookiePath();
//관리자 일경우
if("ROLE_MNGR".equals(userRole) || "ROLE_ADMIN".equals(userRole)){
// 쿠키 삭제
CookieUtils.deleteCookie("ACT", cookiePath.get("ACTPath"),request, response);
CookieUtils.deleteCookie("RFT", cookiePath.get("RFTPath"), request, response);
CookieUtils.deleteCookie("token_expire", cookiePath.get("ACTPath"), request, response);
response.sendRedirect( "/mngr");
}else{
// 쿠키 삭제
CookieUtils.deleteCookie("ACT", cookiePath.get("ACTPath"), request, response);
CookieUtils.deleteCookie("RFT", cookiePath.get("RFTPath"), request, response);
CookieUtils.deleteCookie("token_expire", cookiePath.get("ACTPath"), request, response);
response.sendRedirect("/sopmal/main");
}
}
}

View File

@@ -0,0 +1,40 @@
package com.wb.domainserver.security.jwt.config;
import com.wb.domainserver.security.jwt.handler.JwtHandler;
import com.wb.domainserver.security.jwt.handler.JwtHelper;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
@Configuration
public class JwtConfig {
private final JwtHandler jwtHandler;
public JwtConfig(JwtHandler jwtHandler) {
this.jwtHandler = jwtHandler;
}
@Bean
public JwtHelper accessTokenHelper(
@Value("${jwt.access.max-age}") long accessMaxAge
, @Value("${jwt.access.secretKey}") String accessSecretKey) {
SecretKey secretKey = Keys.hmacShaKeyFor(accessSecretKey.getBytes(StandardCharsets.UTF_8));
return new JwtHelper(jwtHandler, accessMaxAge, secretKey);
}
@Bean
public JwtHelper refreshTokenHelper(
@Value("${jwt.refresh.max-age}") long refreshMaxAge
, @Value("${jwt.refresh.secretKey}") String refreshSecretKey) {
SecretKey secretKey = Keys.hmacShaKeyFor(refreshSecretKey.getBytes(StandardCharsets.UTF_8));
return new JwtHelper(jwtHandler, refreshMaxAge, secretKey);
}
}

View File

@@ -0,0 +1,66 @@
package com.wb.domainserver.security.jwt.handler;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.Map;
@Slf4j
@Component
public class JwtHandler {
//발행일
private final Date ISSUE_AT = Date.from(ZonedDateTime.now(ZoneId.of("Asia/Seoul")).toInstant());
public String createToken(Map<String, Object> claims, long maxAgeSeconds, SecretKey secretKey) {
return Jwts.builder()
.addClaims(claims)
.addClaims(Map.of(
Claims.ISSUED_AT, ISSUE_AT,
Claims.EXPIRATION, Date.from(ZonedDateTime.now(ZoneId.of("Asia/Seoul")).toInstant().plusSeconds(maxAgeSeconds))
))
.signWith(secretKey)
.compact();
}
public Claims parse(String token, SecretKey secretKey) throws JwtException {
var parser = Jwts.parserBuilder().setSigningKey(secretKey).build();
Jws<Claims> parsedJws = parser.parseClaimsJws(unType(token));
return parsedJws.getBody();
}
public boolean isTokenExpired(String token, SecretKey secretKey) {
try {
Claims claims = parse(token, secretKey);
return claims.getExpiration().before(new Date());
} catch (ExpiredJwtException eje) {
log.error("Token has expired: {}", eje.getMessage());
return true;
}catch (JwtException je) {
log.error("Invalid JWT token: {}", je.getMessage());
throw je;
}
catch (Exception e) {
log.error(e.getMessage());
throw new JwtException("Invalid JWT token");
}
}
private String unType(String token) {
return token;
}
}

View File

@@ -0,0 +1,81 @@
package com.wb.domainserver.security.jwt.handler;
import com.wb.domainserver.domain.gcone.login.vo.LoginUser;
import com.wb.domainserver.security.authentication.MemberJwtToken;
import io.jsonwebtoken.Claims;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.SecretKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class JwtHelper {
private final Logger log = LoggerFactory.getLogger(JwtHelper.class);
private final JwtHandler jwtHandler;
private final long maxAgeSeconds;
private final SecretKey secretKey;
public JwtHelper(JwtHandler jwtHandler, long maxAgeSeconds, SecretKey secretKey) {
this.jwtHandler = jwtHandler;
this.maxAgeSeconds = maxAgeSeconds;
this.secretKey = secretKey;
}
public String createToken(LoginUser claims) {
Map<String, Object> claimsMap = new HashMap<>();
claimsMap.put("mbrId", claims.getUserId());
claimsMap.put("sopmalCd", claims.getSopmalCd());
claimsMap.put("mbrDvCd", claims.getMbrDvCd());
claimsMap.put("userInnb", claims.getUserInnb());
claimsMap.put("mbrNm", claims.getUsername());
claimsMap.put("mbtlnb", claims.getMbtlnb());
// claimsMap.put("accessUrl", claims.);
claimsMap.put("clientId", "account");
claimsMap.put("sysDvCd", claims.getSysDvCd());
claimsMap.put("mngrDv", claims.getMngrDv());
claimsMap.put("teamInnb", claims.getTeamInnb());
claimsMap.put("teamMngrAt", claims.getTeamMngrAt());
List<String> roleTypes = new ArrayList<>();
String roleType = switch (claims.getSysDvCd()){
case "02" -> "ROLE_ADMIN";
case "03" -> "ROLE_MNGR";
default -> "ROLE_USER";
};
roleTypes.add(roleType);
claimsMap.put("roleType", roleTypes);
return jwtHandler.createToken(claimsMap, maxAgeSeconds, secretKey);
}
public MemberJwtToken parse(String token) {
Claims claims = jwtHandler.parse(token, secretKey);
return convert(claims);
}
public Claims getClaims(String token) {
return jwtHandler.parse(token, secretKey);
}
public boolean expireToken(String token) {
return jwtHandler.isTokenExpired(token, secretKey);
}
private MemberJwtToken convert(Claims claims) {
return new MemberJwtToken(claims);
}
}

View File

@@ -0,0 +1,21 @@
package com.wb.domainserver.security.jwt.mapper;
import com.wb.domainserver.domain.gcone.login.vo.LoginUser;
import com.wb.domainserver.security.jwt.vo.TokenInfo;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
@Mapper
@Repository
public interface TokenMapper {
public TokenInfo selectToken(LoginUser loginUser);
public TokenInfo selectRefreshToken(TokenInfo token);
public void insertToken(TokenInfo token);
public void updateToken(TokenInfo token);
}

View File

@@ -0,0 +1,136 @@
package com.wb.domainserver.security.jwt.service;
import com.wb.domainserver.domain.gcone.login.vo.LoginUser;
import com.wb.domainserver.security.jwt.handler.JwtHelper;
import com.wb.domainserver.security.jwt.mapper.TokenMapper;
import com.wb.domainserver.security.jwt.vo.TokenInfo;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@RequiredArgsConstructor
public class TokenService {
private final TokenMapper tokenMapper;
private final JwtHelper accessTokenHelper;
private final JwtHelper refreshTokenHelper;
/**
* 엑세스, 리프레쉬 토큰 생성
*
* */
public TokenInfo createToken(LoginUser loginUser){
String accessToken = accessTokenHelper.createToken(loginUser);
Claims accessTokenClaims = accessTokenHelper.getClaims(accessToken);
//리프레쉬 토큰은 회원고유번호와 회원구분코드만 저장
LoginUser refreshUser = new LoginUser();
refreshUser.setUserInnb(loginUser.getUserInnb());
refreshUser.setMbrDvCd(loginUser.getMbrDvCd());
refreshUser.setSysDvCd(loginUser.getSysDvCd());
String refreshToken = refreshTokenHelper.createToken(refreshUser);
Claims refreshTokenClaims = refreshTokenHelper.getClaims(refreshToken);
return TokenInfo.of(accessToken, accessTokenClaims, refreshToken, refreshTokenClaims);
}
/**
* 토큰 만료 여부 확인
* */
public boolean tokenExpiredAt(String tokenDv,String token){
boolean isExpired = false;
if(tokenDv.equals("ACT")){
isExpired = accessTokenHelper.expireToken(token);
}else{
isExpired = refreshTokenHelper.expireToken(token);
}
return isExpired;
}
/**
* 리프레쉬 토큰 파싱 정보 조회
*
* */
public Claims getRefreshToken(String refreshToken){
return refreshTokenHelper.getClaims(refreshToken);
}
/**
* 엑세스 토큰 DB 정보 조회
*
* */
public TokenInfo selectToken(LoginUser loginUser){
return tokenMapper.selectToken(loginUser);
};
/**
* 리프레쉬 토큰 DB 정보 조회
*
* */
public TokenInfo selectRefreshToken(TokenInfo token){
return tokenMapper.selectRefreshToken(token);
};
/**
* 토큰정보 저장
*
* */
@Transactional
public void insertToken(TokenInfo token){
tokenMapper.insertToken(token);
};
/**
* 토큰정보 수정
*
* */
@Transactional
public TokenInfo updateToken(LoginUser loginUser){
TokenInfo token = createToken(loginUser);
token.setRealm(loginUser.getRealm());
token.setUserInnb(Long.parseLong(loginUser.getUserInnb()));
tokenMapper.updateToken(token);
return token;
};
/**
* 엑세스토큰 수정 및 저장
*
* */
@Transactional
public TokenInfo updateAccessToken(LoginUser loginUser, TokenInfo tokenInfo){
//토큰 생성
TokenInfo token = createToken(loginUser);
//엑세스토큰만 가져와 교체
tokenInfo.setAccessToken(token.getAccessToken());
tokenMapper.updateToken(tokenInfo);
return tokenInfo;
};
}

View File

@@ -0,0 +1,80 @@
package com.wb.domainserver.security.jwt.vo;
import io.jsonwebtoken.Claims;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
@Data
@EqualsAndHashCode
public class TokenInfo {
private String realm;
private long userInnb;
private String userId;
private String accessToken;
private String refreshToken;
private Date createdAt;
private Date accessExpiresAt;
private Date refreshExpiresAt;
private Date lastAccessedTime;
//리프레쉬 토큰 만료 여부
private String expiredAt;
public void setLastAccessedTimeToNow() {
this.lastAccessedTime = Date.from(ZonedDateTime.now(ZoneId.of("Asia/Seoul")).toInstant());
}
public TokenInfo(String tokenDv, String token, long userInnb, String realm){
if("ACT".equals(tokenDv)){
this.accessToken = token;
}else{
this.refreshToken = token;
}
this.userInnb = userInnb;
this.realm = realm;
}
public TokenInfo(String accessToken, Claims accessTokenClaims, String refreshToken, Claims refreshTokenClaims){
this.accessToken = accessToken;
this.accessExpiresAt = accessTokenClaims.getExpiration();
this.refreshToken = refreshToken;
this.refreshExpiresAt = refreshTokenClaims.getExpiration();
this.lastAccessedTime = Date.from(ZonedDateTime.now(ZoneId.of("Asia/Seoul")).toInstant());
}
/* mybatis constructor */
public TokenInfo(String realm, BigInteger userInnb, String accessToken, String refreshToken, Timestamp createdAt, Timestamp refreshExpiresAt, String expiredAt, Timestamp lastAccessedTime) {
this.realm = realm;
this.userInnb = userInnb.longValue();
this.accessToken = accessToken;
this.refreshToken = refreshToken;
this.createdAt = createdAt;
this.refreshExpiresAt = refreshExpiresAt;
this.expiredAt = expiredAt;
this.lastAccessedTime = lastAccessedTime;
}
public static TokenInfo of(String accessToken, Claims accessTokenClaims, String refreshToken, Claims refreshTokenClaims) {
return new TokenInfo(accessToken, accessTokenClaims, refreshToken, refreshTokenClaims);
}
}

View File

@@ -0,0 +1,30 @@
package com.wb.domainserver.security.user;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
public class CustomAuthenticationToken extends AbstractAuthenticationToken {
private final UserDetails principal;
public CustomAuthenticationToken(UserDetails principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
setAuthenticated(true); // 인증 상태 적용
}
@Override
public Object getCredentials() {
throw new UnsupportedOperationException();
}
@Override
public Object getPrincipal() {
return this.principal;
}
}

View File

@@ -0,0 +1,253 @@
package com.wb.domainserver.security.user;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
public class SecurityUser implements UserDetails {
private String id;
private String mbrId;
private String sopmalCd;
private String mbrDvCd;
private long userInnb;
private String userName;
private String mbtlnb;
private String accessUrl;
private String clientId;
private String originalHost;
private String sysDvCd;
private String tentGroup;
private String reprsntBrand;
private String teamInnb;
private String mngrDv;
private String tentcpnNm;
private String itrntDv;
private String strOperDv;
public String getItrntDv() {
return itrntDv;
}
public void setItrntDv(String itrntDv) {
this.itrntDv = itrntDv;
}
public String getStrOperDv() {
return strOperDv;
}
public void setStrOperDv(String strOperDv) {
this.strOperDv = strOperDv;
}
public String getTentcpnNm() {
return tentcpnNm;
}
public void setTentcpnNm(String tentcpnNm) {
this.tentcpnNm = tentcpnNm;
}
private List<GrantedAuthority> grantedAuthorityList;
private boolean accessToRestrictedPolicy;
public static SecurityUser builder() {
return new SecurityUser();
}
public SecurityUser withAccessToRestrictedPolicy(boolean restrictedPolicy) {
this.accessToRestrictedPolicy = restrictedPolicy;
return this;
}
public boolean hasAccessToRestrictedPolicy() {
return accessToRestrictedPolicy;
}
public SecurityUser withGrantedAuthorityList(List<GrantedAuthority> grantedAuthorityList) {
this.grantedAuthorityList = grantedAuthorityList;
return this;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.grantedAuthorityList;
}
@Override
public String getPassword() {
return null;
}
@Override
public String getUsername() {
return this.userName;
}
public SecurityUser withUserInnb(long userInnb) {
this.userInnb = userInnb;
return this;
}
public long getUserInnb() {
return this.userInnb;
}
public String getMbrDvCd() { return mbrDvCd; }
public SecurityUser withMbrDvCd(String mbrDvCd) {
this.mbrDvCd = mbrDvCd;
return this;
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return false;
}
public SecurityUser withId(String id) {
this.id = id;
return this;
}
public SecurityUser withUserName(String userName) {
this.userName = userName;
return this;
}
public SecurityUser withMbtlnb(String mbtlnb) {
this.mbtlnb = mbtlnb;
return this;
}
public SecurityUser withSopmalCd(String sopmalCd) {
this.sopmalCd = sopmalCd;
return this;
}
public SecurityUser withAccessUrl(String accessUrl) {
this.accessUrl = accessUrl;
return this;
}
public SecurityUser withClientId(String clientId) {
this.clientId = clientId;
return this;
}
public SecurityUser withOriginalHost(String originalHost) {
this.originalHost = originalHost;
return this;
}
public SecurityUser withSysDvCd(String sysDvCd) {
this.sysDvCd = sysDvCd;
return this;
}
public SecurityUser withMngrDv(String mngrDv) {
this.mngrDv = mngrDv;
return this;
}
public SecurityUser withTeamInnb(String teamInnb) {
this.teamInnb = teamInnb;
return this;
}
public SecurityUser withTentGroup(String tentGroup) {
this.tentGroup = tentGroup;
return this;
}
public SecurityUser withReprsntBrand(String reprsntBrand) {
this.reprsntBrand = reprsntBrand;
return this;
}
public SecurityUser withTentcpnNm(String tentcpnNm) {
this.tentcpnNm = tentcpnNm;
return this;
}
public SecurityUser withItrntDv(String itrntDv) {
this.itrntDv = itrntDv;
return this;
}
public SecurityUser withStrOperDv(String strOperDv) {
this.strOperDv = strOperDv;
return this;
}
public String getId() {
return this.id;
}
public String getSopmalCd() {
return this.sopmalCd;
}
public String getMbtlnb() {
return this.mbtlnb;
}
public String getAccessUrl() {
return this.accessUrl;
}
public String getClientId() {
return this.clientId;
}
public String getOriginalHost() {
return this.originalHost;
}
public String getSysDvCd() {
return this.sysDvCd;
}
public String getTentGroup() {
return this.tentGroup;
}
public String getReprsntBrand() {
return this.reprsntBrand;
}
public SecurityUser withMbrId(String mbrId) {
this.mbrId = mbrId;
return this;
}
public String getMbrId() {return this.mbrId;}
public String getTeamInnb() {return this.teamInnb;}
public String getMngrDv() {
return this.mngrDv;
}
}

View File

@@ -0,0 +1,93 @@
package com.wb.domainserver.security.user;
import com.wb.domainserver.realm.RealmContextHolder;
import com.wb.domainserver.security.authentication.MemberJwtToken;
import com.wb.domainserver.security.jwt.handler.JwtHelper;
import com.wb.domainserver.security.jwt.vo.TokenInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
public class SecurityUserDetailService implements UserDetailsService {
private final SecurityUserMapper securityUserMapper;
private final JwtHelper accessTokenHelper;
public SecurityUserDetailService(
SecurityUserMapper securityUserMapper, JwtHelper accessTokenHelper){
this.securityUserMapper = securityUserMapper;
this.accessTokenHelper = accessTokenHelper ;
}
@Override
public UserDetails loadUserByUsername(String accessToken) throws UsernameNotFoundException {
// accessToken 만료여부 일경우 CustomAuthenticationEntryPoint 처리 유도
if(accessTokenHelper.expireToken(accessToken)){
return null;
}
//accessToken DB 조회 검증
//존재 하지 않을 경우 인증 실패
MemberJwtToken token = accessTokenHelper.parse(accessToken);
String realm = RealmContextHolder.getRealm().toLowerCase();
TokenInfo accessTokenInfo = new TokenInfo("ACT", accessToken, token.getUserInnb(), realm);
if(securityUserMapper.validateAccessToken(accessTokenInfo)){
return null;
}
return tokenToUserDetails(token);
}
private UserDetails tokenToUserDetails (MemberJwtToken token) {
return SecurityUser.builder()
.withId(token.getId())
.withMbrId(token.getMbrId())
.withSopmalCd(token.getSopmalCd())
.withMbrDvCd(token.getMbrDvCd())
.withUserInnb(token.getUserInnb())
.withUserName(token.getMbrNm())
.withMbtlnb(token.getMbtlnb())
.withAccessUrl(token.getAccessUrl())
.withClientId(token.getClientId())
.withSysDvCd(token.getSysDvCd())
.withMngrDv(token.getMngrDv())
.withTentcpnNm(token.getTentcpnNm())
.withItrntDv(token.getItrntDv())
.withStrOperDv(token.getStrOperDv())
// .withTentGroup(token.getTentGroup())
// .withReprsntBrand(token.getReprsntBrand())
.withGrantedAuthorityList(token.getRoleType().stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList())
)
.withAccessToRestrictedPolicy(checkRestrictedPolicy());
}
private boolean checkRestrictedPolicy() {
return true;
}
public String getGrantType(String token) {
return securityUserMapper.getGrantType(token);
}
public List<String> getAvailablePathList(SecurityUser user) {
return securityUserMapper.getAvailablePathList(user);
}
}

View File

@@ -0,0 +1,24 @@
package com.wb.domainserver.security.user;
import com.wb.domainserver.security.jwt.vo.TokenInfo;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
@Mapper
@Repository
public interface SecurityUserMapper {
Map<String, Object> loadUserByUsername(Map<String, Object> username);
String getGrantType(String token);
List<String> getAvailablePathList(SecurityUser user);
Map<String, Object> loadUserByManagername(String username);
String loadUsernameByToken(Map<String, Object> data);
boolean validateAccessToken(TokenInfo tokenInfo);
}