From e038bfc0479e9aacd0177c2a6a787d9e137b1b1b Mon Sep 17 00:00:00 2001 From: onjsdnjs Date: Wed, 25 Dec 2019 17:21:00 +0900 Subject: [PATCH 01/28] lecture --- .../corespringsecurity/service/MethodSecurityService.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/security/corespringsecurity/service/MethodSecurityService.java b/src/main/java/io/security/corespringsecurity/service/MethodSecurityService.java index e6fc9ddd..ddbc3ef0 100644 --- a/src/main/java/io/security/corespringsecurity/service/MethodSecurityService.java +++ b/src/main/java/io/security/corespringsecurity/service/MethodSecurityService.java @@ -2,12 +2,13 @@ import io.security.corespringsecurity.security.aop.CustomMethodSecurityInterceptor; import lombok.extern.slf4j.Slf4j; +import org.springframework.aop.Advisor; import org.springframework.aop.framework.ProxyFactory; -import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.beans.factory.support.DefaultSingletonBeanRegistry; import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; +import org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor; import org.springframework.security.access.method.MapBasedMethodSecurityMetadataSource; import org.springframework.stereotype.Component; import org.springframework.util.ClassUtils; @@ -22,11 +23,13 @@ public class MethodSecurityService { private MapBasedMethodSecurityMetadataSource mapBasedMethodSecurityMetadataSource; private AnnotationConfigServletWebServerApplicationContext applicationContext; private CustomMethodSecurityInterceptor methodSecurityInterceptor; + private MethodSecurityMetadataSourceAdvisor methodSecurityMetadataSourceAdvisor; - public MethodSecurityService(MapBasedMethodSecurityMetadataSource mapBasedMethodSecurityMetadataSource, AnnotationConfigServletWebServerApplicationContext applicationContext, CustomMethodSecurityInterceptor methodSecurityInterceptor) { + public MethodSecurityService(MapBasedMethodSecurityMetadataSource mapBasedMethodSecurityMetadataSource, AnnotationConfigServletWebServerApplicationContext applicationContext, CustomMethodSecurityInterceptor methodSecurityInterceptor, MethodSecurityMetadataSourceAdvisor methodSecurityMetadataSourceAdvisor) { this.mapBasedMethodSecurityMetadataSource = mapBasedMethodSecurityMetadataSource; this.applicationContext = applicationContext; this.methodSecurityInterceptor = methodSecurityInterceptor; + this.methodSecurityMetadataSourceAdvisor = methodSecurityMetadataSourceAdvisor; } public void addMethodSecured(String className, String roleName) throws Exception{ @@ -40,6 +43,7 @@ public void addMethodSecured(String className, String roleName) throws Exception ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTarget(type.getDeclaredConstructor().newInstance()); proxyFactory.addAdvice(methodSecurityInterceptor); +// proxyFactory.addAdvisor(methodSecurityMetadataSourceAdvisor); Object proxy = proxyFactory.getProxy(); List attr = Arrays.asList(new SecurityConfig(roleName)); From a24d2fdb62cda83f940d622a745cbb3fe21b2c9a Mon Sep 17 00:00:00 2001 From: onjsdnjs Date: Wed, 25 Dec 2019 18:55:17 +0900 Subject: [PATCH 02/28] lecture --- .../aopsecurity/AopSecurityController.java | 91 ------ .../liveaop/AopLiveMethodService.java | 13 - .../aopsecurity/method/AopMethodService.java | 29 -- .../pointcut/AopPointcutService.java | 13 - .../common/GlobalWebControllerAdvice.java | 43 --- .../corespringsecurity/config/AppConfig.java | 29 -- .../controller/HomeController.java | 10 - .../controller/admin/AdminController.java | 15 - .../controller/admin/ResourcesController.java | 95 ------- .../controller/admin/RoleController.java | 52 ---- .../admin/UserManagerController.java | 62 ---- .../controller/login/LoginController.java | 28 -- .../controller/user/UserController.java | 38 --- .../domain/dto/AccessIpDto.java | 18 -- .../domain/dto/ResourcesDto.java | 20 -- .../domain/dto/RoleDto.java | 22 -- .../domain/dto/UserDto.java | 25 -- .../domain/entity/AccessIp.java | 27 -- .../domain/entity/PersistentLogin.java | 41 --- .../domain/entity/Resources.java | 44 --- .../domain/entity/Role.java | 43 --- .../domain/entity/RoleHierarchy.java | 36 --- .../domain/entity/User.java | 42 --- .../repository/AccessIpRepository.java | 11 - .../JpaPersistentTokenRepository.java | 49 ---- .../repository/JpaTokenRepositoryCleaner.java | 42 --- .../repository/RememberMeTokenRepository.java | 16 -- .../repository/ResourcesRepository.java | 21 -- .../repository/RoleHierarchyRepository.java | 10 - .../repository/RoleRepository.java | 14 - .../repository/UserRepository.java | 15 - .../aop/CustomMethodSecurityInterceptor.java | 43 --- .../AjaxAuthenticationFailureHandler.java | 42 --- .../AjaxAuthenticationSuccessHandler.java | 49 ---- .../handler/CommonAccessDeniedHandler.java | 61 ---- .../handler/CommonLogoutSuccessHandler.java | 26 -- .../FormAuthenticationFailureHandler.java | 35 --- .../FormAuthenticationSuccessHandler.java | 41 --- .../provider/AjaxAuthenticationProvider.java | 82 ------ .../provider/FormAuthenticationProvider.java | 68 ----- .../services/FormRememberMeServices.java | 70 ----- .../FormWebAuthenticationDetails.java | 22 -- .../FormWebAuthenticationDetailsSource.java | 15 - .../authentication/services/UserDetail.java | 20 -- .../services/UserDetailsServiceImpl.java | 85 ------ .../security/configs/AjaxLoginConfigurer.java | 81 ------ .../configs/MethodSecurityConfig.java | 188 ------------ .../security/configs/SecurityConfig.java | 268 +----------------- .../security/domain/AuthoritiesDto.java | 17 -- .../security/domain/RoleHierarchyDto.java | 15 - .../security/domain/UserDto.java | 72 ----- .../security/enums/SecurtiyMethodType.java | 17 -- .../AuthMethodNotSupportedException.java | 11 - .../exception/UserNotFoundException.java | 23 -- .../MethodResourcesMapFactoryBean.java | 54 ---- .../factory/UrlResourcesMapFactoryBean.java | 39 --- .../filter/AjaxLoginProcessingFilter.java | 54 ---- .../security/filter/PermitAllFilter.java | 77 ----- .../security/init/SecurityInitializer.java | 24 -- .../AuthenticationFailureListener.java | 24 -- .../AuthenticationSuccessEventListener.java | 24 -- .../security/listener/SetupDataLoader.java | 163 ----------- .../UrlSecurityMetadataSource.java | 80 ------ .../ProtectPointcutPostProcessor.java | 133 --------- .../token/AjaxAuthenticationToken.java | 18 -- .../security/voter/IpAddressVoter.java | 59 ---- .../service/LoginAttemptService.java | 8 - .../service/MethodSecurityService.java | 72 ----- .../service/ResourcesService.java | 17 -- .../service/RoleHierarchyService.java | 6 - .../service/RoleService.java | 15 - .../service/SecurityResourceService.java | 134 --------- .../service/UserService.java | 14 - .../service/impl/LoginAttemptServiceImpl.java | 49 ---- .../service/impl/ResourcesServiceImpl.java | 42 --- .../impl/RoleHierarchyServiceImpl.java | 39 --- .../service/impl/RoleServiceImpl.java | 37 --- .../service/impl/UserServiceImpl.java | 76 ----- .../corespringsecurity/util/WebUtil.java | 25 -- .../images/glyphicons-halflings-white.png | Bin 8777 -> 0 bytes .../static/images/glyphicons-halflings.png | Bin 12799 -> 0 bytes .../resources/static/images/spring_boot.png | Bin 38281 -> 0 bytes .../static/images/spring_boot_gray.png | Bin 29212 -> 0 bytes .../static/images/spring_boot_green.png | Bin 3776 -> 0 bytes .../templates/admin/common/head.html | 26 -- .../resources/templates/admin/common/top.html | 13 - src/main/resources/templates/admin/home.html | 25 -- .../templates/admin/resource/detail.html | 64 ----- .../templates/admin/resource/list.html | 44 --- .../templates/admin/role/detail.html | 40 --- .../resources/templates/admin/role/list.html | 39 --- .../templates/admin/user/detail.html | 62 ---- .../resources/templates/admin/user/list.html | 42 --- src/main/resources/templates/aop/liveaop.html | 16 -- src/main/resources/templates/home.html | 28 -- .../resources/templates/layout/footer.html | 4 +- .../resources/templates/layout/header.html | 4 +- src/main/resources/templates/login.html | 6 - .../templates/user/login/denied.html | 16 -- .../templates/user/login/invalidSession.html | 13 - .../resources/templates/user/login/list.html | 57 ---- .../templates/user/login/logout.html | 19 -- .../templates/user/login/register.html | 13 - .../templates/user/login/successRegister.html | 1 - 104 files changed, 6 insertions(+), 4174 deletions(-) delete mode 100644 src/main/java/io/security/corespringsecurity/aopsecurity/AopSecurityController.java delete mode 100644 src/main/java/io/security/corespringsecurity/aopsecurity/liveaop/AopLiveMethodService.java delete mode 100644 src/main/java/io/security/corespringsecurity/aopsecurity/method/AopMethodService.java delete mode 100644 src/main/java/io/security/corespringsecurity/aopsecurity/pointcut/AopPointcutService.java delete mode 100644 src/main/java/io/security/corespringsecurity/common/GlobalWebControllerAdvice.java delete mode 100644 src/main/java/io/security/corespringsecurity/config/AppConfig.java delete mode 100644 src/main/java/io/security/corespringsecurity/controller/admin/AdminController.java delete mode 100644 src/main/java/io/security/corespringsecurity/controller/admin/ResourcesController.java delete mode 100644 src/main/java/io/security/corespringsecurity/controller/admin/RoleController.java delete mode 100644 src/main/java/io/security/corespringsecurity/controller/admin/UserManagerController.java delete mode 100644 src/main/java/io/security/corespringsecurity/controller/login/LoginController.java delete mode 100644 src/main/java/io/security/corespringsecurity/controller/user/UserController.java delete mode 100644 src/main/java/io/security/corespringsecurity/domain/dto/AccessIpDto.java delete mode 100644 src/main/java/io/security/corespringsecurity/domain/dto/ResourcesDto.java delete mode 100644 src/main/java/io/security/corespringsecurity/domain/dto/RoleDto.java delete mode 100644 src/main/java/io/security/corespringsecurity/domain/dto/UserDto.java delete mode 100644 src/main/java/io/security/corespringsecurity/domain/entity/AccessIp.java delete mode 100644 src/main/java/io/security/corespringsecurity/domain/entity/PersistentLogin.java delete mode 100644 src/main/java/io/security/corespringsecurity/domain/entity/Resources.java delete mode 100644 src/main/java/io/security/corespringsecurity/domain/entity/Role.java delete mode 100644 src/main/java/io/security/corespringsecurity/domain/entity/RoleHierarchy.java delete mode 100644 src/main/java/io/security/corespringsecurity/domain/entity/User.java delete mode 100644 src/main/java/io/security/corespringsecurity/repository/AccessIpRepository.java delete mode 100644 src/main/java/io/security/corespringsecurity/repository/JpaPersistentTokenRepository.java delete mode 100644 src/main/java/io/security/corespringsecurity/repository/JpaTokenRepositoryCleaner.java delete mode 100644 src/main/java/io/security/corespringsecurity/repository/RememberMeTokenRepository.java delete mode 100644 src/main/java/io/security/corespringsecurity/repository/ResourcesRepository.java delete mode 100644 src/main/java/io/security/corespringsecurity/repository/RoleHierarchyRepository.java delete mode 100644 src/main/java/io/security/corespringsecurity/repository/RoleRepository.java delete mode 100644 src/main/java/io/security/corespringsecurity/repository/UserRepository.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/aop/CustomMethodSecurityInterceptor.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/authentication/handler/AjaxAuthenticationFailureHandler.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/authentication/handler/AjaxAuthenticationSuccessHandler.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/authentication/handler/CommonAccessDeniedHandler.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/authentication/handler/CommonLogoutSuccessHandler.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/authentication/handler/FormAuthenticationFailureHandler.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/authentication/handler/FormAuthenticationSuccessHandler.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/authentication/provider/AjaxAuthenticationProvider.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/authentication/provider/FormAuthenticationProvider.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/authentication/services/FormRememberMeServices.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/authentication/services/FormWebAuthenticationDetails.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/authentication/services/FormWebAuthenticationDetailsSource.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/authentication/services/UserDetail.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/authentication/services/UserDetailsServiceImpl.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/configs/AjaxLoginConfigurer.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/configs/MethodSecurityConfig.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/domain/AuthoritiesDto.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/domain/RoleHierarchyDto.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/domain/UserDto.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/enums/SecurtiyMethodType.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/exception/AuthMethodNotSupportedException.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/exception/UserNotFoundException.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/factory/MethodResourcesMapFactoryBean.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/factory/UrlResourcesMapFactoryBean.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/filter/AjaxLoginProcessingFilter.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/filter/PermitAllFilter.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/init/SecurityInitializer.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/listener/AuthenticationFailureListener.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/listener/AuthenticationSuccessEventListener.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/listener/SetupDataLoader.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/metaDataSource/UrlSecurityMetadataSource.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/processor/ProtectPointcutPostProcessor.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/token/AjaxAuthenticationToken.java delete mode 100644 src/main/java/io/security/corespringsecurity/security/voter/IpAddressVoter.java delete mode 100644 src/main/java/io/security/corespringsecurity/service/LoginAttemptService.java delete mode 100644 src/main/java/io/security/corespringsecurity/service/MethodSecurityService.java delete mode 100644 src/main/java/io/security/corespringsecurity/service/ResourcesService.java delete mode 100644 src/main/java/io/security/corespringsecurity/service/RoleHierarchyService.java delete mode 100644 src/main/java/io/security/corespringsecurity/service/RoleService.java delete mode 100644 src/main/java/io/security/corespringsecurity/service/SecurityResourceService.java delete mode 100644 src/main/java/io/security/corespringsecurity/service/UserService.java delete mode 100644 src/main/java/io/security/corespringsecurity/service/impl/LoginAttemptServiceImpl.java delete mode 100644 src/main/java/io/security/corespringsecurity/service/impl/ResourcesServiceImpl.java delete mode 100644 src/main/java/io/security/corespringsecurity/service/impl/RoleHierarchyServiceImpl.java delete mode 100644 src/main/java/io/security/corespringsecurity/service/impl/RoleServiceImpl.java delete mode 100644 src/main/java/io/security/corespringsecurity/service/impl/UserServiceImpl.java delete mode 100644 src/main/java/io/security/corespringsecurity/util/WebUtil.java delete mode 100644 src/main/resources/static/images/glyphicons-halflings-white.png delete mode 100644 src/main/resources/static/images/glyphicons-halflings.png delete mode 100644 src/main/resources/static/images/spring_boot.png delete mode 100644 src/main/resources/static/images/spring_boot_gray.png delete mode 100644 src/main/resources/static/images/spring_boot_green.png delete mode 100644 src/main/resources/templates/admin/common/head.html delete mode 100644 src/main/resources/templates/admin/common/top.html delete mode 100644 src/main/resources/templates/admin/home.html delete mode 100644 src/main/resources/templates/admin/resource/detail.html delete mode 100644 src/main/resources/templates/admin/resource/list.html delete mode 100644 src/main/resources/templates/admin/role/detail.html delete mode 100644 src/main/resources/templates/admin/role/list.html delete mode 100644 src/main/resources/templates/admin/user/detail.html delete mode 100644 src/main/resources/templates/admin/user/list.html delete mode 100644 src/main/resources/templates/aop/liveaop.html delete mode 100644 src/main/resources/templates/user/login/denied.html delete mode 100644 src/main/resources/templates/user/login/invalidSession.html delete mode 100644 src/main/resources/templates/user/login/list.html delete mode 100644 src/main/resources/templates/user/login/logout.html diff --git a/src/main/java/io/security/corespringsecurity/aopsecurity/AopSecurityController.java b/src/main/java/io/security/corespringsecurity/aopsecurity/AopSecurityController.java deleted file mode 100644 index 7342dbb1..00000000 --- a/src/main/java/io/security/corespringsecurity/aopsecurity/AopSecurityController.java +++ /dev/null @@ -1,91 +0,0 @@ -package io.security.corespringsecurity.aopsecurity; - -import io.security.corespringsecurity.aopsecurity.method.AopMethodService; -import io.security.corespringsecurity.security.aop.CustomMethodSecurityInterceptor; -import io.security.corespringsecurity.security.processor.ProtectPointcutPostProcessor; -import io.security.corespringsecurity.aopsecurity.pointcut.AopPointcutService; -import io.security.corespringsecurity.aopsecurity.liveaop.AopLiveMethodService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.aop.framework.ProxyFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.access.method.MapBasedMethodSecurityMetadataSource; -import org.springframework.stereotype.Controller; -import org.springframework.util.ClassUtils; -import org.springframework.web.bind.annotation.GetMapping; - -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; - -@Controller -@Slf4j -public class AopSecurityController { - - @Autowired - private ProtectPointcutPostProcessor protectPoitcutPostProcessor; - - @Autowired - MapBasedMethodSecurityMetadataSource mapBasedMethodSecurityMetadataSource; - - @Autowired - private AopMethodService aopMethodService; - - @Autowired - private AopPointcutService aopPointcutService; - - @Autowired - private AopLiveMethodService aopLiveMethodService; - - @Autowired - AnnotationConfigServletWebServerApplicationContext applicationContext; - - @Autowired - CustomMethodSecurityInterceptor methodSecurityInterceptor; - - @GetMapping("/method") - public String methodTest(){ - aopMethodService.methodTest(); - return "method"; - } - - @GetMapping("/method2") - public String methodTest2(){ - log.debug(aopMethodService.getClass().getSimpleName()); - aopMethodService.methodTest2(aopMethodService); - return "method2"; - } - - @GetMapping("/method3") - public String methodTest3(){ - aopMethodService.methodTest3(); - return "method3"; - } - - @GetMapping("/aop1") - public String aopFirstService(){ - aopPointcutService.aopService(); - return "aop1"; - } - - @GetMapping("/liveaop") - public String liveAopService(){ - aopLiveMethodService.liveAopService(); - return "aop/liveaop"; - } - - @GetMapping("/addAop") - public void addPointcut(String fullName, String roleName) throws Exception { - - String expression = "execution(* io.security.corespringsecurity.aopsecurity.liveaop.*Service.*(..))"; - List attr = Arrays.asList(new SecurityConfig("ROLE_MANAGER")); - Map> pointcutMap = new LinkedHashMap<>(); - pointcutMap.put(expression,attr); - protectPoitcutPostProcessor.setPointcutMap(pointcutMap); - - } -} diff --git a/src/main/java/io/security/corespringsecurity/aopsecurity/liveaop/AopLiveMethodService.java b/src/main/java/io/security/corespringsecurity/aopsecurity/liveaop/AopLiveMethodService.java deleted file mode 100644 index 50300cba..00000000 --- a/src/main/java/io/security/corespringsecurity/aopsecurity/liveaop/AopLiveMethodService.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.security.corespringsecurity.aopsecurity.liveaop; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -@Service -@Slf4j -public class AopLiveMethodService { - - public void liveAopService(){ - log.debug("LiveAopFirstService"); - } -} diff --git a/src/main/java/io/security/corespringsecurity/aopsecurity/method/AopMethodService.java b/src/main/java/io/security/corespringsecurity/aopsecurity/method/AopMethodService.java deleted file mode 100644 index 5a03685f..00000000 --- a/src/main/java/io/security/corespringsecurity/aopsecurity/method/AopMethodService.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.security.corespringsecurity.aopsecurity.method; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -@Service -@Slf4j -public class AopMethodService { - - public void methodTest() { - log.debug("methodTest"); - } - - public void methodTest2(AopMethodService methodService) { - methodService.innerCallMethodTest(); - log.debug("methodTest2"); - } - - public void methodTest3() { - log.debug(this.getClass().getSimpleName()); - this.innerCallMethodTest(); - log.debug("methodTest2"); - } - - public void innerCallMethodTest() { - log.debug("innerCallMethodTest"); - } - -} diff --git a/src/main/java/io/security/corespringsecurity/aopsecurity/pointcut/AopPointcutService.java b/src/main/java/io/security/corespringsecurity/aopsecurity/pointcut/AopPointcutService.java deleted file mode 100644 index 6ce8cd09..00000000 --- a/src/main/java/io/security/corespringsecurity/aopsecurity/pointcut/AopPointcutService.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.security.corespringsecurity.aopsecurity.pointcut; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -@Service -@Slf4j -public class AopPointcutService { - - public void aopService(){ - log.debug("AopFirstService"); - } -} diff --git a/src/main/java/io/security/corespringsecurity/common/GlobalWebControllerAdvice.java b/src/main/java/io/security/corespringsecurity/common/GlobalWebControllerAdvice.java deleted file mode 100644 index d80bf615..00000000 --- a/src/main/java/io/security/corespringsecurity/common/GlobalWebControllerAdvice.java +++ /dev/null @@ -1,43 +0,0 @@ -package io.security.corespringsecurity.common; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.multipart.MaxUploadSizeExceededException; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.sql.SQLException; - -@ControllerAdvice(annotations = Controller.class) -@Slf4j -public class GlobalWebControllerAdvice { - - @ExceptionHandler(MaxUploadSizeExceededException.class) - public ResponseEntity fileException(MaxUploadSizeExceededException e) { - return new ResponseEntity(e.getMessage(), new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR); - } - - @ExceptionHandler(SQLException.class) - public String sqlException(HttpServletRequest request, HttpServletResponse response, SQLException e) { - return e.toString(); - } - - @ExceptionHandler(AccessDeniedException.class) - @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) - public Exception accessDeniedException(HttpServletRequest request, Exception e) throws Exception{ - throw e; - } - - @ExceptionHandler(Throwable.class) - @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) - public String handleException(HttpServletRequest request, Exception e) { - return e.toString(); - } -} diff --git a/src/main/java/io/security/corespringsecurity/config/AppConfig.java b/src/main/java/io/security/corespringsecurity/config/AppConfig.java deleted file mode 100644 index 593b1d03..00000000 --- a/src/main/java/io/security/corespringsecurity/config/AppConfig.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.security.corespringsecurity.config; - -import io.security.corespringsecurity.repository.AccessIpRepository; -import io.security.corespringsecurity.repository.ResourcesRepository; -import io.security.corespringsecurity.security.configs.MethodSecurityConfig; -import io.security.corespringsecurity.service.SecurityResourceService; -import io.security.corespringsecurity.service.impl.RoleHierarchyServiceImpl; -import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; - -@Configuration -@AutoConfigureBefore({MethodSecurityConfig.class}) -public class AppConfig { - - @Bean - public SecurityResourceService securityResourceService(ResourcesRepository resourcesRepository, RoleHierarchyImpl roleHierarchy,RoleHierarchyServiceImpl roleHierarchyService, AccessIpRepository accessIpRepository/*, MapBasedMethodSecurityMetadataSource mapBasedMethodSecurityMetadataSource, AnnotationConfigServletWebServerApplicationContext applicationContext, CustomMethodSecurityInterceptor methodSecurityInterceptor*/) { - SecurityResourceService SecurityResourceService = new SecurityResourceService(resourcesRepository, roleHierarchy, roleHierarchyService, accessIpRepository/*, mapBasedMethodSecurityMetadataSource, applicationContext, methodSecurityInterceptor*/); - return SecurityResourceService; - } - -// @Bean -// public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { -// DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); -// return defaultAdvisorAutoProxyCreator; -// } -} diff --git a/src/main/java/io/security/corespringsecurity/controller/HomeController.java b/src/main/java/io/security/corespringsecurity/controller/HomeController.java index fbfc58d9..6a9933c2 100644 --- a/src/main/java/io/security/corespringsecurity/controller/HomeController.java +++ b/src/main/java/io/security/corespringsecurity/controller/HomeController.java @@ -1,25 +1,15 @@ package io.security.corespringsecurity.controller; -import io.security.corespringsecurity.security.authentication.services.UserDetailsServiceImpl; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class HomeController { - @Autowired - private UserDetailsServiceImpl userDetailsService; - @GetMapping(value="/") public String home() throws Exception { return "home"; } - @GetMapping(value="/login") - public String login() throws Exception { - return "login"; - } - } diff --git a/src/main/java/io/security/corespringsecurity/controller/admin/AdminController.java b/src/main/java/io/security/corespringsecurity/controller/admin/AdminController.java deleted file mode 100644 index fe6cc88c..00000000 --- a/src/main/java/io/security/corespringsecurity/controller/admin/AdminController.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.security.corespringsecurity.controller.admin; - - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; - -@Controller -public class AdminController { - - @GetMapping(value="/admin") - public String home() throws Exception { - return "admin/home"; - } - -} diff --git a/src/main/java/io/security/corespringsecurity/controller/admin/ResourcesController.java b/src/main/java/io/security/corespringsecurity/controller/admin/ResourcesController.java deleted file mode 100644 index c33afb6d..00000000 --- a/src/main/java/io/security/corespringsecurity/controller/admin/ResourcesController.java +++ /dev/null @@ -1,95 +0,0 @@ -package io.security.corespringsecurity.controller.admin; - - -import io.security.corespringsecurity.domain.dto.ResourcesDto; -import io.security.corespringsecurity.domain.entity.Resources; -import io.security.corespringsecurity.domain.entity.Role; -import io.security.corespringsecurity.repository.RoleRepository; -import io.security.corespringsecurity.service.MethodSecurityService; -import io.security.corespringsecurity.service.ResourcesService; -import io.security.corespringsecurity.service.RoleService; -import io.security.corespringsecurity.service.SecurityResourceService; -import org.modelmapper.ModelMapper; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -@Controller -public class ResourcesController { - - @Autowired - private ResourcesService resourcesService; - - @Autowired - private RoleRepository roleRepository; - - @Autowired - MethodSecurityService methodSecurityService; - - @Autowired - private RoleService roleService; - - @GetMapping(value="/admin/resources") - public String getResources(Model model) throws Exception { - - List resources = resourcesService.selectResources(); - model.addAttribute("resources", resources); - - return "admin/resource/list"; - } - - @PostMapping(value="/admin/resources") - public String createResources(ResourcesDto resourcesDto) throws Exception { - - ModelMapper modelMapper = new ModelMapper(); - Role role = roleRepository.findByRoleName(resourcesDto.getRoleName()); - Set roles = new HashSet<>(); - roles.add(role); - Resources resources = modelMapper.map(resourcesDto, Resources.class); - resources.setRoleSet(roles); - - resourcesService.insertResources(resources); - methodSecurityService.addMethodSecured(resourcesDto.getResourceName(),resourcesDto.getRoleName()); - - return "redirect:/admin/resources"; - } - - @GetMapping(value="/admin/resources/register") - public String viewRoles(Model model) throws Exception { - - List roleList = roleService.getRoles(); - model.addAttribute("roleList", roleList); - Resources resources = new Resources(); - model.addAttribute("resources", resources); - - return "admin/resource/detail"; - } - - @GetMapping(value="/admin/resources/{id}") - public String getResources(@PathVariable String id, Model model) throws Exception { - - List roleList = roleService.getRoles(); - model.addAttribute("roleList", roleList); - Resources resources = resourcesService.selectResources(Long.valueOf(id)); - model.addAttribute("resources", resources); - - return "admin/resource/detail"; - } - - @GetMapping(value="/admin/resources/delete/{id}") - public String removeResources(@PathVariable String id, Model model) throws Exception { - - Resources resources = resourcesService.selectResources(Long.valueOf(id)); - resourcesService.deleteResources(Long.valueOf(id)); - methodSecurityService.removeMethodSecured(resources.getResourceName()); - - return "redirect:/admin/resources"; - } -} diff --git a/src/main/java/io/security/corespringsecurity/controller/admin/RoleController.java b/src/main/java/io/security/corespringsecurity/controller/admin/RoleController.java deleted file mode 100644 index be02923b..00000000 --- a/src/main/java/io/security/corespringsecurity/controller/admin/RoleController.java +++ /dev/null @@ -1,52 +0,0 @@ -package io.security.corespringsecurity.controller.admin; - -import io.security.corespringsecurity.domain.dto.RoleDto; -import io.security.corespringsecurity.domain.entity.Role; -import io.security.corespringsecurity.service.RoleService; -import org.modelmapper.ModelMapper; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; - -import java.util.List; - -@Controller -public class RoleController { - - @Autowired - private RoleService roleService; - - @GetMapping(value="/admin/roles") - public String getRoles(Model model) throws Exception { - List roles = roleService.getRoles(); - model.addAttribute("roles", roles); - return "admin/role/list"; - } - - @GetMapping(value="/admin/roles/register") - public String viewRoles(Model model) throws Exception { - Role role = new Role(); - model.addAttribute("role", role); - return "admin/role/detail"; - } - - @PostMapping(value="/admin/roles") - public String createRole(RoleDto roleDto) throws Exception { - - ModelMapper modelMapper = new ModelMapper(); - Role role = modelMapper.map(roleDto, Role.class); - roleService.createRole(role); - - return "redirect:/admin/roles"; - } - - @GetMapping(value="/admin/roles/{id}") - public String getRole(@PathVariable String id, Model model) throws Exception { - Role role = roleService.getRole(Long.valueOf(id)); - model.addAttribute("role", role); - return "admin/role/detail"; - } -} diff --git a/src/main/java/io/security/corespringsecurity/controller/admin/UserManagerController.java b/src/main/java/io/security/corespringsecurity/controller/admin/UserManagerController.java deleted file mode 100644 index 78b84c59..00000000 --- a/src/main/java/io/security/corespringsecurity/controller/admin/UserManagerController.java +++ /dev/null @@ -1,62 +0,0 @@ -package io.security.corespringsecurity.controller.admin; - - -import io.security.corespringsecurity.domain.dto.UserDto; -import io.security.corespringsecurity.domain.entity.Role; -import io.security.corespringsecurity.domain.entity.User; -import io.security.corespringsecurity.service.RoleService; -import io.security.corespringsecurity.service.UserService; -import org.modelmapper.ModelMapper; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; - -import java.util.List; - -@Controller -public class UserManagerController { - - @Autowired - private UserService userService; - - @Autowired - private RoleService roleService; - - @GetMapping(value="/admin/users") - public String getUsers(Model model) throws Exception { - List users = userService.getUsers(); - model.addAttribute("users", users); - return "admin/user/list"; - } - - @PostMapping(value="/admin/users") - public String createUser(UserDto userDto) throws Exception { - - ModelMapper modelMapper = new ModelMapper(); - User user = modelMapper.map(userDto, User.class); - userService.createUser(user); - - return "redirect:/admin/users"; - } - - @GetMapping(value = "/admin/users/{id}") - public String getUser(@PathVariable(value = "id") Long id, Model model) { - UserDto userDto = userService.getUser(id); - List roleList = roleService.getRoles(); - - model.addAttribute("act", (id > 0)? "modify":"add"); - model.addAttribute("user", userDto); - model.addAttribute("roleList", roleList); - - return "admin/user/detail"; - } - - @GetMapping(value = "/admin/users/delete/{id}") - public String removeUser(@PathVariable(value = "id") Long id, Model model) { - userService.deleteUser(id); - return "redirect:/admin/users"; - } -} diff --git a/src/main/java/io/security/corespringsecurity/controller/login/LoginController.java b/src/main/java/io/security/corespringsecurity/controller/login/LoginController.java deleted file mode 100644 index aafd8e8e..00000000 --- a/src/main/java/io/security/corespringsecurity/controller/login/LoginController.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.security.corespringsecurity.controller.login; - - -import io.security.corespringsecurity.domain.dto.UserDto; -import io.security.corespringsecurity.domain.entity.User; -import io.security.corespringsecurity.service.RoleService; -import io.security.corespringsecurity.service.UserService; -import org.modelmapper.ModelMapper; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; - -@Controller -public class LoginController { - - @Autowired - private UserService userService; - - @Autowired - private RoleService roleService; - - @GetMapping(value="/denied") - public String accessDenied() throws Exception { - - return "user/login/denied"; - } -} diff --git a/src/main/java/io/security/corespringsecurity/controller/user/UserController.java b/src/main/java/io/security/corespringsecurity/controller/user/UserController.java deleted file mode 100644 index 1d4d33a1..00000000 --- a/src/main/java/io/security/corespringsecurity/controller/user/UserController.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.security.corespringsecurity.controller.user; - - -import io.security.corespringsecurity.domain.dto.UserDto; -import io.security.corespringsecurity.domain.entity.User; -import io.security.corespringsecurity.service.RoleService; -import io.security.corespringsecurity.service.UserService; -import org.modelmapper.ModelMapper; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; - -@Controller -public class UserController { - - @Autowired - private UserService userService; - - @Autowired - private RoleService roleService; - - @GetMapping(value="/users") - public String createUser() throws Exception { - - return "user/login/register"; - } - - @PostMapping(value="/users") - public String createUser(UserDto userDto) throws Exception { - - ModelMapper modelMapper = new ModelMapper(); - User user = modelMapper.map(userDto, User.class); - userService.createUser(user); - - return "redirect:/"; - } -} diff --git a/src/main/java/io/security/corespringsecurity/domain/dto/AccessIpDto.java b/src/main/java/io/security/corespringsecurity/domain/dto/AccessIpDto.java deleted file mode 100644 index 610e0eb5..00000000 --- a/src/main/java/io/security/corespringsecurity/domain/dto/AccessIpDto.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.security.corespringsecurity.domain.dto; - -import lombok.*; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -import javax.persistence.*; -import java.io.Serializable; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class AccessIpDto{ - - private Long id; - private String ipAddress; - -} diff --git a/src/main/java/io/security/corespringsecurity/domain/dto/ResourcesDto.java b/src/main/java/io/security/corespringsecurity/domain/dto/ResourcesDto.java deleted file mode 100644 index 4f15ae8e..00000000 --- a/src/main/java/io/security/corespringsecurity/domain/dto/ResourcesDto.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.security.corespringsecurity.domain.dto; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class ResourcesDto{ - - private String resourceName; - private String httpMethod; - private int orderNum; - private String resourceType; - private String roleName; - -} diff --git a/src/main/java/io/security/corespringsecurity/domain/dto/RoleDto.java b/src/main/java/io/security/corespringsecurity/domain/dto/RoleDto.java deleted file mode 100644 index 70a5523b..00000000 --- a/src/main/java/io/security/corespringsecurity/domain/dto/RoleDto.java +++ /dev/null @@ -1,22 +0,0 @@ - -package io.security.corespringsecurity.domain.dto; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class RoleDto{ - - private String roleName; - private String roleDesc; - -} - - diff --git a/src/main/java/io/security/corespringsecurity/domain/dto/UserDto.java b/src/main/java/io/security/corespringsecurity/domain/dto/UserDto.java deleted file mode 100644 index 8ecb8e12..00000000 --- a/src/main/java/io/security/corespringsecurity/domain/dto/UserDto.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.security.corespringsecurity.domain.dto; - -import io.security.corespringsecurity.domain.entity.Role; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.List; -import java.util.Set; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class UserDto { - - private String username; - private String email; - private int age; - private String password; - private List roles; -} - - diff --git a/src/main/java/io/security/corespringsecurity/domain/entity/AccessIp.java b/src/main/java/io/security/corespringsecurity/domain/entity/AccessIp.java deleted file mode 100644 index ee4f87c8..00000000 --- a/src/main/java/io/security/corespringsecurity/domain/entity/AccessIp.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.security.corespringsecurity.domain.entity; - -import lombok.*; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -import javax.persistence.*; -import java.io.Serializable; - -@Entity -@Table(name = "ACCESS_IP") -@Data -@EntityListeners(value = { AuditingEntityListener.class}) -@EqualsAndHashCode(of = "id") -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class AccessIp implements Serializable { - - @Id - @GeneratedValue - @Column(name = "IP_ID", unique = true, nullable = false) - private Long id; - - @Column(name = "IP_ADDRESS", nullable = false) - private String ipAddress; - -} diff --git a/src/main/java/io/security/corespringsecurity/domain/entity/PersistentLogin.java b/src/main/java/io/security/corespringsecurity/domain/entity/PersistentLogin.java deleted file mode 100644 index 9d74d24a..00000000 --- a/src/main/java/io/security/corespringsecurity/domain/entity/PersistentLogin.java +++ /dev/null @@ -1,41 +0,0 @@ -package io.security.corespringsecurity.domain.entity; - -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.ToString; -import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken; - -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; -import java.io.Serializable; -import java.util.Date; - -@Entity -@Table(name = "PERSISTENT_LOGINS") -@Getter -@Setter -@ToString -@NoArgsConstructor -public class PersistentLogin implements Serializable { - - - @Id - private String series; - - private String username; - private String token; - private Date lastUsed; - - public PersistentLogin(PersistentRememberMeToken token){ - this.series = token.getSeries(); - this.username = token.getUsername(); - this.token = token.getTokenValue(); - this.lastUsed = token.getDate(); - } - - private static final long serialVersionUID = 8433999509932007961L; - - -} diff --git a/src/main/java/io/security/corespringsecurity/domain/entity/Resources.java b/src/main/java/io/security/corespringsecurity/domain/entity/Resources.java deleted file mode 100644 index 4692646d..00000000 --- a/src/main/java/io/security/corespringsecurity/domain/entity/Resources.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.security.corespringsecurity.domain.entity; - -import lombok.*; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -import javax.persistence.*; -import java.io.Serializable; -import java.util.HashSet; -import java.util.Set; - -@Entity -@Table(name = "RESOURCES") -@Data -@ToString(exclude = {"roleSet"}) -@EntityListeners(value = { AuditingEntityListener.class }) -@EqualsAndHashCode(of = "id") -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class Resources implements Serializable { - - @Id - @GeneratedValue - @Column - private Long id; - - @Column(name = "resource_name") - private String resourceName; - - @Column(name = "http_method") - private String httpMethod; - - @Column(name = "order_num") - private int orderNum; - - @Column(name = "resource_type") - private String resourceType; - - @ManyToMany(fetch = FetchType.LAZY) - @JoinTable(name = "role_resources", joinColumns = { - @JoinColumn(name = "resource_id") }, inverseJoinColumns = { @JoinColumn(name = "role_id") }) - private Set roleSet = new HashSet<>(); - -} diff --git a/src/main/java/io/security/corespringsecurity/domain/entity/Role.java b/src/main/java/io/security/corespringsecurity/domain/entity/Role.java deleted file mode 100644 index 336f51d1..00000000 --- a/src/main/java/io/security/corespringsecurity/domain/entity/Role.java +++ /dev/null @@ -1,43 +0,0 @@ - -package io.security.corespringsecurity.domain.entity; - -import lombok.*; - -import javax.persistence.*; -import java.io.Serializable; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Set; - -@Entity -@Table(name = "ROLE") -@Getter -@Setter -@ToString(exclude = {"users","resourcesSet"}) -@Builder -@NoArgsConstructor -@AllArgsConstructor -@EqualsAndHashCode(of = "id") -public class Role implements Serializable { - - @Id - @GeneratedValue - @Column(name = "role_id") - private Long id; - - @Column(name = "role_name") - private String roleName; - - @Column(name = "role_desc") - private String roleDesc; - - @ManyToMany(fetch = FetchType.LAZY, mappedBy = "roleSet") - @OrderBy("ordernum desc") - private Set resourcesSet = new LinkedHashSet<>(); - - @ManyToMany(fetch = FetchType.LAZY, mappedBy = "userRoles") - private Set users = new HashSet<>(); - -} - - diff --git a/src/main/java/io/security/corespringsecurity/domain/entity/RoleHierarchy.java b/src/main/java/io/security/corespringsecurity/domain/entity/RoleHierarchy.java deleted file mode 100644 index 3fa62b8c..00000000 --- a/src/main/java/io/security/corespringsecurity/domain/entity/RoleHierarchy.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.security.corespringsecurity.domain.entity; - -import com.fasterxml.jackson.annotation.JsonIdentityInfo; -import com.fasterxml.jackson.annotation.ObjectIdGenerators; -import lombok.*; - -import javax.persistence.*; -import java.io.Serializable; -import java.util.HashSet; -import java.util.Set; - -@Entity -@Table(name="ROLE_HIERARCHY") -@Getter -@Setter -@AllArgsConstructor -@NoArgsConstructor -@Builder -@ToString(exclude = {"parentName", "roleHierarchy"}) -@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class) -public class RoleHierarchy implements Serializable { - - @Id - @GeneratedValue - private Long id; - - @Column(name = "child_name") - private String childName; - - @ManyToOne(cascade = {CascadeType.ALL},fetch = FetchType.LAZY) - @JoinColumn(name = "parent_name", referencedColumnName = "child_name") - private RoleHierarchy parentName; - - @OneToMany(mappedBy = "parentName", cascade={CascadeType.ALL}) - private Set roleHierarchy = new HashSet(); -} \ No newline at end of file diff --git a/src/main/java/io/security/corespringsecurity/domain/entity/User.java b/src/main/java/io/security/corespringsecurity/domain/entity/User.java deleted file mode 100644 index 989fcc70..00000000 --- a/src/main/java/io/security/corespringsecurity/domain/entity/User.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.security.corespringsecurity.domain.entity; - -import lombok.*; - -import javax.persistence.*; -import java.io.Serializable; -import java.util.HashSet; -import java.util.Set; - -@Entity -@Table(name = "users") -@Data -@ToString(exclude = {"userRoles"}) -@Builder -@EqualsAndHashCode(of = "id") -@NoArgsConstructor -@AllArgsConstructor -public class User implements Serializable { - - @Id - @GeneratedValue - private Long id; - - @Column - private String username; - - @Column - private String email; - - @Column - private int age; - - @Column - private String password; - - @ManyToMany(fetch = FetchType.LAZY, cascade={CascadeType.ALL}) - @JoinTable(name = "user_roles", joinColumns = { @JoinColumn(name = "user_id") }, inverseJoinColumns = { - @JoinColumn(name = "role_id") }) - private Set userRoles = new HashSet<>(); -} - - diff --git a/src/main/java/io/security/corespringsecurity/repository/AccessIpRepository.java b/src/main/java/io/security/corespringsecurity/repository/AccessIpRepository.java deleted file mode 100644 index 85312901..00000000 --- a/src/main/java/io/security/corespringsecurity/repository/AccessIpRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.security.corespringsecurity.repository; - - -import io.security.corespringsecurity.domain.entity.AccessIp; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface AccessIpRepository extends JpaRepository { - - AccessIp findByIpAddress(String IpAddress); - -} diff --git a/src/main/java/io/security/corespringsecurity/repository/JpaPersistentTokenRepository.java b/src/main/java/io/security/corespringsecurity/repository/JpaPersistentTokenRepository.java deleted file mode 100644 index 24b0d6ec..00000000 --- a/src/main/java/io/security/corespringsecurity/repository/JpaPersistentTokenRepository.java +++ /dev/null @@ -1,49 +0,0 @@ -package io.security.corespringsecurity.repository; - -import io.security.corespringsecurity.domain.entity.PersistentLogin; -import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken; -import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; - -import java.util.Date; -import java.util.List; - -public class JpaPersistentTokenRepository implements PersistentTokenRepository { - - private final RememberMeTokenRepository rememberMeTokenRepository; - - public JpaPersistentTokenRepository(RememberMeTokenRepository rememberMeTokenRepository) { - this.rememberMeTokenRepository = rememberMeTokenRepository; - } - - @Override - public void createNewToken(PersistentRememberMeToken token) { - PersistentLogin newToken = new PersistentLogin(token); - this.rememberMeTokenRepository.save(newToken); - } - - @Override - public void updateToken(String series, String tokenValue, Date lastUsed) { - PersistentLogin token = this.rememberMeTokenRepository.findBySeries(series); - if (token != null) { - token.setToken(tokenValue); - token.setLastUsed(lastUsed); - this.rememberMeTokenRepository.save(token); - } - } - - @Override - public PersistentRememberMeToken getTokenForSeries(String seriesId) { - PersistentLogin token = this.rememberMeTokenRepository.findBySeries(seriesId); - return new PersistentRememberMeToken(token.getUsername(), - token.getSeries(), - token.getToken(), - token.getLastUsed()); - } - - @Override - public void removeUserTokens(String username) { - List tokens = this.rememberMeTokenRepository.findByUsername(username); - this.rememberMeTokenRepository.deleteAll(tokens); - } - -} diff --git a/src/main/java/io/security/corespringsecurity/repository/JpaTokenRepositoryCleaner.java b/src/main/java/io/security/corespringsecurity/repository/JpaTokenRepositoryCleaner.java deleted file mode 100644 index b4365e2f..00000000 --- a/src/main/java/io/security/corespringsecurity/repository/JpaTokenRepositoryCleaner.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.security.corespringsecurity.repository; - -import io.security.corespringsecurity.domain.entity.PersistentLogin; -import lombok.extern.slf4j.Slf4j; - -import java.util.Date; - -@Slf4j -public class JpaTokenRepositoryCleaner implements Runnable { - - private final RememberMeTokenRepository rememberMeTokenRepository; - - private final long tokenValidityInMs; - - - public JpaTokenRepositoryCleaner(RememberMeTokenRepository rememberMeTokenRepository, long tokenValidityInMs) { - if (rememberMeTokenRepository == null) { - throw new IllegalArgumentException("jdbcOperations cannot be null"); - } - if (tokenValidityInMs < 1) { - throw new IllegalArgumentException("tokenValidityInMs must be greater than 0. Got " + tokenValidityInMs); - } - this.rememberMeTokenRepository = rememberMeTokenRepository; - this.tokenValidityInMs = tokenValidityInMs; - } - - public void run() { - long expiredInMs = System.currentTimeMillis() - tokenValidityInMs; - - log.info("Searching for persistent logins older than {}ms", tokenValidityInMs); - - try { - Iterable expired = rememberMeTokenRepository.findByLastUsedAfter(new Date(expiredInMs)); - for(PersistentLogin pl: expired){ - log.info("*** Removing persistent login for {} ***", pl.getUsername()); - rememberMeTokenRepository.delete(pl); - } - } catch(Throwable t) { - log.error("**** Could not clean up expired persistent remember me tokens. ***", t); - } - } -} diff --git a/src/main/java/io/security/corespringsecurity/repository/RememberMeTokenRepository.java b/src/main/java/io/security/corespringsecurity/repository/RememberMeTokenRepository.java deleted file mode 100644 index 3ab5592b..00000000 --- a/src/main/java/io/security/corespringsecurity/repository/RememberMeTokenRepository.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.security.corespringsecurity.repository; - - -import io.security.corespringsecurity.domain.entity.PersistentLogin; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Date; -import java.util.List; - -public interface RememberMeTokenRepository extends JpaRepository { - - PersistentLogin findBySeries(String series); - List findByUsername(String username); - Iterable findByLastUsedAfter(Date expiration); - -} diff --git a/src/main/java/io/security/corespringsecurity/repository/ResourcesRepository.java b/src/main/java/io/security/corespringsecurity/repository/ResourcesRepository.java deleted file mode 100644 index c18ea51d..00000000 --- a/src/main/java/io/security/corespringsecurity/repository/ResourcesRepository.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.security.corespringsecurity.repository; - -import io.security.corespringsecurity.domain.entity.Resources; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; - -import java.util.List; - -public interface ResourcesRepository extends JpaRepository { - - Resources findByResourceNameAndHttpMethod(String resourceName, String httpMethod); - - @Query("select r from Resources r join fetch r.roleSet where r.resourceType = 'url' order by r.orderNum desc") - List findAllResources(); - - @Query("select r from Resources r join fetch r.roleSet where r.resourceType = 'method' order by r.orderNum desc") - List findAllMethodResources(); - - @Query("select r from Resources r join fetch r.roleSet where r.resourceType = 'pointcut' order by r.orderNum desc") - List findAllPointcutResources(); -} \ No newline at end of file diff --git a/src/main/java/io/security/corespringsecurity/repository/RoleHierarchyRepository.java b/src/main/java/io/security/corespringsecurity/repository/RoleHierarchyRepository.java deleted file mode 100644 index 2a286f48..00000000 --- a/src/main/java/io/security/corespringsecurity/repository/RoleHierarchyRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.security.corespringsecurity.repository; - - -import io.security.corespringsecurity.domain.entity.RoleHierarchy; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface RoleHierarchyRepository extends JpaRepository { - - RoleHierarchy findByChildName(String roleName); -} diff --git a/src/main/java/io/security/corespringsecurity/repository/RoleRepository.java b/src/main/java/io/security/corespringsecurity/repository/RoleRepository.java deleted file mode 100644 index f78e6519..00000000 --- a/src/main/java/io/security/corespringsecurity/repository/RoleRepository.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.security.corespringsecurity.repository; - - -import io.security.corespringsecurity.domain.entity.Role; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface RoleRepository extends JpaRepository { - - Role findByRoleName(String name); - - @Override - void delete(Role role); - -} diff --git a/src/main/java/io/security/corespringsecurity/repository/UserRepository.java b/src/main/java/io/security/corespringsecurity/repository/UserRepository.java deleted file mode 100644 index f62ba41e..00000000 --- a/src/main/java/io/security/corespringsecurity/repository/UserRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.security.corespringsecurity.repository; - -import io.security.corespringsecurity.domain.entity.User; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface UserRepository extends JpaRepository { - - User findByUsername(String username); - - int countByUsername(String username); - - @Override - void delete(User user); - -} \ No newline at end of file diff --git a/src/main/java/io/security/corespringsecurity/security/aop/CustomMethodSecurityInterceptor.java b/src/main/java/io/security/corespringsecurity/security/aop/CustomMethodSecurityInterceptor.java deleted file mode 100644 index 4e38842f..00000000 --- a/src/main/java/io/security/corespringsecurity/security/aop/CustomMethodSecurityInterceptor.java +++ /dev/null @@ -1,43 +0,0 @@ -package io.security.corespringsecurity.security.aop; - -import org.springframework.security.access.SecurityMetadataSource; -import org.springframework.security.access.intercept.AbstractSecurityInterceptor; -import org.springframework.security.access.intercept.InterceptorStatusToken; -import org.springframework.security.access.method.MethodSecurityMetadataSource; - -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; - -public class CustomMethodSecurityInterceptor extends AbstractSecurityInterceptor implements - MethodInterceptor { - private MethodSecurityMetadataSource securityMetadataSource; - - public Class getSecureObjectClass() { - return MethodInvocation.class; - } - - public Object invoke(MethodInvocation mi) throws Throwable { - InterceptorStatusToken token = super.beforeInvocation(mi); - - Object result; - try { - result = mi.proceed(); - } - finally { - super.finallyInvocation(token); - } - return super.afterInvocation(token, result); - } - - public MethodSecurityMetadataSource getSecurityMetadataSource() { - return this.securityMetadataSource; - } - - public SecurityMetadataSource obtainSecurityMetadataSource() { - return this.securityMetadataSource; - } - - public void setSecurityMetadataSource(MethodSecurityMetadataSource newSource) { - this.securityMetadataSource = newSource; - } -} diff --git a/src/main/java/io/security/corespringsecurity/security/authentication/handler/AjaxAuthenticationFailureHandler.java b/src/main/java/io/security/corespringsecurity/security/authentication/handler/AjaxAuthenticationFailureHandler.java deleted file mode 100644 index c90f9310..00000000 --- a/src/main/java/io/security/corespringsecurity/security/authentication/handler/AjaxAuthenticationFailureHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.security.corespringsecurity.security.authentication.handler; - -import com.fasterxml.jackson.databind.ObjectMapper; -import io.security.corespringsecurity.security.exception.AuthMethodNotSupportedException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.stereotype.Component; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -@Component("ajaxAuthenticationFailureHandler") -public class AjaxAuthenticationFailureHandler implements AuthenticationFailureHandler { - private final ObjectMapper mapper; - - @Autowired - public AjaxAuthenticationFailureHandler(ObjectMapper mapper) { - this.mapper = mapper; - } - - @Override - public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, - AuthenticationException e) throws IOException, ServletException { - - response.setStatus(HttpStatus.UNAUTHORIZED.value()); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - - if (e instanceof BadCredentialsException) { - mapper.writeValue(response.getWriter(), "Invalid username or password"); - } else if (e instanceof AuthMethodNotSupportedException) { - mapper.writeValue(response.getWriter(), e.getMessage()); - } - - mapper.writeValue(response.getWriter(), "Authentication failed"); - } -} diff --git a/src/main/java/io/security/corespringsecurity/security/authentication/handler/AjaxAuthenticationSuccessHandler.java b/src/main/java/io/security/corespringsecurity/security/authentication/handler/AjaxAuthenticationSuccessHandler.java deleted file mode 100644 index ae308854..00000000 --- a/src/main/java/io/security/corespringsecurity/security/authentication/handler/AjaxAuthenticationSuccessHandler.java +++ /dev/null @@ -1,49 +0,0 @@ -package io.security.corespringsecurity.security.authentication.handler; - -import com.fasterxml.jackson.databind.ObjectMapper; -import io.security.corespringsecurity.security.domain.UserDto; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.WebAttributes; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.stereotype.Component; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import java.io.IOException; - -@Component("ajaxAuthenticationSuccessHandler") -public class AjaxAuthenticationSuccessHandler implements AuthenticationSuccessHandler { - - private final ObjectMapper mapper; - - @Autowired - public AjaxAuthenticationSuccessHandler(final ObjectMapper mapper) { - this.mapper = mapper; - } - - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException, ServletException { - UserDto userDto = (UserDto) authentication.getPrincipal(); - response.setStatus(HttpStatus.OK.value()); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - mapper.writeValue(response.getWriter(), userDto); - - clearAuthenticationAttributes(request); - } - - protected final void clearAuthenticationAttributes(HttpServletRequest request) { - HttpSession session = request.getSession(false); - - if (session == null) { - return; - } - - session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); - } -} diff --git a/src/main/java/io/security/corespringsecurity/security/authentication/handler/CommonAccessDeniedHandler.java b/src/main/java/io/security/corespringsecurity/security/authentication/handler/CommonAccessDeniedHandler.java deleted file mode 100644 index a81e5e1f..00000000 --- a/src/main/java/io/security/corespringsecurity/security/authentication/handler/CommonAccessDeniedHandler.java +++ /dev/null @@ -1,61 +0,0 @@ -package io.security.corespringsecurity.security.authentication.handler; - -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.web.DefaultRedirectStrategy; -import org.springframework.security.web.RedirectStrategy; -import org.springframework.security.web.access.AccessDeniedHandler; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -public class CommonAccessDeniedHandler implements AccessDeniedHandler { - - private String errorPage; - - private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); - - @Override - public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { - - // Ajax를 통해 들어온것인지 파악한다 - String ajaxHeader = request.getHeader("X-Ajax-call"); - String result = ""; - - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - response.setCharacterEncoding("UTF-8"); - - if(ajaxHeader == null){ // null로 받은 경우는 X-Ajax-call 헤더 변수가 없다는 의미이기 때문에 ajax가 아닌 일반적인 방법으로 접근했음을 의미한다 - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - Object principal = auth.getPrincipal(); - if (principal instanceof UserDetails) { - String username = ((UserDetails) principal).getUsername(); - request.setAttribute("username", username); - } - request.setAttribute("errormsg", accessDeniedException); - redirectStrategy.sendRedirect(request, response, errorPage); - - }else{ - if("true".equals(ajaxHeader)){ // true로 값을 받았다는 것은 ajax로 접근했음을 의미한다 - result = "{\"result\" : \"fail\", \"message\" : \"" + accessDeniedException.getMessage() + "\"}"; - }else{ // 헤더 변수는 있으나 값이 틀린 경우이므로 헤더값이 틀렸다는 의미로 돌려준다 - result = "{\"result\" : \"fail\", \"message\" : \"Access Denied(Header Value Mismatch)\"}"; - } - response.getWriter().print(result); - response.getWriter().flush(); - } - } - - public void setErrorPage(String errorPage) { - if ((errorPage != null) && !errorPage.startsWith("/")) { - throw new IllegalArgumentException("errorPage must begin with '/'"); - } - - this.errorPage = errorPage; - } - -} diff --git a/src/main/java/io/security/corespringsecurity/security/authentication/handler/CommonLogoutSuccessHandler.java b/src/main/java/io/security/corespringsecurity/security/authentication/handler/CommonLogoutSuccessHandler.java deleted file mode 100644 index 101ffc35..00000000 --- a/src/main/java/io/security/corespringsecurity/security/authentication/handler/CommonLogoutSuccessHandler.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.security.corespringsecurity.security.authentication.handler; - -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; -import org.springframework.stereotype.Component; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import java.io.IOException; - -@Component("logoutSuccessHandler") -public class CommonLogoutSuccessHandler implements LogoutSuccessHandler { - - @Override - public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { - final HttpSession session = request.getSession(); - if (session != null) { - session.removeAttribute("user"); - session.invalidate(); - } - - response.sendRedirect("/"); - } -} diff --git a/src/main/java/io/security/corespringsecurity/security/authentication/handler/FormAuthenticationFailureHandler.java b/src/main/java/io/security/corespringsecurity/security/authentication/handler/FormAuthenticationFailureHandler.java deleted file mode 100644 index ab81946a..00000000 --- a/src/main/java/io/security/corespringsecurity/security/authentication/handler/FormAuthenticationFailureHandler.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.security.corespringsecurity.security.authentication.handler; - -import io.security.corespringsecurity.security.exception.AuthMethodNotSupportedException; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.WebAttributes; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; -import org.springframework.stereotype.Component; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -@Component("formAuthenticationFailureHandler") -public class FormAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { - - @Override - public void onAuthenticationFailure(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException exception) throws IOException, ServletException { - - setDefaultFailureUrl("/login?error=true"); - - super.onAuthenticationFailure(request, response, exception); - - String errorMessage = "Authentication failed"; - - if (exception instanceof BadCredentialsException) { - errorMessage = "Invalid username or password"; - } else if (exception instanceof AuthMethodNotSupportedException) { - errorMessage = exception.getMessage(); - } - - request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, ""); - } -} \ No newline at end of file diff --git a/src/main/java/io/security/corespringsecurity/security/authentication/handler/FormAuthenticationSuccessHandler.java b/src/main/java/io/security/corespringsecurity/security/authentication/handler/FormAuthenticationSuccessHandler.java deleted file mode 100644 index 944b2818..00000000 --- a/src/main/java/io/security/corespringsecurity/security/authentication/handler/FormAuthenticationSuccessHandler.java +++ /dev/null @@ -1,41 +0,0 @@ -package io.security.corespringsecurity.security.authentication.handler; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.DefaultRedirectStrategy; -import org.springframework.security.web.RedirectStrategy; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; -import org.springframework.security.web.savedrequest.HttpSessionRequestCache; -import org.springframework.security.web.savedrequest.RequestCache; -import org.springframework.security.web.savedrequest.SavedRequest; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -@Component("formAuthenticationSuccessHandler") -@Slf4j -public class FormAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { - - private RequestCache requestCache = new HttpSessionRequestCache(); - - private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); - - @Override - public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication) throws IOException { - - setDefaultTargetUrl("/"); - - SavedRequest savedRequest = requestCache.getRequest(request, response); - - if(savedRequest!=null) { - String targetUrl = savedRequest.getRedirectUrl(); - redirectStrategy.sendRedirect(request, response, targetUrl); - } else { - redirectStrategy.sendRedirect(request, response, getDefaultTargetUrl()); - } - - } - -} \ No newline at end of file diff --git a/src/main/java/io/security/corespringsecurity/security/authentication/provider/AjaxAuthenticationProvider.java b/src/main/java/io/security/corespringsecurity/security/authentication/provider/AjaxAuthenticationProvider.java deleted file mode 100644 index 16e08121..00000000 --- a/src/main/java/io/security/corespringsecurity/security/authentication/provider/AjaxAuthenticationProvider.java +++ /dev/null @@ -1,82 +0,0 @@ -package io.security.corespringsecurity.security.authentication.provider; - -import io.security.corespringsecurity.security.domain.UserDto; -import io.security.corespringsecurity.security.token.AjaxAuthenticationToken; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Component; - -import javax.transaction.Transactional; -import java.util.Arrays; - - -@Component -@Slf4j -public class AjaxAuthenticationProvider implements AuthenticationProvider { - - @Autowired - private UserDetailsService uerDetailsService; - - @Autowired - PasswordEncoder passwordEncoder; - - @Override - @Transactional - public Authentication authenticate(Authentication auth) throws AuthenticationException { - - String loginId = auth.getName(); - String passwd = (String) auth.getCredentials(); - - UserDetails userDetails = null; - try { - - // 사용자 조회 - userDetails = uerDetailsService.loadUserByUsername(loginId); - - if (userDetails == null || !passwordEncoder.matches(passwd, userDetails.getPassword())) { - throw new BadCredentialsException("Invalid password"); - } - - if (!userDetails.isEnabled()) { - throw new BadCredentialsException("not user confirm"); - } - - } catch(UsernameNotFoundException e) { - log.info(e.toString()); - throw new UsernameNotFoundException(e.getMessage()); - } catch(BadCredentialsException e) { - log.info(e.toString()); - throw new BadCredentialsException(e.getMessage()); - } catch(Exception e) { - log.info(e.toString()); - throw new RuntimeException(e.getMessage()); - } - - UserDto userDto = UserDto.builder().username(userDetails.getUsername()).password(userDetails.getPassword()).build(); - userDto.setRoles(Arrays.asList("ROLE_USER")); - - return AjaxAuthenticationToken.getTokenFromAccountContext(userDto); - } - - private boolean isValidLong(String code) { - try { - Long.parseLong(code); - } catch (final NumberFormatException e) { - return false; - } - return true; - } - - @Override - public boolean supports(Class authentication) { - return authentication.equals(AjaxAuthenticationToken.class); - } -} diff --git a/src/main/java/io/security/corespringsecurity/security/authentication/provider/FormAuthenticationProvider.java b/src/main/java/io/security/corespringsecurity/security/authentication/provider/FormAuthenticationProvider.java deleted file mode 100644 index 5818f0c6..00000000 --- a/src/main/java/io/security/corespringsecurity/security/authentication/provider/FormAuthenticationProvider.java +++ /dev/null @@ -1,68 +0,0 @@ -package io.security.corespringsecurity.security.authentication.provider; - -import io.security.corespringsecurity.security.authentication.services.UserDetail; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Component; - -import javax.transaction.Transactional; - -@Component -@Slf4j -public class FormAuthenticationProvider implements AuthenticationProvider { - - @Autowired - private UserDetailsService userDetailsService; - - @Autowired - PasswordEncoder passwordEncoder; - - @Override - @Transactional - public Authentication authenticate(Authentication auth) throws AuthenticationException { - - String loginId = auth.getName(); - String passwd = (String) auth.getCredentials(); - - UserDetails userDetails = null; - try { - - // 사용자 조회 - userDetails = userDetailsService.loadUserByUsername(loginId); - - if (userDetails == null || !passwordEncoder.matches(passwd, userDetails.getPassword())) { - throw new BadCredentialsException("Invalid password"); - } - - if (!userDetails.isEnabled()) { - throw new BadCredentialsException("not user confirm"); - } - - } catch(UsernameNotFoundException e) { - log.info(e.toString()); - throw new UsernameNotFoundException(e.getMessage()); - } catch(BadCredentialsException e) { - log.info(e.toString()); - throw new BadCredentialsException(e.getMessage()); - } catch(Exception e) { - log.info(e.toString()); - throw new RuntimeException(e.getMessage()); - } - - return new UsernamePasswordAuthenticationToken(((UserDetail)userDetails).getUser(), null, userDetails.getAuthorities()); - } - - @Override - public boolean supports(Class authentication) { - return authentication.equals(UsernamePasswordAuthenticationToken.class); - } -} diff --git a/src/main/java/io/security/corespringsecurity/security/authentication/services/FormRememberMeServices.java b/src/main/java/io/security/corespringsecurity/security/authentication/services/FormRememberMeServices.java deleted file mode 100644 index c51854e7..00000000 --- a/src/main/java/io/security/corespringsecurity/security/authentication/services/FormRememberMeServices.java +++ /dev/null @@ -1,70 +0,0 @@ -package io.security.corespringsecurity.security.authentication.services; - -import io.security.corespringsecurity.domain.entity.User; -import io.security.corespringsecurity.repository.UserRepository; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.authentication.RememberMeAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; -import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken; -import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices; -import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.Date; - -@Slf4j -public class FormRememberMeServices extends PersistentTokenBasedRememberMeServices { - - @Autowired - private UserRepository userRepository; - - private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); - private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); - private PersistentTokenRepository tokenRepository; - private String key; - - public FormRememberMeServices(String key, UserDetailsService userDetailsService, PersistentTokenRepository tokenRepository) { - super(key, userDetailsService, tokenRepository); - this.tokenRepository = tokenRepository; - this.key = key; - } - - @Override - public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { - super.logout(request, response, authentication); - } - - @Override - protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) { - String username = ((User) successfulAuthentication.getPrincipal()).getUsername(); - log.debug("Creating new persistent login for user " + username); - PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, generateSeriesData(), generateTokenData(), new Date()); - try { - tokenRepository.createNewToken(persistentToken); - addCookie(persistentToken, request, response); - } catch (Exception e) { - log.error("Failed to save persistent token ", e); - } - } - - @Override - protected Authentication createSuccessfulAuthentication(HttpServletRequest request, UserDetails user) { - User auser = userRepository.findByUsername(user.getUsername()); - RememberMeAuthenticationToken auth = new RememberMeAuthenticationToken(key, auser, authoritiesMapper.mapAuthorities(user.getAuthorities())); - auth.setDetails(authenticationDetailsSource.buildDetails(request)); - return auth; - } - - private void addCookie(PersistentRememberMeToken token, HttpServletRequest request, HttpServletResponse response) { - //setTokenValiditySeconds(60); - setCookie(new String[] { token.getSeries(), token.getTokenValue() }, getTokenValiditySeconds(), request, response); - } -} diff --git a/src/main/java/io/security/corespringsecurity/security/authentication/services/FormWebAuthenticationDetails.java b/src/main/java/io/security/corespringsecurity/security/authentication/services/FormWebAuthenticationDetails.java deleted file mode 100644 index 95879208..00000000 --- a/src/main/java/io/security/corespringsecurity/security/authentication/services/FormWebAuthenticationDetails.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.security.corespringsecurity.security.authentication.services; - -import org.springframework.security.web.authentication.WebAuthenticationDetails; - -import javax.servlet.http.HttpServletRequest; - -public class FormWebAuthenticationDetails extends WebAuthenticationDetails { - - private static final long serialVersionUID = 1L; - - private final String verificationCode; - - public FormWebAuthenticationDetails(HttpServletRequest request) { - super(request); - verificationCode = request.getParameter("code"); - } - - public String getVerificationCode() { - - return verificationCode; - } -} \ No newline at end of file diff --git a/src/main/java/io/security/corespringsecurity/security/authentication/services/FormWebAuthenticationDetailsSource.java b/src/main/java/io/security/corespringsecurity/security/authentication/services/FormWebAuthenticationDetailsSource.java deleted file mode 100644 index 44090be5..00000000 --- a/src/main/java/io/security/corespringsecurity/security/authentication/services/FormWebAuthenticationDetailsSource.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.security.corespringsecurity.security.authentication.services; - -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.web.authentication.WebAuthenticationDetails; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; - -@Component -public class FormWebAuthenticationDetailsSource implements AuthenticationDetailsSource { - @Override - public WebAuthenticationDetails buildDetails(HttpServletRequest request) { - return new FormWebAuthenticationDetails(request); - } -} \ No newline at end of file diff --git a/src/main/java/io/security/corespringsecurity/security/authentication/services/UserDetail.java b/src/main/java/io/security/corespringsecurity/security/authentication/services/UserDetail.java deleted file mode 100644 index fa0725db..00000000 --- a/src/main/java/io/security/corespringsecurity/security/authentication/services/UserDetail.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.security.corespringsecurity.security.authentication.services; - -import io.security.corespringsecurity.domain.entity.User; -import lombok.Data; -import org.springframework.security.core.authority.SimpleGrantedAuthority; - -import java.util.List; -import java.util.stream.Collectors; - -@Data -public class UserDetail extends org.springframework.security.core.userdetails.User { - private User user; - private List roles; - - public UserDetail(User user, List roles) { - super(user.getUsername(), user.getPassword(), roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList())); - this.user = user; - this.roles = roles; - } -} diff --git a/src/main/java/io/security/corespringsecurity/security/authentication/services/UserDetailsServiceImpl.java b/src/main/java/io/security/corespringsecurity/security/authentication/services/UserDetailsServiceImpl.java deleted file mode 100644 index 16d52ae3..00000000 --- a/src/main/java/io/security/corespringsecurity/security/authentication/services/UserDetailsServiceImpl.java +++ /dev/null @@ -1,85 +0,0 @@ -package io.security.corespringsecurity.security.authentication.services; - -import io.security.corespringsecurity.domain.entity.Role; -import io.security.corespringsecurity.domain.entity.User; -import io.security.corespringsecurity.repository.RoleRepository; -import io.security.corespringsecurity.repository.UserRepository; -import io.security.corespringsecurity.service.impl.LoginAttemptServiceImpl; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import javax.servlet.http.HttpServletRequest; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -@Slf4j -@Service("userDetailsService") -public class UserDetailsServiceImpl implements UserDetailsService { - - @Autowired - private UserRepository userRepository; - - @Autowired - private RoleRepository roleRepository; - - @Autowired - private LoginAttemptServiceImpl loginAttemptService; - - @Autowired - private HttpServletRequest request; - - @Autowired - private PasswordEncoder passwordEncoder; - - - - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - - final String ip = request.getRemoteAddr(); - if (loginAttemptService.isBlocked(ip)) { - throw new RuntimeException("blocked"); - } - - User user = userRepository.findByUsername(username); - if (user == null) { - if (userRepository.countByUsername(username) == 0) { - throw new UsernameNotFoundException("No user found with username: " + username); - } - } - Set userRoles = user.getUserRoles() - .stream() - .map(userRole -> userRole.getRoleName()) - .collect(Collectors.toSet()); - - return new UserDetail(user, userRoles.stream().collect(Collectors.toList())); - } - - @Transactional - public User selectUser(long id) { - return userRepository.findById(id).orElse(new User()); - } - - @Transactional - public List selectUsers() { - return userRepository.findAll(); - } - - @Transactional - public void insertUser(User user){ - - Role role = roleRepository.findByRoleName("ROLE_USER"); - Set roles = new HashSet<>(); - roles.add(role); - user.setUserRoles(roles); - user.setPassword(passwordEncoder.encode(user.getPassword())); - userRepository.save(user); - } -} \ No newline at end of file diff --git a/src/main/java/io/security/corespringsecurity/security/configs/AjaxLoginConfigurer.java b/src/main/java/io/security/corespringsecurity/security/configs/AjaxLoginConfigurer.java deleted file mode 100644 index de34a3e3..00000000 --- a/src/main/java/io/security/corespringsecurity/security/configs/AjaxLoginConfigurer.java +++ /dev/null @@ -1,81 +0,0 @@ -package io.security.corespringsecurity.security.configs; - -import com.fasterxml.jackson.databind.ObjectMapper; -import io.security.corespringsecurity.security.filter.AjaxLoginProcessingFilter; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.web.HttpSecurityBuilder; -import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.RememberMeServices; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; - -public final class AjaxLoginConfigurer> extends - AbstractAuthenticationFilterConfigurer, AjaxLoginProcessingFilter> { - - private AuthenticationSuccessHandler successHandler; - private AuthenticationFailureHandler failureHandler; - private AuthenticationManager authenticationManager; - - public AjaxLoginConfigurer() { - super(new AjaxLoginProcessingFilter(), null); - } - - @Override - public void init(H http) throws Exception { - super.init(http); - } - - @Override - public void configure(H http) { - - if(authenticationManager == null){ - authenticationManager = http.getSharedObject(AuthenticationManager.class); - } - getAuthenticationFilter().setAuthenticationManager(authenticationManager); - getAuthenticationFilter().setAuthenticationSuccessHandler(successHandler); - getAuthenticationFilter().setAuthenticationFailureHandler(failureHandler); - - SessionAuthenticationStrategy sessionAuthenticationStrategy = http - .getSharedObject(SessionAuthenticationStrategy.class); - if (sessionAuthenticationStrategy != null) { - getAuthenticationFilter().setSessionAuthenticationStrategy(sessionAuthenticationStrategy); - } - RememberMeServices rememberMeServices = http - .getSharedObject(RememberMeServices.class); - if (rememberMeServices != null) { - getAuthenticationFilter().setRememberMeServices(rememberMeServices); - } - http.setSharedObject(AjaxLoginProcessingFilter.class,getAuthenticationFilter()); - http.addFilterBefore(getAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); - } - - public AjaxLoginConfigurer successHandlerAjax(AuthenticationSuccessHandler successHandler) { - this.successHandler = successHandler; - return this; - } - - public AjaxLoginConfigurer failureHandlerAjax(AuthenticationFailureHandler authenticationFailureHandler) { - this.failureHandler = authenticationFailureHandler; - return this; - } - - public AjaxLoginConfigurer setAuthenticationManager(AuthenticationManager authenticationManager) { - this.authenticationManager = authenticationManager; - return this; - } - - @Override - protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) { - return new AntPathRequestMatcher(loginProcessingUrl, "POST"); - } - - public AjaxLoginConfigurer readAndWriteMapper(ObjectMapper objectMapper) { - getAuthenticationFilter().setObjectMapper(objectMapper); - return this; - } - -} diff --git a/src/main/java/io/security/corespringsecurity/security/configs/MethodSecurityConfig.java b/src/main/java/io/security/corespringsecurity/security/configs/MethodSecurityConfig.java deleted file mode 100644 index c925f3b9..00000000 --- a/src/main/java/io/security/corespringsecurity/security/configs/MethodSecurityConfig.java +++ /dev/null @@ -1,188 +0,0 @@ -package io.security.corespringsecurity.security.configs; - -import io.security.corespringsecurity.repository.AccessIpRepository; -import io.security.corespringsecurity.repository.ResourcesRepository; -import io.security.corespringsecurity.security.aop.CustomMethodSecurityInterceptor; -import io.security.corespringsecurity.security.enums.SecurtiyMethodType; -import io.security.corespringsecurity.security.factory.MethodResourcesMapFactoryBean; -import io.security.corespringsecurity.security.factory.UrlResourcesMapFactoryBean; -import io.security.corespringsecurity.security.metaDataSource.UrlSecurityMetadataSource; -import io.security.corespringsecurity.security.processor.ProtectPointcutPostProcessor; -import io.security.corespringsecurity.security.voter.IpAddressVoter; -import io.security.corespringsecurity.service.SecurityResourceService; -import io.security.corespringsecurity.service.impl.RoleHierarchyServiceImpl; -import lombok.extern.slf4j.Slf4j; -import org.aopalliance.intercept.MethodInterceptor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.security.access.AccessDecisionManager; -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; -import org.springframework.security.access.intercept.RunAsManager; -import org.springframework.security.access.method.MapBasedMethodSecurityMetadataSource; -import org.springframework.security.access.method.MethodSecurityMetadataSource; -import org.springframework.security.access.vote.AffirmativeBased; -import org.springframework.security.access.vote.AuthenticatedVoter; -import org.springframework.security.access.vote.RoleHierarchyVoter; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; -import org.springframework.security.web.access.expression.WebExpressionVoter; -import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -@Configuration -@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) -@Slf4j -public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration{ - - @Autowired - private SecurityResourceService securityResourceService; - @Autowired - private ResourcesRepository resourcesRepository; - @Autowired - private RoleHierarchyServiceImpl roleHierarchyService; - @Autowired - private AccessIpRepository accessIpRepository; - @Autowired - private RoleHierarchyImpl roleHierarchy; - @Autowired - private MapBasedMethodSecurityMetadataSource mapBasedMethodSecurityMetadataSource; - @Autowired - private AnnotationConfigServletWebServerApplicationContext applicationContext; - @Autowired - private CustomMethodSecurityInterceptor methodSecurityInterceptor; - - protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() { - return mapBasedMethodSecurityMetadataSource(); - } - - @Bean - public MapBasedMethodSecurityMetadataSource mapBasedMethodSecurityMetadataSource() { - return new MapBasedMethodSecurityMetadataSource(methodResourcesMapFactoryBean().getObject()); - } - - @Bean - public MethodResourcesMapFactoryBean methodResourcesMapFactoryBean(){ - MethodResourcesMapFactoryBean methodResourcesMapFactoryBean = new MethodResourcesMapFactoryBean(); - methodResourcesMapFactoryBean.setSecurityResourceService(securityResourceService); - methodResourcesMapFactoryBean.setResourceType(SecurtiyMethodType.METHOD.getValue()); - return methodResourcesMapFactoryBean; - } - -// @Bean - //@Profile("pointcut") - /*BeanPostProcessor protectPointcutPostProcessor() throws Exception { - - DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)applicationContext.getAutowireCapableBeanFactory(); - - Class clazz = Class.forName("org.springframework.security.config.method.ProtectPointcutPostProcessor"); - Constructor declaredConstructor = clazz.getDeclaredConstructor(MapBasedMethodSecurityMetadataSource.class); - declaredConstructor.setAccessible(true); - Object instance = declaredConstructor.newInstance(mapBasedMethodSecurityMetadataSource()); - Method setPointcutMap = instance.getClass().getMethod("setPointcutMap", Map.class); - setPointcutMap.setAccessible(true); - setPointcutMap.invoke(instance, pointcutResourcesMapFactoryBean().getObject()); - - return (BeanPostProcessor)instance; - }*/ - - /** - * - * 설정클래스에서 람다 형식으로 선언된 빈이 존재할 경우 오류가 발생하여 스프링 빈과 동일한 클래스를 생성하여 처리 - * 아직 AspectJ 라이브러리에서 Fix 하지 못한 것으로 판단되지만 다른 오류 원인이 존재하는지 계속 살펴보도록 함 - */ - @Bean -// @Profile("pointcut") - public ProtectPointcutPostProcessor protectPointcutPostProcessor() { - - ProtectPointcutPostProcessor protectPointcutPostProcessor = new ProtectPointcutPostProcessor(mapBasedMethodSecurityMetadataSource()); - protectPointcutPostProcessor.setPointcutMap(pointcutResourcesMapFactoryBean().getObject()); - - return protectPointcutPostProcessor; - } - - @Bean - //@Profile("pointcut") - public MethodResourcesMapFactoryBean pointcutResourcesMapFactoryBean(){ - - MethodResourcesMapFactoryBean pointcutResourcesMapFactoryBean = new MethodResourcesMapFactoryBean(); - pointcutResourcesMapFactoryBean.setSecurityResourceService(securityResourceService); - pointcutResourcesMapFactoryBean.setResourceType(SecurtiyMethodType.POINTCUT.getValue()); - return pointcutResourcesMapFactoryBean; - } - - @Bean - public FilterInvocationSecurityMetadataSource urlSecurityMetadataSource() { - return new UrlSecurityMetadataSource(urlResourcesMapFactoryBean().getObject(),securityResourceService); - } - - @Bean - public UrlResourcesMapFactoryBean urlResourcesMapFactoryBean(){ - UrlResourcesMapFactoryBean urlResourcesMapFactoryBean = new UrlResourcesMapFactoryBean(); - urlResourcesMapFactoryBean.setSecurityResourceService(securityResourceService); - return urlResourcesMapFactoryBean; - } - - @Bean - public AccessDecisionManager affirmativeBased() { - AffirmativeBased accessDecisionManager = new AffirmativeBased(getAccessDecisionVoters()); - accessDecisionManager.setAllowIfAllAbstainDecisions(false); // 접근 승인 거부 보류시 접근 허용은 true 접근 거부는 false - return accessDecisionManager; - } - - private List> getAccessDecisionVoters() { - - AuthenticatedVoter authenticatedVoter = new AuthenticatedVoter(); - WebExpressionVoter webExpressionVoter = new WebExpressionVoter(); - IpAddressVoter ipAddressVoter = new IpAddressVoter(securityResourceService); - - List> accessDecisionVoterList = Arrays.asList(/*ipAddressVoter, */authenticatedVoter, webExpressionVoter, roleVoter()); - return accessDecisionVoterList; - } - - @Bean - public RoleHierarchyVoter roleVoter() { - RoleHierarchyVoter roleHierarchyVoter = new RoleHierarchyVoter(roleHierarchy()); - roleHierarchyVoter.setRolePrefix("ROLE_"); - return roleHierarchyVoter; - } - - @Bean - public RoleHierarchyImpl roleHierarchy() { - RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); - return roleHierarchy; - } - - @Bean - public CustomMethodSecurityInterceptor customMethodSecurityInterceptor(MapBasedMethodSecurityMetadataSource methodSecurityMetadataSource) { - CustomMethodSecurityInterceptor customMethodSecurityInterceptor = new CustomMethodSecurityInterceptor(); - customMethodSecurityInterceptor.setAccessDecisionManager(accessDecisionManager()); - customMethodSecurityInterceptor.setAfterInvocationManager(afterInvocationManager()); - customMethodSecurityInterceptor.setSecurityMetadataSource(methodSecurityMetadataSource); - RunAsManager runAsManager = runAsManager(); - if (runAsManager != null) { - customMethodSecurityInterceptor.setRunAsManager(runAsManager); - } - - return customMethodSecurityInterceptor; - } - - /*@Bean - public SecurityResourceService securityResourceService(ResourcesRepository resourcesRepository, RoleHierarchyImpl roleHierarchy,RoleHierarchyServiceImpl roleHierarchyService, AccessIpRepository accessIpRepository, MapBasedMethodSecurityMetadataSource mapBasedMethodSecurityMetadataSource, AnnotationConfigServletWebServerApplicationContext applicationContext, CustomMethodSecurityInterceptor methodSecurityInterceptor) { - SecurityResourceService securityResourceService = new SecurityResourceService(resourcesRepository, roleHierarchy, roleHierarchyService, accessIpRepository, mapBasedMethodSecurityMetadataSource, applicationContext, methodSecurityInterceptor); - return securityResourceService; - }*/ -} \ No newline at end of file diff --git a/src/main/java/io/security/corespringsecurity/security/configs/SecurityConfig.java b/src/main/java/io/security/corespringsecurity/security/configs/SecurityConfig.java index 79615277..164bfb24 100644 --- a/src/main/java/io/security/corespringsecurity/security/configs/SecurityConfig.java +++ b/src/main/java/io/security/corespringsecurity/security/configs/SecurityConfig.java @@ -1,126 +1,22 @@ package io.security.corespringsecurity.security.configs; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.security.corespringsecurity.repository.JpaPersistentTokenRepository; -import io.security.corespringsecurity.repository.RememberMeTokenRepository; -import io.security.corespringsecurity.security.authentication.handler.*; -import io.security.corespringsecurity.security.authentication.provider.AjaxAuthenticationProvider; -import io.security.corespringsecurity.security.authentication.provider.FormAuthenticationProvider; -import io.security.corespringsecurity.security.authentication.services.FormRememberMeServices; -import io.security.corespringsecurity.security.authentication.services.FormWebAuthenticationDetailsSource; -import io.security.corespringsecurity.security.filter.AjaxLoginProcessingFilter; -import io.security.corespringsecurity.security.filter.PermitAllFilter; -import io.security.corespringsecurity.service.SecurityResourceService; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.web.servlet.FilterRegistrationBean; -import org.springframework.context.annotation.Bean; +import org.springframework.boot.autoconfigure.security.servlet.PathRequest; import org.springframework.context.annotation.Configuration; -import org.springframework.security.access.AccessDecisionManager; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.ProviderManager; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.core.session.SessionRegistry; -import org.springframework.security.core.session.SessionRegistryImpl; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.crypto.factory.PasswordEncoderFactories; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.access.AccessDeniedHandler; -import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; -import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; -import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; -import org.springframework.security.web.authentication.RememberMeServices; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; -import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; -import org.springframework.security.web.csrf.CsrfFilter; -import org.springframework.security.web.session.HttpSessionEventPublisher; import org.springframework.web.filter.CharacterEncodingFilter; -import java.util.ArrayList; -import java.util.List; - @Configuration @EnableWebSecurity @Slf4j public class SecurityConfig extends WebSecurityConfigurerAdapter { - private String[] ignoredMatcherPattern = {"/static/**", "/css/**", "/js/**", "/static/css/images/**", "/webjars/**", "/**/favicon.ico"}; - private String[] permitAllPattern = {"/", "/home", "/users", "/login", "/errorpage/**"}; - - public static final String AUTHENTICATION_HEADER_NAME = "Authorization"; - public static final String AUTHENTICATION_URL = "/api/auth/login"; - public static final String REFRESH_TOKEN_URL = "/api/auth/token"; - public static final String API_ROOT_URL = "/api/**"; - - @Autowired - private UserDetailsService userDetailsService; - @Autowired - private FormAuthenticationProvider commonAuthenticationProvider; - @Autowired - private AjaxAuthenticationProvider ajaxAuthenticationProvider; - @Autowired - private FormAuthenticationSuccessHandler formAuthenticationSuccessHandler; - @Autowired - private FormAuthenticationFailureHandler formAuthenticationFailureHandler; - @Autowired - private AjaxAuthenticationSuccessHandler ajaxAuthenticationSuccessHandler; - @Autowired - private AjaxAuthenticationFailureHandler ajaxAuthenticationFailureHandler; - @Autowired - private LogoutSuccessHandler logoutSuccessHandler; - @Autowired - private AccessDeniedHandler accessDeniedHandler; - @Autowired - private AccessDecisionManager accessDecisionManager; - @Autowired - private FormWebAuthenticationDetailsSource authenticationDetailsSource; - @Autowired - private FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource; - @Autowired - private RememberMeServices rememberMeServices; - @Autowired - private ObjectMapper objectMapper; - @Autowired - private AuthenticationManagerBuilder authenticationManagerBuilder; - @Autowired - private SecurityResourceService securityResourceService; - - @Bean - @Override - public AuthenticationManager authenticationManagerBean() throws Exception { - return super.authenticationManagerBean(); - } - -// @Override -// protected void configure(AuthenticationManagerBuilder auth) { -// auth.authenticationProvider(commonAuthenticationProvider); -// auth.authenticationProvider(ajaxAuthenticationProvider); -// } - - public AuthenticationManager ajaxAuthenticationManager() { - List authProviderList = new ArrayList<>(); - authProviderList.add(ajaxAuthenticationProvider); - ProviderManager providerManager = new ProviderManager(authProviderList); - return providerManager; - } - - public AuthenticationManager authenticationManager() { - List authProviderList = new ArrayList<>(); - authProviderList.add(commonAuthenticationProvider); - authProviderList.add(ajaxAuthenticationProvider); - ProviderManager providerManager = new ProviderManager(authProviderList); - return providerManager; - } - @Override public void configure(WebSecurity web) throws Exception { - web.ignoring().antMatchers(ignoredMatcherPattern); + web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations()); } @Override @@ -129,167 +25,9 @@ protected void configure(final HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() - .and() - .exceptionHandling() - .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")) - .accessDeniedPage("/denied") - .accessDeniedHandler(accessDeniedHandler) - - .and() - .formLogin() - .loginPage("/login") - .loginProcessingUrl("/login_proc") - .defaultSuccessUrl("/index") - .failureUrl("/login?error=true") - .usernameParameter("username") - .passwordParameter("password") - .successHandler(formAuthenticationSuccessHandler) - .failureHandler(formAuthenticationFailureHandler) - .authenticationDetailsSource(authenticationDetailsSource) -// .permitAll() .and() - .sessionManagement() -// .invalidSessionUrl("/users/invalidSession.html") - .maximumSessions(1) // -1 : 무제한 로그인 세션 허용 - .maxSessionsPreventsLogin(true) // false : 동시 로그인을 하지 못하도록 차단함 - //.expiredUrl("/login?expired=true") - .sessionRegistry(sessionRegistry()).and() - .sessionFixation().migrateSession() - - .and() - .logout() - .logoutSuccessHandler(logoutSuccessHandler) - //.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/index") - .clearAuthentication(true) - .invalidateHttpSession(true) - .deleteCookies("SESSION", "JSESSIONID", "remember-me") -// .permitAll() - - .and() - .rememberMe() - .alwaysRemember(true) - .rememberMeServices(rememberMeServices) - .tokenValiditySeconds(3600) - .key("anymobi") - - .and() - .addFilterBefore(buildAjaxLoginProcessingFilter(AUTHENTICATION_URL), UsernamePasswordAuthenticationFilter.class) - .addFilterBefore(filter, CsrfFilter.class) - .addFilterBefore(permitAllFilter(), FilterSecurityInterceptor.class) - .csrf().disable(); - - customConfigurer(http); - } - - private void customConfigurer(HttpSecurity http) throws Exception { - http - .apply(new AjaxLoginConfigurer<>()) - .successHandlerAjax(ajaxAuthenticationSuccessHandler) - .failureHandlerAjax(ajaxAuthenticationFailureHandler) - .loginProcessingUrl(AUTHENTICATION_URL) - .setAuthenticationManager(ajaxAuthenticationManager()) - .readAndWriteMapper(objectMapper); - + .formLogin(); } - - protected AjaxLoginProcessingFilter buildAjaxLoginProcessingFilter(String loginEntryPoint){ - AjaxLoginProcessingFilter filter = new AjaxLoginProcessingFilter(); - filter.setAuthenticationManager(ajaxAuthenticationManager()); - return filter; - } - - @Bean - public PermitAllFilter permitAllFilter() { - PermitAllFilter permitAllFilter = new PermitAllFilter(permitAllPattern); - //commonFilterSecurityInterceptor.setAuthenticationManager(authenticationManager()); - permitAllFilter.setAccessDecisionManager(accessDecisionManager); - permitAllFilter.setSecurityMetadataSource(filterInvocationSecurityMetadataSource); - permitAllFilter.setRejectPublicInvocations(false); - return permitAllFilter; - } - - @Bean - public FilterRegistrationBean filterRegistrationBean() { - FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); - filterRegistrationBean.setFilter(permitAllFilter()); - filterRegistrationBean.setEnabled(false); - return filterRegistrationBean; - } - - @Bean - public AccessDeniedHandler accessDeniedHandler() { - CommonAccessDeniedHandler commonAccessDeniedHandler = new CommonAccessDeniedHandler(); - commonAccessDeniedHandler.setErrorPage("/denied"); - return commonAccessDeniedHandler; - } - - @Bean - public RememberMeServices rememberMeServices(PersistentTokenRepository ptr) { - FormRememberMeServices rememberMeServices = new FormRememberMeServices("anymobi", userDetailsService, ptr); - return rememberMeServices; - } - - @Bean - public PersistentTokenRepository persistentTokenRepository(RememberMeTokenRepository rmtr) { - return new JpaPersistentTokenRepository(rmtr); - } - - @Bean - public HttpSessionEventPublisher httpSessionEventPublisher() { - return new HttpSessionEventPublisher(); - } - - @Bean - public PasswordEncoder passwordEncoder() { - return PasswordEncoderFactories.createDelegatingPasswordEncoder(); - } - - @Bean - public SessionRegistry sessionRegistry() { - return new SessionRegistryImpl(); - } - - /*@Bean - public FilterInvocationSecurityMetadataSource urlSecurityMetadataSource() { - return new UrlSecurityMetadataSource(urlResourcesMapFactoryBean().getObject(),securityResourceService); - } - - @Bean - public UrlResourcesMapFactoryBean urlResourcesMapFactoryBean(){ - UrlResourcesMapFactoryBean urlResourcesMapFactoryBean = new UrlResourcesMapFactoryBean(); - urlResourcesMapFactoryBean.setSecurityResourceService(securityResourceService); - return urlResourcesMapFactoryBean; - } - - @Bean - public AccessDecisionManager affirmativeBased() { - AffirmativeBased accessDecisionManager = new AffirmativeBased(getAccessDecisionVoters()); - accessDecisionManager.setAllowIfAllAbstainDecisions(false); // 접근 승인 거부 보류시 접근 허용은 true 접근 거부는 false - return accessDecisionManager; - } - - private List> getAccessDecisionVoters() { - - AuthenticatedVoter authenticatedVoter = new AuthenticatedVoter(); - WebExpressionVoter webExpressionVoter = new WebExpressionVoter(); - IpAddressVoter ipAddressVoter = new IpAddressVoter(securityResourceService); - - List> accessDecisionVoterList = Arrays.asList(ipAddressVoter, authenticatedVoter, webExpressionVoter, roleVoter()); - return accessDecisionVoterList; - } - - @Bean - public RoleHierarchyVoter roleVoter() { - RoleHierarchyVoter roleHierarchyVoter = new RoleHierarchyVoter(roleHierarchy()); - roleHierarchyVoter.setRolePrefix("ROLE_"); - return roleHierarchyVoter; - } - - @Bean - public RoleHierarchyImpl roleHierarchy() { - RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); - return roleHierarchy; - }*/ } diff --git a/src/main/java/io/security/corespringsecurity/security/domain/AuthoritiesDto.java b/src/main/java/io/security/corespringsecurity/security/domain/AuthoritiesDto.java deleted file mode 100644 index 8e9a074a..00000000 --- a/src/main/java/io/security/corespringsecurity/security/domain/AuthoritiesDto.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.security.corespringsecurity.security.domain; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; - -@Data -@AllArgsConstructor -@NoArgsConstructor -@Builder -public class AuthoritiesDto { - - private String roleName; - private AntPathRequestMatcher antPathRequestMatcher; -} diff --git a/src/main/java/io/security/corespringsecurity/security/domain/RoleHierarchyDto.java b/src/main/java/io/security/corespringsecurity/security/domain/RoleHierarchyDto.java deleted file mode 100644 index f7abbb2f..00000000 --- a/src/main/java/io/security/corespringsecurity/security/domain/RoleHierarchyDto.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.security.corespringsecurity.security.domain; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class RoleHierarchyDto { - - private String roldId; - private String roleName; - private String parentRoleId; -} diff --git a/src/main/java/io/security/corespringsecurity/security/domain/UserDto.java b/src/main/java/io/security/corespringsecurity/security/domain/UserDto.java deleted file mode 100644 index 1d1e79ae..00000000 --- a/src/main/java/io/security/corespringsecurity/security/domain/UserDto.java +++ /dev/null @@ -1,72 +0,0 @@ -package io.security.corespringsecurity.security.domain; - -import io.security.corespringsecurity.domain.entity.User; -import lombok.*; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; - -import javax.validation.constraints.NotNull; -import java.io.Serializable; -import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; - -@Builder -@AllArgsConstructor -@NoArgsConstructor -@Getter -@Setter -public class UserDto implements Serializable, UserDetails { - - private User user; - private List roles; - - public UserDto(User user, List roles) { - this.user = user; - this.roles = roles; - } - - @NotNull - private String username; - - private String password; - - private Integer role; - - @Override - public Collection getAuthorities() { - return roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()); - } - - @Override - public String getPassword() { - return password; - } - - @Override - public String getUsername() { - return username; - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return true; - } - -} diff --git a/src/main/java/io/security/corespringsecurity/security/enums/SecurtiyMethodType.java b/src/main/java/io/security/corespringsecurity/security/enums/SecurtiyMethodType.java deleted file mode 100644 index d9c05ba0..00000000 --- a/src/main/java/io/security/corespringsecurity/security/enums/SecurtiyMethodType.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.security.corespringsecurity.security.enums; - -public enum SecurtiyMethodType { - - METHOD("method"), - POINTCUT("pointcut"); - - private String name; - - SecurtiyMethodType(String name) { - this.name = name; - } - - public String getValue() { - return name; - } -} diff --git a/src/main/java/io/security/corespringsecurity/security/exception/AuthMethodNotSupportedException.java b/src/main/java/io/security/corespringsecurity/security/exception/AuthMethodNotSupportedException.java deleted file mode 100644 index 943ac3e3..00000000 --- a/src/main/java/io/security/corespringsecurity/security/exception/AuthMethodNotSupportedException.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.security.corespringsecurity.security.exception; - -import org.springframework.security.authentication.AuthenticationServiceException; - -public class AuthMethodNotSupportedException extends AuthenticationServiceException { - private static final long serialVersionUID = 3705043083010304496L; - - public AuthMethodNotSupportedException(String msg) { - super(msg); - } -} diff --git a/src/main/java/io/security/corespringsecurity/security/exception/UserNotFoundException.java b/src/main/java/io/security/corespringsecurity/security/exception/UserNotFoundException.java deleted file mode 100644 index d4b44071..00000000 --- a/src/main/java/io/security/corespringsecurity/security/exception/UserNotFoundException.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.security.corespringsecurity.security.exception; - -public final class UserNotFoundException extends RuntimeException { - - private static final long serialVersionUID = 5861310537366287163L; - - public UserNotFoundException() { - super(); - } - - public UserNotFoundException(final String message, final Throwable cause) { - super(message, cause); - } - - public UserNotFoundException(final String message) { - super(message); - } - - public UserNotFoundException(final Throwable cause) { - super(cause); - } - -} diff --git a/src/main/java/io/security/corespringsecurity/security/factory/MethodResourcesMapFactoryBean.java b/src/main/java/io/security/corespringsecurity/security/factory/MethodResourcesMapFactoryBean.java deleted file mode 100644 index 6bb186ec..00000000 --- a/src/main/java/io/security/corespringsecurity/security/factory/MethodResourcesMapFactoryBean.java +++ /dev/null @@ -1,54 +0,0 @@ -package io.security.corespringsecurity.security.factory; - -import io.security.corespringsecurity.service.SecurityResourceService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.stereotype.Component; - -import java.util.LinkedHashMap; -import java.util.List; - -@Slf4j -public class MethodResourcesMapFactoryBean implements FactoryBean>> { - - private SecurityResourceService securityResourceService; - private String resourceType; - - public void setResourceType(String resourceType) { - this.resourceType = resourceType; - } - - public void setSecurityResourceService(SecurityResourceService securityResourceService) { - this.securityResourceService = securityResourceService; - } - - private LinkedHashMap> resourcesMap; - - public void init() { - if ("method".equals(resourceType)) { - resourcesMap = securityResourceService.getMethodResourceList(); - } else if ("pointcut".equals(resourceType)) { - resourcesMap = securityResourceService.getPointcutResourceList(); - } else { - log.error("resourceType must be 'method' or 'pointcut'"); - } - } - - public LinkedHashMap> getObject() { - if (resourcesMap == null) { - init(); - } - return resourcesMap; - } - - @SuppressWarnings("rawtypes") - public Class getObjectType() { - return LinkedHashMap.class; - } - - public boolean isSingleton() { - return true; - } -} diff --git a/src/main/java/io/security/corespringsecurity/security/factory/UrlResourcesMapFactoryBean.java b/src/main/java/io/security/corespringsecurity/security/factory/UrlResourcesMapFactoryBean.java deleted file mode 100644 index 6c7247af..00000000 --- a/src/main/java/io/security/corespringsecurity/security/factory/UrlResourcesMapFactoryBean.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.security.corespringsecurity.security.factory; - -import io.security.corespringsecurity.service.SecurityResourceService; -import org.springframework.beans.factory.FactoryBean; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.web.util.matcher.RequestMatcher; - -import java.util.LinkedHashMap; -import java.util.List; - -public class UrlResourcesMapFactoryBean implements FactoryBean>> { - - private SecurityResourceService securityResourceService; - - public void setSecurityResourceService(SecurityResourceService securityResourceService) { - this.securityResourceService = securityResourceService; - } - - private LinkedHashMap> resourcesMap; - - public void init() { - resourcesMap = securityResourceService.getResourceList(); - } - - public LinkedHashMap> getObject() { - if (resourcesMap == null) { - init(); - } - return resourcesMap; - } - - public Class getObjectType() { - return LinkedHashMap.class; - } - - public boolean isSingleton() { - return true; - } -} diff --git a/src/main/java/io/security/corespringsecurity/security/filter/AjaxLoginProcessingFilter.java b/src/main/java/io/security/corespringsecurity/security/filter/AjaxLoginProcessingFilter.java deleted file mode 100644 index d16a7c7a..00000000 --- a/src/main/java/io/security/corespringsecurity/security/filter/AjaxLoginProcessingFilter.java +++ /dev/null @@ -1,54 +0,0 @@ -package io.security.corespringsecurity.security.filter; - -import com.fasterxml.jackson.databind.ObjectMapper; -import io.security.corespringsecurity.security.domain.UserDto; -import io.security.corespringsecurity.security.exception.AuthMethodNotSupportedException; -import io.security.corespringsecurity.security.token.AjaxAuthenticationToken; -import io.security.corespringsecurity.util.WebUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.util.StringUtils; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Arrays; - -public class AjaxLoginProcessingFilter extends AbstractAuthenticationProcessingFilter { - private static Logger logger = LoggerFactory.getLogger(AjaxLoginProcessingFilter.class); - - private ObjectMapper objectMapper; - - public AjaxLoginProcessingFilter() { - super(new AntPathRequestMatcher("/login", "POST")); - } - - @Override - public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) - throws AuthenticationException, IOException { - - if (!HttpMethod.POST.name().equals(request.getMethod()) || !WebUtil.isAjax(request)) { - throw new AuthMethodNotSupportedException("Authentication method not supported"); - } - - UserDto userDto = objectMapper.readValue(request.getReader(), UserDto.class); - userDto.setRoles(Arrays.asList("ROLE_USER")); - - if (StringUtils.isEmpty(userDto.getUsername()) || StringUtils.isEmpty(userDto.getPassword())) { - throw new AuthenticationServiceException("Username or Password not provided"); - } - AjaxAuthenticationToken token = AjaxAuthenticationToken.getTokenFromAccountContext(userDto); - - return this.getAuthenticationManager().authenticate(token); - } - - public void setObjectMapper(ObjectMapper objectMapper) { - this.objectMapper = objectMapper; - } -} diff --git a/src/main/java/io/security/corespringsecurity/security/filter/PermitAllFilter.java b/src/main/java/io/security/corespringsecurity/security/filter/PermitAllFilter.java deleted file mode 100644 index bf1c6e48..00000000 --- a/src/main/java/io/security/corespringsecurity/security/filter/PermitAllFilter.java +++ /dev/null @@ -1,77 +0,0 @@ -package io.security.corespringsecurity.security.filter; - -import org.springframework.security.access.intercept.InterceptorStatusToken; -import org.springframework.security.web.FilterInvocation; -import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public class PermitAllFilter extends FilterSecurityInterceptor { - - - - private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied"; - - private List permitAllRequestMatcher = new ArrayList<>(); - - public PermitAllFilter(String... permitAllPattern) { - createPermitAllPattern(permitAllPattern); - } - - @Override - protected InterceptorStatusToken beforeInvocation(Object object) { - boolean permitAll = false; - HttpServletRequest request = ((FilterInvocation) object).getRequest(); - for (RequestMatcher requestMatcher : permitAllRequestMatcher) { - if (requestMatcher.matches(request)) { - permitAll = true; - break; - } - } - - if (permitAll) { - return null; - } - - return super.beforeInvocation(object); - } - - @Override - public void invoke(FilterInvocation fi) throws IOException, ServletException { - - if ((fi.getRequest() != null) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null) - && super.isObserveOncePerRequest()) { - // filter already applied to this request and user wants us to observe - // once-per-request handling, so don't re-do security checking - fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); - } else { - // first time this request being called, so perform security checking - if (fi.getRequest() != null) { - fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); - } - - InterceptorStatusToken token = beforeInvocation(fi); - - try { - fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); - } finally { - super.finallyInvocation(token); - } - - super.afterInvocation(token, null); - } - } - - private void createPermitAllPattern(String... permitAllPattern) { - for (String pattern : permitAllPattern) { - permitAllRequestMatcher.add(new AntPathRequestMatcher(pattern)); - } - - } -} diff --git a/src/main/java/io/security/corespringsecurity/security/init/SecurityInitializer.java b/src/main/java/io/security/corespringsecurity/security/init/SecurityInitializer.java deleted file mode 100644 index 0e7aa272..00000000 --- a/src/main/java/io/security/corespringsecurity/security/init/SecurityInitializer.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.security.corespringsecurity.security.init; - -import io.security.corespringsecurity.service.SecurityResourceService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -@Component -@Slf4j -public class SecurityInitializer implements ApplicationRunner { - - @Autowired - private SecurityResourceService securityResourceService; - - @Override - @Transactional - public void run(ApplicationArguments args) { - - securityResourceService.setRoleHierarchy(); - } -} diff --git a/src/main/java/io/security/corespringsecurity/security/listener/AuthenticationFailureListener.java b/src/main/java/io/security/corespringsecurity/security/listener/AuthenticationFailureListener.java deleted file mode 100644 index 0e7f88a4..00000000 --- a/src/main/java/io/security/corespringsecurity/security/listener/AuthenticationFailureListener.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.security.corespringsecurity.security.listener; - -import io.security.corespringsecurity.service.impl.LoginAttemptServiceImpl; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationListener; -import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; - -@Component -public class AuthenticationFailureListener implements ApplicationListener { - @Autowired - private HttpServletRequest request; - - @Autowired - private LoginAttemptServiceImpl loginAttemptService; - - @Override - public void onApplicationEvent(final AuthenticationFailureBadCredentialsEvent e) { - loginAttemptService.loginFailed(request.getRemoteAddr()); - } - -} \ No newline at end of file diff --git a/src/main/java/io/security/corespringsecurity/security/listener/AuthenticationSuccessEventListener.java b/src/main/java/io/security/corespringsecurity/security/listener/AuthenticationSuccessEventListener.java deleted file mode 100644 index ac232afc..00000000 --- a/src/main/java/io/security/corespringsecurity/security/listener/AuthenticationSuccessEventListener.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.security.corespringsecurity.security.listener; - -import io.security.corespringsecurity.service.impl.LoginAttemptServiceImpl; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationListener; -import org.springframework.security.authentication.event.AuthenticationSuccessEvent; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; - -@Component -public class AuthenticationSuccessEventListener implements ApplicationListener { - @Autowired - private HttpServletRequest request; - - @Autowired - private LoginAttemptServiceImpl loginAttemptService; - - @Override - public void onApplicationEvent(final AuthenticationSuccessEvent e) { - loginAttemptService.loginSucceeded(request.getRemoteAddr()); - } - -} diff --git a/src/main/java/io/security/corespringsecurity/security/listener/SetupDataLoader.java b/src/main/java/io/security/corespringsecurity/security/listener/SetupDataLoader.java deleted file mode 100644 index 3c7f8367..00000000 --- a/src/main/java/io/security/corespringsecurity/security/listener/SetupDataLoader.java +++ /dev/null @@ -1,163 +0,0 @@ -package io.security.corespringsecurity.security.listener; - -import io.security.corespringsecurity.domain.entity.*; -import io.security.corespringsecurity.repository.*; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; - -@Component -public class SetupDataLoader implements ApplicationListener { - - private boolean alreadySetup = false; - - @Autowired - private UserRepository userRepository; - - @Autowired - private RoleRepository roleRepository; - - @Autowired - private ResourcesRepository resourcesRepository; - - @Autowired - private RoleHierarchyRepository roleHierarchyRepository; - - @Autowired - private PasswordEncoder passwordEncoder; - - @Autowired - private AccessIpRepository accessIpRepository; - - private static AtomicInteger count = new AtomicInteger(0); - - @Override - @Transactional - public void onApplicationEvent(final ContextRefreshedEvent event) { - - if (alreadySetup) { - return; - } - - setupSecurityResources(); - setupAccessIpData(); - - alreadySetup = true; - } - - - - private void setupSecurityResources() { - Set roles = new HashSet<>(); - Role adminRole = createRoleIfNotFound("ROLE_ADMIN", "관리자"); - roles.add(adminRole); - createResourceIfNotFound("/admin/**", "", roles, "url"); - User user = createUserIfNotFound("admin", "pass", "admin@gmail.com", 10, roles); - - Set roles1 = new HashSet<>(); - - Role managerRole = createRoleIfNotFound("ROLE_MANAGER", "매니저"); - roles1.add(managerRole); - createResourceIfNotFound("io.security.corespringsecurity.aopsecurity.method.AopMethodService.methodTest", "", roles1, "method"); - createResourceIfNotFound("io.security.corespringsecurity.aopsecurity.method.AopMethodService.innerCallMethodTest", "", roles1, "method"); - createResourceIfNotFound("execution(* io.security.corespringsecurity.aopsecurity.pointcut.*Service.*(..))", "", roles1, "pointcut"); - createUserIfNotFound("manager", "pass", "manager@gmail.com", 20, roles1); - createRoleHierarchyIfNotFound(managerRole, adminRole); - - Set roles3 = new HashSet<>(); - - Role childRole1 = createRoleIfNotFound("ROLE_USER", "회원"); - roles3.add(childRole1); - createResourceIfNotFound("/users/**", "", roles3, "url"); - createUserIfNotFound("user", "pass", "user@gmail.com", 30, roles3); - createRoleHierarchyIfNotFound(childRole1, managerRole); - - } - - @Transactional - public Role createRoleIfNotFound(String roleName, String roleDesc) { - - Role role = roleRepository.findByRoleName(roleName); - - if (role == null) { - role = Role.builder() - .roleName(roleName) - .roleDesc(roleDesc) - .build(); - } - return roleRepository.save(role); - } - - @Transactional - public User createUserIfNotFound(String userName, String password, String email, int age, Set roleSet) { - - User user = userRepository.findByUsername(userName); - - if (user == null) { - user = User.builder() - .username(userName) - .email(email) - .age(age) - .password(passwordEncoder.encode(password)) - .userRoles(roleSet) - .build(); - } - return userRepository.save(user); - } - - @Transactional - public Resources createResourceIfNotFound(String resourceName, String httpMethod, Set roleSet, String resourceType) { - Resources resources = resourcesRepository.findByResourceNameAndHttpMethod(resourceName, httpMethod); - - if (resources == null) { - resources = Resources.builder() - .resourceName(resourceName) - .roleSet(roleSet) - .httpMethod(httpMethod) - .resourceType(resourceType) - .orderNum(count.incrementAndGet()) - .build(); - } - return resourcesRepository.save(resources); - } - - @Transactional - public void createRoleHierarchyIfNotFound(Role childRole, Role parentRole) { - - RoleHierarchy roleHierarchy = roleHierarchyRepository.findByChildName(parentRole.getRoleName()); - if (roleHierarchy == null) { - roleHierarchy = RoleHierarchy.builder() - .childName(parentRole.getRoleName()) - .build(); - } - RoleHierarchy parentRoleHierarchy = roleHierarchyRepository.save(roleHierarchy); - - roleHierarchy = roleHierarchyRepository.findByChildName(childRole.getRoleName()); - if (roleHierarchy == null) { - roleHierarchy = RoleHierarchy.builder() - .childName(childRole.getRoleName()) - .build(); - } - - RoleHierarchy childRoleHierarchy = roleHierarchyRepository.save(roleHierarchy); - childRoleHierarchy.setParentName(parentRoleHierarchy); - } - - private void setupAccessIpData() { - AccessIp byIpAddress = accessIpRepository.findByIpAddress("127.0.0.1"); - if (byIpAddress == null) { - AccessIp accessIp = AccessIp.builder() - .ipAddress("127.0.0.1") - .build(); - accessIpRepository.save(accessIp); - } - - } -} \ No newline at end of file diff --git a/src/main/java/io/security/corespringsecurity/security/metaDataSource/UrlSecurityMetadataSource.java b/src/main/java/io/security/corespringsecurity/security/metaDataSource/UrlSecurityMetadataSource.java deleted file mode 100644 index 5fa15c9b..00000000 --- a/src/main/java/io/security/corespringsecurity/security/metaDataSource/UrlSecurityMetadataSource.java +++ /dev/null @@ -1,80 +0,0 @@ -package io.security.corespringsecurity.security.metaDataSource; - - -import io.security.corespringsecurity.service.SecurityResourceService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.web.FilterInvocation; -import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; -import org.springframework.security.web.util.matcher.RequestMatcher; - -import javax.servlet.http.HttpServletRequest; -import java.util.*; - -@Slf4j -public class UrlSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { - - private LinkedHashMap> requestMap; - private SecurityResourceService securityResourceService; - - public UrlSecurityMetadataSource(LinkedHashMap> requestMap, SecurityResourceService securityResourceService) { - this.requestMap = requestMap; - this.securityResourceService = securityResourceService; - } - - @Override - public Collection getAttributes(Object object) throws IllegalArgumentException { - - Collection result = null; - FilterInvocation fi = (FilterInvocation) object; - HttpServletRequest httpServletRequest = fi.getHttpRequest(); - - if (requestMap != null) { - for (Map.Entry> entry : requestMap.entrySet()) { - RequestMatcher matcher = entry.getKey(); - if (matcher.matches(httpServletRequest)) { - result = entry.getValue(); - break; - } - } - } - return result; - } - - @Override - public Collection getAllConfigAttributes() { - - Set result = new HashSet<>(); - for (Map.Entry> entry : requestMap.entrySet()) { - List list = entry.getValue(); - if (list != null) { - result.addAll(list); - } - } - return null; - } - - @Override - public boolean supports(Class clazz) { - return FilterInvocation.class.isAssignableFrom(clazz); - } - - public void reload() throws Exception { - - LinkedHashMap> reloadedMap = securityResourceService.getResourceList(); - - Iterator>> iterator = reloadedMap.entrySet().iterator(); - - // 이전 데이터 삭제 - requestMap.clear(); - - while (iterator.hasNext()) { - Map.Entry> entry = iterator.next(); - - requestMap.put(entry.getKey(), entry.getValue()); - } - - log.info("Secured Url Resources - Role Mappings reloaded at Runtime!"); - } - -} \ No newline at end of file diff --git a/src/main/java/io/security/corespringsecurity/security/processor/ProtectPointcutPostProcessor.java b/src/main/java/io/security/corespringsecurity/security/processor/ProtectPointcutPostProcessor.java deleted file mode 100644 index 20361877..00000000 --- a/src/main/java/io/security/corespringsecurity/security/processor/ProtectPointcutPostProcessor.java +++ /dev/null @@ -1,133 +0,0 @@ -package io.security.corespringsecurity.security.processor; - -import lombok.extern.slf4j.Slf4j; -import org.aspectj.weaver.tools.PointcutExpression; -import org.aspectj.weaver.tools.PointcutParser; -import org.aspectj.weaver.tools.PointcutPrimitive; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.method.MapBasedMethodSecurityMetadataSource; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -import java.lang.reflect.Method; -import java.util.*; - -@Slf4j -public class ProtectPointcutPostProcessor implements BeanPostProcessor { - - private final Map> pointcutMap = new LinkedHashMap>(); - private final MapBasedMethodSecurityMetadataSource mapBasedMethodSecurityMetadataSource; - private final Set pointCutExpressions = new LinkedHashSet<>(); - private final PointcutParser parser; - private final Set processedBeans = new HashSet<>(); - - public ProtectPointcutPostProcessor(MapBasedMethodSecurityMetadataSource mapBasedMethodSecurityMetadataSource) { - Assert.notNull(mapBasedMethodSecurityMetadataSource, "MapBasedMethodSecurityMetadataSource to populate is required"); - this.mapBasedMethodSecurityMetadataSource = mapBasedMethodSecurityMetadataSource; - - Set supportedPrimitives = new HashSet<>(3); - supportedPrimitives.add(PointcutPrimitive.EXECUTION); - supportedPrimitives.add(PointcutPrimitive.ARGS); - supportedPrimitives.add(PointcutPrimitive.REFERENCE); - parser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingContextClassloaderForResolution(supportedPrimitives); - } - - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - return bean; - } - - public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - - if (processedBeans.contains(beanName)) { - return bean; - } - - synchronized (processedBeans) { - if (processedBeans.contains(beanName)) { - return bean; - } - - Method[] methods; - try { - methods = bean.getClass().getMethods(); - } catch (Exception e) { - throw new IllegalStateException(e.getMessage()); - } - - for (Method method : methods) { - for (PointcutExpression expression : pointCutExpressions) { - if (attemptMatch(bean.getClass(), method, expression, beanName)) { - break; - } - } - } - - processedBeans.add(beanName); - } - - return bean; - } - - /** - * 설정클래스에서 람다 형식으로 선언된 빈이 존재할 경우 에러가 발생하여 스프링 빈과 동일한 클래스를 생성하여 약간 수정함 - * 아직 AspectJ 라이브러리에서 Fix 하지 못한 것으로 판단되지만 다른 원인이 존재하는지 계속 살펴보도록 함 - */ - private boolean attemptMatch(Class targetClass, Method method, PointcutExpression expression, String beanName) { - - boolean matches; - try { - matches = expression.matchesMethodExecution(method).alwaysMatches(); - if (matches) { - List attr = pointcutMap.get(expression.getPointcutExpression()); - - if (log.isDebugEnabled()) { - log.debug("AspectJ pointcut expression '" - + expression.getPointcutExpression() + "' matches target class '" - + targetClass.getName() + "' (bean ID '" + beanName - + "') for method '" + method - + "'; registering security configuration attribute '" + attr - + "'"); - } - - mapBasedMethodSecurityMetadataSource.addSecureMethod(targetClass, method, attr); - } - return matches; - - } catch (Exception e) { - matches = false; - } - return matches; - } - - public void setPointcutMap(Map> map) { - Assert.notEmpty(map, "configAttributes cannot be empty"); - for (String expression : map.keySet()) { - List value = map.get(expression); - addPointcut(expression, value); - } - } - - private void addPointcut(String pointcutExpression, List definition) { - Assert.hasText(pointcutExpression, "An AspectJ pointcut expression is required"); - Assert.notNull(definition, "A List of ConfigAttributes is required"); - pointcutExpression = replaceBooleanOperators(pointcutExpression); - pointcutMap.put(pointcutExpression, definition); - pointCutExpressions.add(parser.parsePointcutExpression(pointcutExpression)); - - if (log.isDebugEnabled()) { - log.debug("AspectJ pointcut expression '" + pointcutExpression - + "' registered for security configuration attribute '" + definition - + "'"); - } - } - - private String replaceBooleanOperators(String pcExpr) { - pcExpr = StringUtils.replace(pcExpr, " and ", " && "); - pcExpr = StringUtils.replace(pcExpr, " or ", " || "); - pcExpr = StringUtils.replace(pcExpr, " not ", " ! "); - return pcExpr; - } - -} diff --git a/src/main/java/io/security/corespringsecurity/security/token/AjaxAuthenticationToken.java b/src/main/java/io/security/corespringsecurity/security/token/AjaxAuthenticationToken.java deleted file mode 100644 index 4cb2b73d..00000000 --- a/src/main/java/io/security/corespringsecurity/security/token/AjaxAuthenticationToken.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.security.corespringsecurity.security.token; - -import io.security.corespringsecurity.security.domain.UserDto; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.GrantedAuthority; - -import java.util.Collection; - -public class AjaxAuthenticationToken extends UsernamePasswordAuthenticationToken { - - private AjaxAuthenticationToken(Object principal, Object credentials, Collection authorities) { - super(principal, credentials, authorities); - } - - public static AjaxAuthenticationToken getTokenFromAccountContext(UserDto userDto) { - return new AjaxAuthenticationToken(userDto, userDto.getPassword(), userDto.getAuthorities()); - } -} diff --git a/src/main/java/io/security/corespringsecurity/security/voter/IpAddressVoter.java b/src/main/java/io/security/corespringsecurity/security/voter/IpAddressVoter.java deleted file mode 100644 index c2891dad..00000000 --- a/src/main/java/io/security/corespringsecurity/security/voter/IpAddressVoter.java +++ /dev/null @@ -1,59 +0,0 @@ -package io.security.corespringsecurity.security.voter; - -import io.security.corespringsecurity.service.SecurityResourceService; -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.WebAuthenticationDetails; - -import java.util.Collection; -import java.util.List; - -public class IpAddressVoter implements AccessDecisionVoter { - - private SecurityResourceService securityResourceService; - -// private static final String REQUEST_URI = "/admin/"; - - public IpAddressVoter(SecurityResourceService securityResourceService) { - this.securityResourceService = securityResourceService; - } - - @Override - public boolean supports(ConfigAttribute attribute) { - return (attribute.getAttribute() != null); - } - - @Override - public boolean supports(Class clazz) { - return true; - } - - @Override - public int vote(Authentication authentication, Object object, Collection configList) { - - if (!(authentication.getDetails() instanceof WebAuthenticationDetails)) { - return ACCESS_DENIED; - } - - WebAuthenticationDetails details = (WebAuthenticationDetails) authentication.getDetails(); - String address = details.getRemoteAddress(); - List accessIpList = securityResourceService.getAccessIpList(); - - int result = ACCESS_DENIED; - - for (String ipAddress : accessIpList) { - - if (address.equals(ipAddress)) { - return ACCESS_ABSTAIN; - } - } - - if(result == ACCESS_DENIED){ - throw new AccessDeniedException("Invalid ipAddress can not accessed"); - } - - return result; - } -} \ No newline at end of file diff --git a/src/main/java/io/security/corespringsecurity/service/LoginAttemptService.java b/src/main/java/io/security/corespringsecurity/service/LoginAttemptService.java deleted file mode 100644 index 49582249..00000000 --- a/src/main/java/io/security/corespringsecurity/service/LoginAttemptService.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.security.corespringsecurity.service; - -public interface LoginAttemptService { - - void loginSucceeded(final String key); - void loginFailed(final String key); - boolean isBlocked(final String key); -} diff --git a/src/main/java/io/security/corespringsecurity/service/MethodSecurityService.java b/src/main/java/io/security/corespringsecurity/service/MethodSecurityService.java deleted file mode 100644 index ddbc3ef0..00000000 --- a/src/main/java/io/security/corespringsecurity/service/MethodSecurityService.java +++ /dev/null @@ -1,72 +0,0 @@ -package io.security.corespringsecurity.service; - -import io.security.corespringsecurity.security.aop.CustomMethodSecurityInterceptor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.aop.Advisor; -import org.springframework.aop.framework.ProxyFactory; -import org.springframework.beans.factory.support.DefaultSingletonBeanRegistry; -import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor; -import org.springframework.security.access.method.MapBasedMethodSecurityMetadataSource; -import org.springframework.stereotype.Component; -import org.springframework.util.ClassUtils; - -import java.util.Arrays; -import java.util.List; - -@Slf4j -@Component -public class MethodSecurityService { - - private MapBasedMethodSecurityMetadataSource mapBasedMethodSecurityMetadataSource; - private AnnotationConfigServletWebServerApplicationContext applicationContext; - private CustomMethodSecurityInterceptor methodSecurityInterceptor; - private MethodSecurityMetadataSourceAdvisor methodSecurityMetadataSourceAdvisor; - - public MethodSecurityService(MapBasedMethodSecurityMetadataSource mapBasedMethodSecurityMetadataSource, AnnotationConfigServletWebServerApplicationContext applicationContext, CustomMethodSecurityInterceptor methodSecurityInterceptor, MethodSecurityMetadataSourceAdvisor methodSecurityMetadataSourceAdvisor) { - this.mapBasedMethodSecurityMetadataSource = mapBasedMethodSecurityMetadataSource; - this.applicationContext = applicationContext; - this.methodSecurityInterceptor = methodSecurityInterceptor; - this.methodSecurityMetadataSourceAdvisor = methodSecurityMetadataSourceAdvisor; - } - - public void addMethodSecured(String className, String roleName) throws Exception{ - - int lastDotIndex = className.lastIndexOf("."); - String methodName = className.substring(lastDotIndex + 1); - String typeName = className.substring(0, lastDotIndex); - Class type = ClassUtils.resolveClassName(typeName, ClassUtils.getDefaultClassLoader()); - String beanName = type.getSimpleName().substring(0, 1).toLowerCase() + type.getSimpleName().substring(1); - - ProxyFactory proxyFactory = new ProxyFactory(); - proxyFactory.setTarget(type.getDeclaredConstructor().newInstance()); - proxyFactory.addAdvice(methodSecurityInterceptor); -// proxyFactory.addAdvisor(methodSecurityMetadataSourceAdvisor); - Object proxy = proxyFactory.getProxy(); - - List attr = Arrays.asList(new SecurityConfig(roleName)); - mapBasedMethodSecurityMetadataSource.addSecureMethod(type,methodName, attr); - - DefaultSingletonBeanRegistry registry = (DefaultSingletonBeanRegistry)applicationContext.getBeanFactory(); - registry.destroySingleton(beanName); - registry.registerSingleton(beanName, proxy); - - } - - public void removeMethodSecured(String className) throws Exception{ - - int lastDotIndex = className.lastIndexOf("."); - String typeName = className.substring(0, lastDotIndex); - Class type = ClassUtils.resolveClassName(typeName, ClassUtils.getDefaultClassLoader()); - String beanName = type.getSimpleName().substring(0, 1).toLowerCase() + type.getSimpleName().substring(1); - Object newInstance = type.getDeclaredConstructor().newInstance(); - - DefaultSingletonBeanRegistry registry = (DefaultSingletonBeanRegistry)applicationContext.getBeanFactory(); - Object singleton = registry.getSingleton(beanName); - registry.destroySingleton(beanName); - registry.registerSingleton(beanName, newInstance); - - } -} diff --git a/src/main/java/io/security/corespringsecurity/service/ResourcesService.java b/src/main/java/io/security/corespringsecurity/service/ResourcesService.java deleted file mode 100644 index c46197e5..00000000 --- a/src/main/java/io/security/corespringsecurity/service/ResourcesService.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.security.corespringsecurity.service; - -import io.security.corespringsecurity.domain.entity.Resources; -import io.security.corespringsecurity.domain.entity.Role; - -import java.util.List; - -public interface ResourcesService { - - Resources selectResources(long id); - - List selectResources(); - - void insertResources(Resources Resources); - - void deleteResources(long id); -} \ No newline at end of file diff --git a/src/main/java/io/security/corespringsecurity/service/RoleHierarchyService.java b/src/main/java/io/security/corespringsecurity/service/RoleHierarchyService.java deleted file mode 100644 index b63a7f7b..00000000 --- a/src/main/java/io/security/corespringsecurity/service/RoleHierarchyService.java +++ /dev/null @@ -1,6 +0,0 @@ -package io.security.corespringsecurity.service; - -public interface RoleHierarchyService { - - String findAllHierarchy(); -} diff --git a/src/main/java/io/security/corespringsecurity/service/RoleService.java b/src/main/java/io/security/corespringsecurity/service/RoleService.java deleted file mode 100644 index 55cc8449..00000000 --- a/src/main/java/io/security/corespringsecurity/service/RoleService.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.security.corespringsecurity.service; - -import io.security.corespringsecurity.domain.dto.RoleDto; -import io.security.corespringsecurity.domain.entity.Role; - -import java.util.List; - -public interface RoleService { - - Role getRole(long id); - - List getRoles(); - - void createRole(Role role); -} \ No newline at end of file diff --git a/src/main/java/io/security/corespringsecurity/service/SecurityResourceService.java b/src/main/java/io/security/corespringsecurity/service/SecurityResourceService.java deleted file mode 100644 index ff21ce76..00000000 --- a/src/main/java/io/security/corespringsecurity/service/SecurityResourceService.java +++ /dev/null @@ -1,134 +0,0 @@ -package io.security.corespringsecurity.service; - -import io.security.corespringsecurity.domain.entity.Resources; -import io.security.corespringsecurity.repository.AccessIpRepository; -import io.security.corespringsecurity.repository.ResourcesRepository; -import io.security.corespringsecurity.security.aop.CustomMethodSecurityInterceptor; -import io.security.corespringsecurity.service.impl.RoleHierarchyServiceImpl; -import lombok.extern.slf4j.Slf4j; -import org.springframework.aop.framework.ProxyFactory; -import org.springframework.beans.factory.support.DefaultSingletonBeanRegistry; -import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; -import org.springframework.security.access.method.MapBasedMethodSecurityMetadataSource; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.stereotype.Service; -import org.springframework.util.ClassUtils; -import org.springframework.web.bind.annotation.GetMapping; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.stream.Collectors; - -@Slf4j -public class SecurityResourceService { - - private ResourcesRepository resourcesRepository; - private RoleHierarchyServiceImpl roleHierarchyService; - private RoleHierarchyImpl roleHierarchy; - private AccessIpRepository accessIpRepository; - - public SecurityResourceService(ResourcesRepository resourcesRepository, RoleHierarchyImpl roleHierarchy, RoleHierarchyServiceImpl roleHierarchyService, AccessIpRepository accessIpRepository/*, MapBasedMethodSecurityMetadataSource mapBasedMethodSecurityMetadataSource, AnnotationConfigServletWebServerApplicationContext applicationContext, CustomMethodSecurityInterceptor methodSecurityInterceptor*/) { - this.resourcesRepository = resourcesRepository; - this.roleHierarchy = roleHierarchy; - this.roleHierarchyService = roleHierarchyService; - this.accessIpRepository = accessIpRepository; - } - - @Cacheable(value = "resourceList") - public LinkedHashMap> getResourceList() { - - LinkedHashMap> result = new LinkedHashMap<>(); - List resourcesList = resourcesRepository.findAllResources(); - - resourcesList.forEach(re -> - { - List configAttributeList = new ArrayList<>(); - re.getRoleSet().forEach(ro -> { - configAttributeList.add(new SecurityConfig(ro.getRoleName())); - result.put(new AntPathRequestMatcher(re.getResourceName()), configAttributeList); - }); - } - ); - log.debug("cache test"); - return result; - } - - @Cacheable(value = "methodResourceList") - public LinkedHashMap> getMethodResourceList() { - - LinkedHashMap> result = new LinkedHashMap<>(); - List resourcesList = resourcesRepository.findAllMethodResources(); - - getResourceMap(result, resourcesList); - return result; - } - - @Cacheable(value = "pointcutResourceList") - public LinkedHashMap> getPointcutResourceList() { - - LinkedHashMap> result = new LinkedHashMap<>(); - List resourcesList = resourcesRepository.findAllPointcutResources(); - - getResourceMap(result, resourcesList); - return result; - } - - @Cacheable(value = "accessIpList") - public List getAccessIpList() { - - List accessIpList = accessIpRepository.findAll().stream().map(accessIp -> accessIp.getIpAddress()).collect(Collectors.toList()); - - return accessIpList; - } - - private void getResourceMap(LinkedHashMap> result, List resourcesList) { - resourcesList.forEach(re -> - { - List configAttributeList = new ArrayList<>(); - re.getRoleSet().forEach(ro -> { - configAttributeList.add(new SecurityConfig(ro.getRoleName())); - result.put(re.getResourceName(), configAttributeList); - }); - } - ); - log.debug("cache test"); - } - - @CacheEvict(value = "resourceList") - public void clearCacheResourceList() { - - } - - @CacheEvict(value = "methodResourceList") - public void clearCacheMethodResourceList() { - - } - - @CacheEvict(value = "pointcutResourceList") - public void clearCachePointcutResourceList() { - - } - - @CacheEvict(value = "accessIpList") - public void clearAccessIpList() { - - } - - public void setRoleHierarchy() { - String allHierarchy = roleHierarchyService.findAllHierarchy(); - roleHierarchy.setHierarchy(allHierarchy); - } - - private void init() { - getResourceList(); - } - -} diff --git a/src/main/java/io/security/corespringsecurity/service/UserService.java b/src/main/java/io/security/corespringsecurity/service/UserService.java deleted file mode 100644 index 255f5e79..00000000 --- a/src/main/java/io/security/corespringsecurity/service/UserService.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.security.corespringsecurity.service; - -import io.security.corespringsecurity.domain.dto.UserDto; -import io.security.corespringsecurity.domain.entity.User; - -import java.util.List; - -public interface UserService { - - List getUsers(); - UserDto getUser(Long id); - void createUser(User user); - void deleteUser(Long idx); -} diff --git a/src/main/java/io/security/corespringsecurity/service/impl/LoginAttemptServiceImpl.java b/src/main/java/io/security/corespringsecurity/service/impl/LoginAttemptServiceImpl.java deleted file mode 100644 index 0886b070..00000000 --- a/src/main/java/io/security/corespringsecurity/service/impl/LoginAttemptServiceImpl.java +++ /dev/null @@ -1,49 +0,0 @@ -package io.security.corespringsecurity.service.impl; - -import io.security.corespringsecurity.service.LoginAttemptService; -import org.springframework.cglib.core.internal.LoadingCache; -import org.springframework.stereotype.Service; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; - -@Service -public class LoginAttemptServiceImpl implements LoginAttemptService { - - private final int MAX_ATTEMPT = 5; - private Map attemptsCache; - - public LoginAttemptServiceImpl() { - super(); - attemptsCache = new HashMap(); - } - - @Override - public void loginSucceeded(final String key) { - - attemptsCache.put(key, 0); - } - - @Override - public void loginFailed(final String key) { - int attempts; - try { - attempts = attemptsCache.get(key); - } catch (Exception e) { - attempts = 0; - } - attempts++; - attemptsCache.put(key, attempts); - } - - @Override - public boolean isBlocked(final String key) { - try { - return attemptsCache.get(key) >= MAX_ATTEMPT; - } catch (Exception e) { - return false; - } - } -} diff --git a/src/main/java/io/security/corespringsecurity/service/impl/ResourcesServiceImpl.java b/src/main/java/io/security/corespringsecurity/service/impl/ResourcesServiceImpl.java deleted file mode 100644 index d7ec7e01..00000000 --- a/src/main/java/io/security/corespringsecurity/service/impl/ResourcesServiceImpl.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.security.corespringsecurity.service.impl; - -import io.security.corespringsecurity.domain.entity.Resources; -import io.security.corespringsecurity.domain.entity.Role; -import io.security.corespringsecurity.repository.ResourcesRepository; -import io.security.corespringsecurity.service.ResourcesService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -@Slf4j -@Service -public class ResourcesServiceImpl implements ResourcesService { - - @Autowired - private ResourcesRepository ResourcesRepository; - - @Transactional - public Resources selectResources(long id) { - return ResourcesRepository.findById(id).orElse(new Resources()); - } - - @Transactional - public List selectResources() { - return ResourcesRepository.findAll(); - } - - @Transactional - public void insertResources(Resources resources){ - ResourcesRepository.save(resources); - } - - @Transactional - public void deleteResources(long id) { - ResourcesRepository.deleteById(id); - } -} \ No newline at end of file diff --git a/src/main/java/io/security/corespringsecurity/service/impl/RoleHierarchyServiceImpl.java b/src/main/java/io/security/corespringsecurity/service/impl/RoleHierarchyServiceImpl.java deleted file mode 100644 index 90af3135..00000000 --- a/src/main/java/io/security/corespringsecurity/service/impl/RoleHierarchyServiceImpl.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.security.corespringsecurity.service.impl; - -import io.security.corespringsecurity.domain.entity.RoleHierarchy; -import io.security.corespringsecurity.repository.RoleHierarchyRepository; -import io.security.corespringsecurity.service.RoleHierarchyService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.Iterator; -import java.util.List; - -@Service -public class RoleHierarchyServiceImpl implements RoleHierarchyService { - - @Autowired - private RoleHierarchyRepository roleHierarchyRepository; - - @Transactional - @Override - public String findAllHierarchy() { - - List rolesHierarchy = roleHierarchyRepository.findAll(); - - Iterator itr = rolesHierarchy.iterator(); - StringBuffer concatedRoles = new StringBuffer(); - while (itr.hasNext()) { - RoleHierarchy model = itr.next(); - if (model.getParentName() != null) { - concatedRoles.append(model.getParentName().getChildName()); - concatedRoles.append(" > "); - concatedRoles.append(model.getChildName()); - concatedRoles.append("\n"); - } - } - return concatedRoles.toString(); - - } -} diff --git a/src/main/java/io/security/corespringsecurity/service/impl/RoleServiceImpl.java b/src/main/java/io/security/corespringsecurity/service/impl/RoleServiceImpl.java deleted file mode 100644 index 533b3ebb..00000000 --- a/src/main/java/io/security/corespringsecurity/service/impl/RoleServiceImpl.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.security.corespringsecurity.service.impl; - -import io.security.corespringsecurity.domain.dto.RoleDto; -import io.security.corespringsecurity.domain.entity.Role; -import io.security.corespringsecurity.repository.RoleRepository; -import io.security.corespringsecurity.service.RoleService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; - -@Slf4j -@Service -public class RoleServiceImpl implements RoleService { - - @Autowired - private RoleRepository roleRepository; - - @Transactional - public Role getRole(long id) { - return roleRepository.findById(id).orElse(new Role()); - } - - @Transactional - public List getRoles() { - - return roleRepository.findAll(); - } - - @Transactional - public void createRole(Role role){ - - roleRepository.save(role); - } -} \ No newline at end of file diff --git a/src/main/java/io/security/corespringsecurity/service/impl/UserServiceImpl.java b/src/main/java/io/security/corespringsecurity/service/impl/UserServiceImpl.java deleted file mode 100644 index 98c408d1..00000000 --- a/src/main/java/io/security/corespringsecurity/service/impl/UserServiceImpl.java +++ /dev/null @@ -1,76 +0,0 @@ -package io.security.corespringsecurity.service.impl; - -import io.security.corespringsecurity.domain.dto.UserDto; -import io.security.corespringsecurity.domain.entity.Role; -import io.security.corespringsecurity.domain.entity.User; -import io.security.corespringsecurity.repository.RoleRepository; -import io.security.corespringsecurity.repository.UserRepository; -import io.security.corespringsecurity.security.authentication.services.UserDetail; -import io.security.corespringsecurity.service.UserService; -import lombok.extern.slf4j.Slf4j; -import org.modelmapper.ModelMapper; -import org.modelmapper.convention.MatchingStrategies; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import javax.servlet.http.HttpServletRequest; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -@Slf4j -@Service("userService") -public class UserServiceImpl implements UserService { - - @Autowired - private UserRepository userRepository; - - @Autowired - private RoleRepository roleRepository; - - @Autowired - private PasswordEncoder passwordEncoder; - - @Transactional - public void createUser(User user){ - - Role role = roleRepository.findByRoleName("ROLE_USER"); - Set roles = new HashSet<>(); - roles.add(role); - user.setUserRoles(roles); - user.setPassword(passwordEncoder.encode(user.getPassword())); - userRepository.save(user); - } - - @Transactional - public UserDto getUser(Long id) { - - User user = userRepository.findById(id).orElse(new User()); - ModelMapper modelMapper = new ModelMapper(); - UserDto userDto = modelMapper.map(user, UserDto.class); - - List roles = user.getUserRoles() - .stream() - .map(role -> role.getRoleName()) - .collect(Collectors.toList()); - - userDto.setRoles(roles); - return userDto; - } - - @Transactional - public List getUsers() { - return userRepository.findAll(); - } - - @Override - public void deleteUser(Long id) { - userRepository.deleteById(id); - } -} \ No newline at end of file diff --git a/src/main/java/io/security/corespringsecurity/util/WebUtil.java b/src/main/java/io/security/corespringsecurity/util/WebUtil.java deleted file mode 100644 index f9e32cbe..00000000 --- a/src/main/java/io/security/corespringsecurity/util/WebUtil.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.security.corespringsecurity.util; - -import org.springframework.security.web.savedrequest.SavedRequest; - -import javax.servlet.http.HttpServletRequest; - -public class WebUtil { - private static final String XML_HTTP_REQUEST = "XMLHttpRequest"; - private static final String X_REQUESTED_WITH = "X-Requested-With"; - - private static final String CONTENT_TYPE = "Content-type"; - private static final String CONTENT_TYPE_JSON = "application/json"; - - public static boolean isAjax(HttpServletRequest request) { - return XML_HTTP_REQUEST.equals(request.getHeader(X_REQUESTED_WITH)); - } - - public static boolean isAjax(SavedRequest request) { - return request.getHeaderValues(X_REQUESTED_WITH).contains(XML_HTTP_REQUEST); - } - - public static boolean isContentTypeJson(SavedRequest request) { - return request.getHeaderValues(CONTENT_TYPE).contains(CONTENT_TYPE_JSON); - } -} diff --git a/src/main/resources/static/images/glyphicons-halflings-white.png b/src/main/resources/static/images/glyphicons-halflings-white.png deleted file mode 100644 index 3bf6484a29d8da269f9bc874b25493a45fae3bae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8777 zcmZvC1yGz#v+m*$LXcp=A$ZWB0fL7wNbp_U*$~{_gL`my3oP#L!5tQYy99Ta`+g_q zKlj|KJ2f@c)ARJx{q*bbkhN_!|Wn*Vos8{TEhUT@5e;_WJsIMMcG5%>DiS&dv_N`4@J0cnAQ-#>RjZ z00W5t&tJ^l-QC*ST1-p~00u^9XJ=AUl7oW-;2a+x2k__T=grN{+1c4XK0ZL~^z^i$ zp&>vEhr@4fZWb380S18T&!0cQ3IKpHF)?v=b_NIm0Q>vwY7D0baZ)n z31Fa5sELUQARIVaU0nqf0XzT+fB_63aA;@<$l~wse|mcA;^G1TmX?-)e)jkGPfkuA z92@|!<>h5S_4f8QP-JRq>d&7)^Yin8l7K8gED$&_FaV?gY+wLjpoW%~7NDe=nHfMG z5DO3j{R9kv5GbssrUpO)OyvVrlx>u0UKD0i;Dpm5S5dY16(DL5l{ixz|mhJU@&-OWCTb7_%}8-fE(P~+XIRO zJU|wp1|S>|J3KrLcz^+v1f&BDpd>&MAaibR4#5A_4(MucZwG9E1h4@u0P@C8;oo+g zIVj7kfJi{oV~E(NZ*h(@^-(Q(C`Psb3KZ{N;^GB(a8NE*Vwc715!9 zr-H4Ao|T_c6+VT_JH9H+P3>iXSt!a$F`>s`jn`w9GZ_~B!{0soaiV|O_c^R2aWa%}O3jUE)WO=pa zs~_Wz08z|ieY5A%$@FcBF9^!1a}m5ks@7gjn;67N>}S~Hrm`4sM5Hh`q7&5-N{|31 z6x1{ol7BnskoViZ0GqbLa#kW`Z)VCjt1MysKg|rT zi!?s##Ck>8c zpi|>$lGlw#@yMNi&V4`6OBGJ(H&7lqLlcTQ&1zWriG_fL>BnFcr~?;E93{M-xIozQ zO=EHQ#+?<}%@wbWWv23#!V70h9MOuUVaU>3kpTvYfc|LBw?&b*89~Gc9i&8tlT#kF ztpbZoAzkdB+UTy=tx%L3Z4)I{zY(Kb)eg{InobSJmNwPZt$14aS-uc4eKuY8h$dtfyxu^a%zA)>fYI&)@ZXky?^{5>xSC?;w4r&td6vBdi%vHm4=XJH!3yL3?Ep+T5aU_>i;yr_XGq zxZfCzUU@GvnoIk+_Nd`aky>S&H!b*{A%L>?*XPAgWL(Vf(k7qUS}>Zn=U(ZfcOc{B z3*tOHH@t5Ub5D~#N7!Fxx}P2)sy{vE_l(R7$aW&CX>c|&HY+7};vUIietK%}!phrCuh+;C@1usp;XLU<8Gq8P!rEI3ieg#W$!= zQcZr{hp>8sF?k&Yl0?B84OneiQxef-4TEFrq3O~JAZR}yEJHA|Xkqd49tR&8oq{zP zY@>J^HBV*(gJvJZc_0VFN7Sx?H7#75E3#?N8Z!C+_f53YU}pyggxx1?wQi5Yb-_`I`_V*SMx5+*P^b=ec5RON-k1cIlsBLk}(HiaJyab0`CI zo0{=1_LO$~oE2%Tl_}KURuX<`+mQN_sTdM&* zkFf!Xtl^e^gTy6ON=&gTn6)$JHQq2)33R@_!#9?BLNq-Wi{U|rVX7Vny$l6#+SZ@KvQt@VYb%<9JfapI^b9j=wa+Tqb4ei;8c5 z&1>Uz@lVFv6T4Z*YU$r4G`g=91lSeA<=GRZ!*KTWKDPR}NPUW%peCUj`Ix_LDq!8| zMH-V`Pv!a~QkTL||L@cqiTz)*G-0=ytr1KqTuFPan9y4gYD5>PleK`NZB$ev@W%t= zkp)_=lBUTLZJpAtZg;pjI;7r2y|26-N7&a(hX|`1YNM9N8{>8JAuv}hp1v`3JHT-=5lbXpbMq7X~2J5Kl zh7tyU`_AusMFZ{ej9D;Uyy;SQ!4nwgSnngsYBwdS&EO3NS*o04)*juAYl;57c2Ly0(DEZ8IY?zSph-kyxu+D`tt@oU{32J#I{vmy=#0ySPK zA+i(A3yl)qmTz*$dZi#y9FS;$;h%bY+;StNx{_R56Otq+?pGe^T^{5d7Gs&?`_r`8 zD&dzOA|j8@3A&FR5U3*eQNBf<4^4W_iS_()*8b4aaUzfk2 zzIcMWSEjm;EPZPk{j{1>oXd}pXAj!NaRm8{Sjz!D=~q3WJ@vmt6ND_?HI~|wUS1j5 z9!S1MKr7%nxoJ3k`GB^7yV~*{n~O~n6($~x5Bu{7s|JyXbAyKI4+tO(zZYMslK;Zc zzeHGVl{`iP@jfSKq>R;{+djJ9n%$%EL()Uw+sykjNQdflkJZSjqV_QDWivbZS~S{K zkE@T^Jcv)Dfm93!mf$XYnCT--_A$zo9MOkPB6&diM8MwOfV?+ApNv`moV@nqn>&lv zYbN1-M|jc~sG|yLN^1R2=`+1ih3jCshg`iP&mY$GMTcY^W^T`WOCX!{-KHmZ#GiRH zYl{|+KLn5!PCLtBy~9i}`#d^gCDDx$+GQb~uc;V#K3OgbbOG0j5{BRG-si%Bo{@lB zGIt+Ain8^C`!*S0d0OSWVO+Z89}}O8aFTZ>p&k}2gGCV zh#<$gswePFxWGT$4DC^8@84_e*^KT74?7n8!$8cg=sL$OlKr&HMh@Rr5%*Wr!xoOl zo7jItnj-xYgVTX)H1=A2bD(tleEH57#V{xAeW_ezISg5OC zg=k>hOLA^urTH_e6*vSYRqCm$J{xo}-x3@HH;bsHD1Z`Pzvsn}%cvfw%Q(}h`Dgtb z0_J^niUmoCM5$*f)6}}qi(u;cPgxfyeVaaVmOsG<)5`6tzU4wyhF;k|~|x>7-2hXpVBpc5k{L4M`Wbe6Q?tr^*B z`Y*>6*&R#~%JlBIitlZ^qGe3s21~h3U|&k%%jeMM;6!~UH|+0+<5V-_zDqZQN79?n?!Aj!Nj`YMO9?j>uqI9-Tex+nJD z%e0#Yca6(zqGUR|KITa?9x-#C0!JKJHO(+fy@1!B$%ZwJwncQW7vGYv?~!^`#L~Um zOL++>4qmqW`0Chc0T23G8|vO)tK=Z2`gvS4*qpqhIJCEv9i&&$09VO8YOz|oZ+ubd zNXVdLc&p=KsSgtmIPLN69P7xYkYQ1vJ?u1g)T!6Ru`k2wkdj*wDC)VryGu2=yb0?F z>q~~e>KZ0d_#7f3UgV%9MY1}vMgF{B8yfE{HL*pMyhYF)WDZ^^3vS8F zGlOhs%g_~pS3=WQ#494@jAXwOtr^Y|TnQ5zki>qRG)(oPY*f}U_=ip_{qB0!%w7~G zWE!P4p3khyW-JJnE>eECuYfI?^d366Shq!Wm#x&jAo>=HdCllE$>DPO0N;y#4G)D2y#B@5=N=+F%Xo2n{gKcPcK2!hP*^WSXl+ut; zyLvVoY>VL{H%Kd9^i~lsb8j4>$EllrparEOJNT?Ym>vJa$(P^tOG)5aVb_5w^*&M0 zYOJ`I`}9}UoSnYg#E(&yyK(tqr^@n}qU2H2DhkK-`2He% zgXr_4kpXoQHxAO9S`wEdmqGU4j=1JdG!OixdqB4PPP6RXA}>GM zumruUUH|ZG2$bBj)Qluj&uB=dRb)?^qomw?Z$X%#D+Q*O97eHrgVB2*mR$bFBU`*} zIem?dM)i}raTFDn@5^caxE^XFXVhBePmH9fqcTi`TLaXiueH=@06sl}>F%}h9H_e9 z>^O?LxM1EjX}NVppaO@NNQr=AtHcH-BU{yBT_vejJ#J)l^cl69Z7$sk`82Zyw7Wxt z=~J?hZm{f@W}|96FUJfy65Gk8?^{^yjhOahUMCNNpt5DJw}ZKH7b!bGiFY9y6OY&T z_N)?Jj(MuLTN36ZCJ6I5Xy7uVlrb$o*Z%=-)kPo9s?<^Yqz~!Z* z_mP8(unFq65XSi!$@YtieSQ!<7IEOaA9VkKI?lA`*(nURvfKL8cX}-+~uw9|_5)uC2`ZHcaeX7L8aG6Ghleg@F9aG%X$#g6^yP5apnB>YTz&EfS{q z9UVfSyEIczebC)qlVu5cOoMzS_jrC|)rQlAzK7sfiW0`M8mVIohazPE9Jzn*qPt%6 zZL8RELY@L09B83@Be;x5V-IHnn$}{RAT#<2JA%ttlk#^(%u}CGze|1JY5MPhbfnYG zIw%$XfBmA-<_pKLpGKwbRF$#P;@_)ech#>vj25sv25VM$ouo)?BXdRcO{)*OwTw)G zv43W~T6ekBMtUD%5Bm>`^Ltv!w4~65N!Ut5twl!Agrzyq4O2Fi3pUMtCU~>9gt_=h-f% z;1&OuSu?A_sJvIvQ+dZNo3?m1%b1+s&UAx?8sUHEe_sB7zkm4R%6)<@oYB_i5>3Ip zIA+?jVdX|zL{)?TGpx+=Ta>G80}0}Ax+722$XFNJsC1gcH56{8B)*)eU#r~HrC&}` z|EWW92&;6y;3}!L5zXa385@?-D%>dSvyK;?jqU2t_R3wvBW;$!j45uQ7tyEIQva;Db}r&bR3kqNSh)Q_$MJ#Uj3Gj1F;)sO|%6z#@<+ zi{pbYsYS#u`X$Nf($OS+lhw>xgjos1OnF^$-I$u;qhJswhH~p|ab*nO>zBrtb0ndn zxV0uh!LN`&xckTP+JW}gznSpU492)u+`f{9Yr)js`NmfYH#Wdtradc0TnKNz@Su!e zu$9}G_=ku;%4xk}eXl>)KgpuT>_<`Ud(A^a++K&pm3LbN;gI}ku@YVrA%FJBZ5$;m zobR8}OLtW4-i+qPPLS-(7<>M{)rhiPoi@?&vDeVq5%fmZk=mDdRV>Pb-l7pP1y6|J z8I>sF+TypKV=_^NwBU^>4JJq<*14GLfM2*XQzYdlqqjnE)gZsPW^E@mp&ww* zW9i>XL=uwLVZ9pO*8K>t>vdL~Ek_NUL$?LQi5sc#1Q-f6-ywKcIT8Kw?C(_3pbR`e|)%9S-({if|E+hR2W!&qfQ&UiF^I!|M#xhdWsenv^wpKCBiuxXbnp85`{i|;BM?Ba`lqTA zyRm=UWJl&E{8JzYDHFu>*Z10-?#A8D|5jW9Ho0*CAs0fAy~MqbwYuOq9jjt9*nuHI zbDwKvh)5Ir$r!fS5|;?Dt>V+@F*v8=TJJF)TdnC#Mk>+tGDGCw;A~^PC`gUt*<(|i zB{{g{`uFehu`$fm4)&k7`u{xIV)yvA(%5SxX9MS80p2EKnLtCZ>tlX>*Z6nd&6-Mv$5rHD*db;&IBK3KH&M<+ArlGXDRdX1VVO4)&R$f4NxXI>GBh zSv|h>5GDAI(4E`@F?EnW zS>#c&Gw6~_XL`qQG4bK`W*>hek4LX*efn6|_MY+rXkNyAuu?NxS%L7~9tD3cn7&p( zCtfqe6sjB&Q-Vs7BP5+%;#Gk};4xtwU!KY0XXbmkUy$kR9)!~?*v)qw00!+Yg^#H> zc#8*z6zZo>+(bud?K<*!QO4ehiTCK&PD4G&n)Tr9X_3r-we z?fI+}-G~Yn93gI6F{}Dw_SC*FLZ)5(85zp4%uubtD)J)UELLkvGk4#tw&Tussa)mTD$R2&O~{ zCI3>fr-!-b@EGRI%g0L8UU%%u_<;e9439JNV;4KSxd|78v+I+8^rmMf3f40Jb}wEszROD?xBZu>Ll3;sUIoNxDK3|j3*sam2tC@@e$ z^!;+AK>efeBJB%ALsQ{uFui)oDoq()2USi?n=6C3#eetz?wPswc={I<8x=(8lE4EIsUfyGNZ{|KYn1IR|=E==f z(;!A5(-2y^2xRFCSPqzHAZn5RCN_bp22T(KEtjA(rFZ%>a4@STrHZflxKoqe9Z4@^ zM*scx_y73?Q{vt6?~WEl?2q*;@8 z3M*&@%l)SQmXkcUm)d@GT2#JdzhfSAP9|n#C;$E8X|pwD!r#X?0P>0ZisQ~TNqupW z*lUY~+ikD`vQb?@SAWX#r*Y+;=_|oacL$2CL$^(mV}aKO77pg}O+-=T1oLBT5sL2i z42Qth2+0@C`c+*D0*5!qy26sis<9a7>LN2{z%Qj49t z=L@x`4$ALHb*3COHoT?5S_c(Hs}g!V>W^=6Q0}zaubkDn)(lTax0+!+%B}9Vqw6{H zvL|BRM`O<@;eVi1DzM!tXtBrA20Ce@^Jz|>%X-t`vi-%WweXCh_LhI#bUg2*pcP~R z*RuTUzBKLXO~~uMd&o$v3@d0shHfUjC6c539PE6rF&;Ufa(Rw@K1*m7?f5)t`MjH0 z)_V(cajV5Am>f!kWcI@5rE8t6$S>5M=k=aRZROH6fA^jJp~2NlR4;Q2>L$7F#RT#9 z>4@1RhWG`Khy>P2j1Yx^BBL{S`niMaxlSWV-JBU0-T9zZ%>7mR3l$~QV$({o0;jTI ze5=cN^!Bc2bT|BcojXp~K#2cM>OTe*cM{Kg-j*CkiW)EGQot^}s;cy8_1_@JA0Whq zlrNr+R;Efa+`6N)s5rH*|E)nYZ3uqkk2C(E7@A|3YI`ozP~9Lexx#*1(r8luq+YPk z{J}c$s` zPM35Fx(YWB3Z5IYnN+L_4|jaR(5iWJi2~l&xy}aU7kW?o-V*6Av2wyZTG!E2KSW2* zGRLQkQU;Oz##ie-Z4fI)WSRxn$(ZcD;TL+;^r=a4(G~H3ZhK$lSXZj?cvyY8%d9JM zzc3#pD^W_QnWy#rx#;c&N@sqHhrnHRmj#i;s%zLm6SE(n&BWpd&f7>XnjV}OlZntI70fq%8~9<7 zMYaw`E-rp49-oC1N_uZTo)Cu%RR2QWdHpzQIcNsoDp`3xfP+`gI?tVQZ4X={qU?(n zV>0ASES^Xuc;9JBji{)RnFL(Lez;8XbB1uWaMp@p?7xhXk6V#!6B@aP4Rz7-K%a>i z?fvf}va_DGUXlI#4--`A3qK7J?-HwnG7O~H2;zR~RLW)_^#La!=}+>KW#anZ{|^D3 B7G?kd diff --git a/src/main/resources/static/images/glyphicons-halflings.png b/src/main/resources/static/images/glyphicons-halflings.png deleted file mode 100644 index a9969993201f9cee63cf9f49217646347297b643..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12799 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 diff --git a/src/main/resources/static/images/spring_boot.png b/src/main/resources/static/images/spring_boot.png deleted file mode 100644 index d56f8e782ce181b7e57d3895feeea27939768ec3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38281 zcmeFYgLh=#(}&xWNhY>!O>EoFBoo`o#5N|jZQFJxwrv|78@Io|_pbX_+|{dloz2cs+IqMWm|8t|t}_w=03v@H0|T+1zp&Lf`KaLl_KkgT&_gz6I8R`Teo7^5XQnW(80M ze@bMBevM(jQc^}?+zfv$!U6!XRoGI{OpW(n$DE(G5m8bvIwClL6_T{aY`{NQzXge5 z9(OPRE?^EF^X%*gU;{8hC(ik={EL-&aLX)706*Cn9SZOcpbDI?%mN6a0S*80r-;Pj}Dqq-f74dhp!p^vN!kN02+Y%bMfM;V+I5Ytc?2(23fvH z(|rhlKOoiJp|ItnMtveD`vCZ^CBnJ6c9imiHk07Ep&VXDUb6XKJ8DuP)L!qVb}Ub@MHS9S_2+7R)j>5G^)PF*Jlq|6ZX* zF5ut{T`Tt+Dv2M$pNbJ6lmKXZ&+mtB>^DMUgp=5N$|)4{f8gAO!g#bFLcaKm`lK^{ z&}D)lWTC;X{{q#uu{iqW?94XcIEE+ftT&K2UhyeMZ#sU?2Aj(@Qm&ySeV#xCxP|_8 z(+Au4?ad_maS{tAn=7&bB={VaaFcKobQ5tC#UkhZrsoDLtN(5a!Qznir9=-+&j>?Z zp`0f|HPE|Yi$EV^x$n_RZWErdD^3)4x8Ii=e7+al3ekP{G;3+_6<2J|jS5^bqm@q*eW5DzFQMEb(Qj%UXfBpje999BQv+j~%Gizf>o5 zfwp{$;n}5QB;HeCQ^*y;6BU7D^l@ZWu#mU-d{?tx`{PY9Lrk64O^prs_miJEYcw=e zHn~w!GBi(ydmyN+Li&&8pVRNpkl@mv9!_32;NSqH&$loAUm4%v$N;gHU;6h3ep8qL z<0W6lmrK9}=fBI?S~Sx$1~^Cn5rD=HzyS4^bHtCN^e_8K0CZ{qJf}adn+U+lk1;e* zU0)c->M!0%i0}#|WFA=bKl{>CL|;Z(3aZhhqY0rV|S zny_gcAysgFUS}!R&zL{C!KDyqfgS}y=HTg}rn&Cs$eO`RxpL+-Yf)Qbgn0!gkWL8S zBmHlTlu~g_Lp2O+)QOsrjSRKwbkwO;1G0L#>oQi5*u&+FU^$VGd&%wDHo#kky{{R0 zk+;KvLp0YOo#?>9kn81VxNg~86jA7(;70!6dvlP4BuNiYrJ^bVfS7bY-y#(<{r{W~Zh~r^K6A#4m<2l zi(|=0){M0rh`6FNW$4K{;u#VkM^O*NFyt|~FpMxblI@b6lDlY|XtQXaG=?-VG}_eB z)#Wt0Gz!!m)yLJRYjw@Mr(T1E^CXocsR+=1kY^}~bIG)cxyxzj@0%){HW+G6C-@e`8EzbhY;-)iF9EL?&W9iYD2TLydIVx?0KxrG>Wz z-j∓K}~Va>IxhD+VqGT>)#FJzF#ToOi!dR2M=Qa*Jw9cx%4%qm#vJ!>iq^+biSw z=L`K!)UDC`?K{Fd)4S)(^c~{e7ZMU`EmAHl8dN4S7FsGM8Zs+pA?y{36^0ktvxub9 zh&Yv|d5>bo46K2+y2gqKr(CP%bNh88R844@WayZJ#rC?$atectBaPFHYYrk4OB1_( zhc7Ud`IL!SKv)o-YM+{(Vlr1U2Q%ZEAdK9POpHj6w2#=t@sYp%jux9zq*uC%-H>Z@ zcZYBnc)`E5zDt3iHiR>$xrN`Vl*n$@Kgn39%FH1Nn=kc zis;_x!O}@%-&Op}BFraeYC+V&o6_pNr+zvbxH$ z66^wviWk`kt?^$pY8A?ss#+R6vK>lqc?c@Qs+2M%I>!yw;q*E!ny#WT64PR~A-XYJ z@!fy%+srGsT0)W5M^#TFLCp{F%AMNyEdE#iOH$DUpJba$N0DZC`C|vvahgy7$|nqI3}v0c#X z7nuLD@v{Sv=JLg&o#3ita<)vFZuUCwpFb=e*AOBoL9L-Vc?Mhur^2WBbNO5JVhTeM z`w&MF3s~4R;;d478`yTiWs&H_l?bskIi9BH_voq zv=2TVsFV?o_m5voa8E#ue^WwJZZ7GRXOpRt{m4Gba4|q#E^d|cJ{>!SI7P*7#-?Q7 z&**1=vWi=aZ#Z-=7CWk|8d;jD;?rQ(aM##hby1;T7Qmnb&WHeMfJ)o!{znqN|{Hz+mM zmHO6vZk>4#c-y}PKhHmd63od0wJOfytD~ z(9qJES6LmH`}1We33=NKoq!^D-kWt5lPsr}x~ z!aneix20`1Bk!ytrM8-SixczJfq9piw{3ush)99?{+9AdS)#JC+FtiS9RGt*=Eb{HGCHjCzm}}P9IC~ z>$uA{nj4)Ci?TC z>Kyq7QNyW6_2n+4O(ptVUK*(W_t|WesH}r|sGjn`9lai%GEqk*eQ{;6pOxO6{!hjU z*(t&7`J%6Nt^R;1tyO;K(9FWxgtK+7d!bmlNw-RwbC+DnC4NWAV*&&9ezLJzPaoZ+YV<>GM-VWd6@#A=w(XKIR(f2wgA2YWsTY>MNMJQq4e?l(GupwF` zohF{Eo~WGRFV!$tH}eS#Xu4P(aOzbJhUK=mCSX6jz`xErU^ei!k4*Tlz(s9)Hm$1eMhVF%7xw;Yn zfI&n0W_=w9XYTJB3WgG+EhaXj?SvuouX~>dS}E{+z&M`!6t*p-D4Z<;W$H-b!SLxA z>QHv~=Rx%`_Rhyt4G|KVI*F5B;LkJKX01-W_@9TwU3#X1(ImD7vE}L|_ku4KKTFc{ z+pB})isO=_o8zNf=}QRmpHzG|cItNN3J~^xB!sgC6=zK5FIBU+Hq|z=)+)6mHz7A4 zHj=KES+;R-xkpebPidbbG3<#whLR(w`1xSBtK zAufa~$(EE^HBOky;7_*qaeTyBmYtn=(&A}&G?qAYJJPgenPl0j&Z%h=F)7jhJ3yZ& ze6^X#_ncjnZ|f-l%-HNv6y_#!Q>`_cB4@q+*nCB%vFhrk$awf6?k?G^PoOVsfe!X+4$aNx*q(-IG9&iPQ>-H$RvayufX=lBt7~PHE8|?@ zplpQhsCJ#XV|~~$_@$&T6sn~zH=vqG(Jb~JJAge(P!=+`&rE1yc4&G)i7t-jnJ7!$D$?M94mekS<_L|oAa4d%h}6G1y1e{(%_OPhbV8kM7iW8xKsiT8Le}3 zdINs5Su^*(KNU?iQSEfSk8PV`Z#oFX(XaAoCBKd_QoxppIOtsc-%w*HrmYwlY`K88 z{4HKI@A)5cU{v3x!4#^-)olp}2=86`+jc6EDANJfwD+>=;;nR95ua8@I--V!(BIAO zCGf)$!hVIn;H?v+;M->Wi=Rnp%{WaJm2c0^%rH!@PhCq+Zj|z#|LtKder>utiP(Z> zvSusRqC2&gZkpxCeyO_EcA50z&SU8MijjI5&rpalb?MW6todty>-(F-V+lHo z3#LQ(2?O7*Ri#Dn%ZdRw+v!B_J8> zfZg!~{2Pbb5k>aX<^wpx`|+UvTZdB2a}cB_2h{7{OuvcX(GsCG3(;wZxI!WS62yx& zH%6R`YkhG9$M9=ht? zn#`O0HBLRy784O<$-^<0H7>G4y>oa#cbu@_@y~WrfCPy_5~C8m9>s&EQ$$qmAkI8F zwS2|#h6LI&jC@qS49bw zONWFzBkx`3lt<44`s3X#1I&q*gbon-ce?>uU_deIAIu|E0{Q7Rx1WabdFV-~d}tL| zDfnNkvwj-s_k4p4P1Ki0?SjEarsH(q(loSq>NOll9D#?Ywl%B0b&i(3qIrW?oqTj+ z1#c@)9T+)$xt`ar#LM-b=#NPp<-FxRgk36*ZI51?d9RV;;`8k+7-lb1UEOx=WMgkJ*kSaS+j+eTZV^>m|wx2?0Y^TEsV zCHuMr>KdjI<_Aokuw36a5?T_Sd?~|n!-PFba;*5X`0c{>qNg$N@w1V-v92WGuTm9S zMQJ63UpI|DS{*At^N-#@ z&yJiIoMAWw3J=^&n0S{yPWqlK8Ff2$K{BoI&Dh-m;e{;%f81SFV@VoD;E)JcKnp^} zUvs#6bZc;T;wGFWC?cRDY(40WMS-h^4M@9f@1yrET(CtyIJSF+V;3Qg_7^16w>qgC z85BtJG$n$$cILkN){DxG)OXY%p}{xl?_)8)b<2Ge-^zNc%E}K*@e7pmJvD#@e+%<+ ze%0OV;PB5AH4HUHU|c0fEzz>qHOE}yQI(3gm zdqNllja(^^GU@ijRmF#4ww%OwG6`ZWxDK}aY zk`XnfnnS4_+VNbtSY1^h($G*R^Qd%){#cQ=39NZ}c-)%lcE4bM%IfU6doq08MgZq? zV?KDgzNvlMUsq_%!4P=6?z=FVt-!2V+_J*Q#Y%B+vDn*6oOoM6R4Yjlu=u#-M7`$H zUwRl%uVHp9Y~$GGK5ppA@X65_dFlPwKOq@6+}lgYNfsFQm8Zw1wbt`!rEQo{=kMZe z$nU7PIIKxR_bZIzUIPI9YBj)+bgL`9;vk98ztdjoX=qexXlQUF`0+uu;@ny4K?3|n zpzWSP^+r^r+4fgg&#eHB)nMzIdp`)<1Bi?ymE-^b4+_wk0s(+m&{uvZ0Dv{asSdEssSh3&BZyl&J;%VM5m#kMQlSjw+MN2ZMD}>VZ zJ;bu(?xU^)GM2=TFR|l0C!%j*VE@+D#KlA>3$Rm6lA0U_gyR&cDA+WcVRIF!&|rcL zFR1=j|F<=WCJ62HzrDo(>0BDnE@37JMKB%E&(XrE{ZLN+cc|>QL_xLx9S4&O-u~aY z(3An>GwA>AOa=S@PesKXn23R5@g;yFqq#~SZwQn3KPwqS5wp^c=L^h+2e|82GCPERry&S;44;S0!T5zY&AVHC#P}8BP`k$ZcPG_X_Fp$5ahtK}s2+1O$ zNLlKkemy@!Lg5h0jt9;!oVS}IN!>fhKR|e0ym!pbAPGY|i@j2PM1uxY7I0u7=I(c4NStY7bu(Yx+r6Y54p9 zeS$y=Rkw zy#;79-84KCwF7>$CP7!q@Idr^1)i^r8r6Fi-$<|;sYLDosTP&UI?WRIGx^_2AA zE0^mOdES18=1q|e*|oIjsqSJdwZIukO?y*}h3lp{3~wt3|lge+foRP zKvRWtn#Oy!xvlMR>dnNoN2hckLm`Rw#$U4>j#g`ko)J# zzLo99#NWqFvWZr@+8@w#@bGfxPaYytW_UdZ%0m^=irsj;0(N>J(gsB zV!IBa#k!g3L~cj`Lu%WGde~xgNzKI19&t0 z{cBh`gfO3UEjT#1`jnPRYizNprM~eNZ~DpUEjXegOc~aHv{!?2W;6Q3@MHh|;t;q$ z|NcjUKN<>LiUu_OG7+<$5EsxGMqJ{Cl6uSM`|;o0?*DmTSgwm{6+%OU-vioBJH;2N z0kE+1%u=cLD5{zHg3v{IFjHCfOv_7~$!p{})!A~8uKwFj?;4-?XABl6eDWhu2If9^ zG@(LY*=LCs262t?1CvndcKKQ%Gq-Z%|o(8)@3x~}C zByLZ%0wSjYCi;GupuTj%m>qgf>q!okqf1@_e#;lgW`l$-pPAm)JkU7Ata*ql@FQ{l zNMVUgiPrk&ajd9N#GJN6McQhvJdQF)F>^EH7<1Q3=;_t zy@}jf2zV8kqE>BS@}P;KR_P|U9tn9R|E?U_EW4Pa% z;uos|p?m5k9Shq^hOj(vSYcPMBL-;-i>$&i{;sP43tqbLQjUS zo2>ww)oDQMjv{w=nR04^D&JBX^jJD6~aX>e3pe{zXX@r8GLMiosIGk#5a)s?bh; zkDsR9kM|{^WN$*36O?eo7g?`di^sQlcb~Hu9trB!lv$%MJ!Oa+r{ zCwEz;&GhucU))@_V`ZLe&fU+p`U?^~J!CNSUp&Q~Ov-hdOgF;6yvMG0uX9(fRppp! z>}O|zu>*_Q9KjO&$URdalFKGm1M=&X}j0#D!AfJ5|yaM|KZ)&~8poA1EBVw|? zAm~%wUqYXfB)MPLpWi~Z#0_fQR$Qx~qd>l~-TEk?cNgu#X1o8Fujf$xiP&%y3lMCY z3r**&taoSg~)k0MHT{N&QLCTC(|B}|`jzyH^@ z;7WL;jOo4D5bNYBGmWnm62!Jw&HllxnL4-i0C7(w3`G*A`}vggb#NmBswP1>Ugh#! z>5^^fX=BCHU!7Te2FjfpKYivT+!^e8V1(2M05pCQf511{Os?sq$c+c zBLJC<3Lyz>W^H*uLGKg6Q_}^BweKWdUJx;}-VH^#9|@;A6-k&h{^Stk#yh(ozI*ZM zmhR)XPcd(XHjQwZUx@YJ?Yg9Y{Ba)Rs1y<}ORI-Zlbnh#O5y`FbJ!Sr?wfkG+g`N% zjtq7BF)<%Pm{(L|poD-CMrJ_qyr)dK@AicJ?xDXCbf`~jfpQ``+(-@k)2r^1S!9}l^?M!V^8qRF zpsXL!{if?GX6o6A{FU@+BznWkzw-nzUi!_E9F zrxwXP;WCa_ou%E*VV(41yrlb_@e3FhbM1=h758EyBi^L!+B1)AEjQk> z5s$%FM-P&aJZRwI!L0na&V2a2$TI;O4L6n>-m5JNTnYr?#ajs{eOMam+?v7NZVG1! zd3IWj3|kp6J-jIPEYTKe7gk$iPwZagO%()_ebe5@%})UbJuN;tuhsqUlU#(8Bl4SU z=}yBUT7_wkpQ<<>XCVKb9?dpt6ip8$f6L--*NT{5pVb8%yuhYv@#X{_(x@x7#n&qi z1}6KGq}_t?6@~M@QiZHN?Q?)7ctEgIBiEh|c&N(t#6p;iQ-+;wBEa4C)lsf`?Q!*e zc1Awp%A|vA5<~DVKwsAOBj<|nh9^#x5PE)+BX*VlO8%gj_2so8=O_WwCkTBJM2R>L z@f4m0+UGFfk{d%a>Y%Gj!W5qF`sKEF^s3(&?h}O$(oVx&+^TyhSUvFn@?Wewx-8hU zq%U!DZ+WLmAa10f92pI&*dM>ay4_v}h$bNO+g+Jah?aItJGkyZu0PeDK6`*P%#r9c za=VcZRyv{x3nOdJoo;8794$&w*%SCQo_pZCAaTUF+BL_qmy?oscjoI)*Kgi8Hrc%I zNWoWXeY~(xtR8z^ye>QZd!|-Oa7g2)lwU1x%LOP$SED*VnK!7;Xu(h)lhW2rc`&^bnpoq8hY0ADJ5QQbYc#*v65^)zS zh{W>7r(*_+&g^(7S|?jQ$G%}1b@l?x4%;sg4+?jLxL;f*iX6JF!YZgZ|J|_y->rQ2 z?F6OU-nCvCkUvte07PN3d;I*6MKL|bc>2fn!UjrSsG-pQ2CAj&9XZDM##+PUQtIT^ zQPY8R&Gr3mi213d{e}u!+K>w6m4$auR~3&^&f!lRcfCsA@aE5Zm6dkX3$pl&0*)m) zz5I~URm6hpZFF{cQ$18Ig(t+m!-R$lDa3+^zG*J)bTWUItyf0 z;ZC=Ai;@8|ABK3R2QtH*5BRsiS+V;0rZCv=mQoS13=>BQ=kAXuMge^SG4RxvTwZr! z$IlZ5z(dS+A3?HNiS8OAliCv)+cc$`lCO;gzSzerf?h;+@2r9>NXp7d3V+a(K==n| z8&&e$t9iBOxg2W@L}@=R)Fe@>6^ldg6PnpyFJTLOU@VX_V%@k z2kIvHL+oW!Y;f;^`T5?wmzQn7+Jzf(0-5e;BELw=+#V=a=ku&d{lokX#uu^Dp_{c? zRdanidJL+VUac@K)L&7(K@rYZTD4}kHXvi5)!|C6)8yrC`UKJS^TYz= zl-ktL3TZ5R#Yp9EQbi zPR*IIp2)NqbUA8!a_(cpf_ zr?h1u@wyl`%HQ0so}{Dh9}3A!U|Ol$-6vI@Mu#D@f$Ssj=0q;-7HPCpdSenxpQPLE zSI3oE?$k=suL;PMb`jZY?x*QOzV$gtD#uJUPP2iJdh0Xh?;@?z67X*;y>FZwbplmy zF;7?HI+CI&T~Nd$Pne2Yow0-HG-Wh8BNKgGpa>hP#guRQRV%KNJ`T)cmt<&))u4N^_R?aw780`hwKm?BsjtHd(Yvus%p!%JM{ z?;B=gS2O#ji2|3eMf=6cmvdCwPIAX?PfP>?*8p4Ho4?8=;m2Urn_@;l==I^{aYhV9 zoS*wgLAKmR+EyI#$OP2zeJ2kmm z+|V?!mLbOSkL@l<%<_7zD3=fxp<<{IF{f(@LkY1W3!!p1gdZtlREvuA_vIJW#G!6V ze4BH5NK)Ec{%*xxmmPFCJ|F60uJ<%v^#pp_GjPeV#|>-CmH(<}5P)*nin1Pqc7nbi z>;UPEE|=uuN>`}CQz&r08v+6WHvu3`ngB6&$B^_tDEBpNNvkGpPhT1rT{mEBLZ(xE z<3ciRZnH5F&!#BQ)cPBVhGtuQpCB|KWl0-S(3eEmwK9RPipT9}q#*NxOMD{|nYqs* zq?4^EOb^lSD|`DoEaH>;;UY{F6=&91fP=m@fd`}KJ18B}v6!4%=VTzp6>Kq`beJzC zX|tY7%hE{sGW%PQGqDP|f4;|U%l$l-@wkggQbok^UMH&r55Be^j(4@Qm~;lYmi#O& zh(T4GLn2;%k%H0_Yn9?EidHVr#-3L*leLkQbY-?R?f9IK@?e~>e@{Gfw!nfFLvTDdP(I+4J#zBza5M20E+;#$e~x^DR9-;G49msYW+hdA8t5`2NWzS3;&e#iQyI2y3P@`o!VI zR+Y;Kii(E#1M$D!#q|fyH&1MPHeZze#X0E=u5j4W=LYI&J%2eBcb{VMp9zc1J53WO zd8fyGSa(p~HbvqpOXtFZ4|gEefe^~Gqp(f^77T~@YkL4Mfm=Eptbbp{|IV%jezkyrn;c*=>S`~ z7h%(|nJ9+hHBFTi|1 zy2n>Sd*KjAO*&)U6n*e_vxhuA($G9X;9G2*H0=F)%1cNf?Qp-=_oPthBWJ@g8;XQ9JbB=6Zd(1Ya97Ch_BO8N{Qk$+BuSH zX7hZRQ0JIyHBzhjQ#24%-^X zB<@!uMveE`6J@U;5#4ufwzWVbKW9M1eBh zaUqoOC9%9US=rZiDpri@A8n57eUsgYQsdUiHEW#6LF*1F^K(i{3@hU|6vYIe3?_Fm ztNSt+VSl`;<|RV)gOscHJ=4X}%Wu%Dq%k@u( z1N5707#;4t8E)@0dNWytq`J^p%_>hzL-1%2(rtE2=#`b+F{eQdqfeN{)d_RbS?4AR zv#sPBb9(_GNyxzW&{CGe#~YfuI$c*SLR+oQRqc$l0<`w%wdz1TtQ9wI)s!sBiF$&= zXxRgCPa%y~hlR8g7%;I3I*pO7W;Jnbq?L#VGM(AS-5ST=pBd3Lga=Yg!ZchwcdprY zOrUbeRB)Z~f)nStikwbb*yK0n!~v+TR@i5-89E7bj@gAb4whn=(fM}${GXz6XOz&! zA5zWP6NCn8KD5wQb3*PUY7er&95WZ#(N`4fj4-rp_J>`)N^!flJv2d{L=5McYAoU; z|E?T!iL(Mfc)4>QuYbC$xcm}YNjY|ZGM0iBVX;cmXYLDl`y2tiC8bI;Z|%rw%BM^o z8ez-Ea{dy-9rHB9s<3027esmURq10iM4V-UV{bKL*|YEygKc{ICw+(Q`!>fA!uBm) z>@QJvWPPPW)W;px>q_UG*oDnz^e!LG2Kh;(lkfzT-w$+))lMAfP=5r|z zlQ-1l2_WClk~zthwAJ*wKZ-)tY#6Z4ZVXs_;WNlu6m`N5G3)(4o(9DHh;jiJa_-ROt79|)a+$VxWklWG6mnZ1e7&Vnbf1+O(1i5Z3(Ih#=nxc;d^htcR>Rjc1#e&;@)$v`j~8% z$qZRfpDV0(LQZm@7w>U*{HcsZif2<{J(K5n-&BH;6OLL*`q zND15^UOIw+&85VE>SA2MHqS6r14;35K!r3=#mTTFAVxOJ^N1Ubdi8QWJoUv3qTu4R zt1-^O*VU)`l(n_fOdlSAoGTHL{ZrDI>!Om+=r5la&$8~!u%O9QjK1jc>|?XMSzO?h z`eyxPscZi}zFJP5saC@{DywkGJFn7a!O;jUCe5TBHmv`VSdA4xt(z zC!o*ssv!!j(|XoF)TC4*5nrCyqgT59HQQBs2yo` z31C0Hm@y(zbOYItT!P1OC5s1|Y_V_w*lg{X1w8umjL5avG z>8NMZR@Zj+)IKNXwR;P^E~4q4t{+TlWAdN?%FVYjfsn55ok=R_F793cr58ox=5APj#yIc^46HKXeT=DVqpn2 z)`xCX&th+xThz3IQt*f{LYAPE`+Xq(gSYnBSNV8!wJ*y`&Vu;AE(>M59gFGHHvWKwx#T1?wZhaaunjPXIe6#vl1ryk0Y@h_yM0_f3eem>OMYM7_?v?Y8PG zm)nn*)MTlN3s+|6FoStJgrM~t=gFP*Y2WOl?gZ2|xB~BEu->U+It#keqQw3mUy8DD zE(48qJZwdaYJo%sZD1d8AN{AtRdJ@Qllq17^mXvELHmWA$(n!yPWJIlu1%Aw#l>;)9dqOb~2&}a5f z^x^n-Gs1Tbb1gXsHcQ@>!u1Tnu4B#YfC?-PmVMBIMU2Bh+gE+7UK?QX-8;ktgVxx4 zeGw>fy>YHOMuM6PE6dFhAZ2bI(}=F2=ykk?U@P!(qq%T7 z5v<(x+z)lkui39JEfxK{?lWLs@8@KzwdDN!^U3L|652Y`<>WL`A=b|2q~Gk1Bm=L_ z>o#_|+&wEW;mzA&4%(@9TAP6D+RRIXJ#d~a3)W1xcd((_PNkj z#xB2Pt6KtKX*mKE? zS)%_rd|BL2gt<$feIC}-(fM*RHF_Rv z0GU3=QKXqBOY`@It!L7IYa0BJWyeXj8&M``E9XFceBgYXobvoU- zT_3F^kE)_rLCj4@o+PD?!RivqZ3{^@TFmq@<;`N}YJp(mjz^P}&RheFwmMwk0QRmK zop2|M<}2B~NJd$;P)Qopgw^JMa`rmt^etrOg-_7t^sIBFo2Jni37LAMJ(lg5d0cn> zl9Y1(q}^)Y6X~>k*j7TJom`t*fiGz9x*-SQ6#f#pgJ5CQHR_+d9Ga_@`I=j;_c~se zXN0#PUmK6VO&XZ1O>VjyeskNK35eG7JX$xXRq3W+T_V&O=2-S+w>F?hdUoo$9Os>5 zaeVjotiA)M)0|V`i6B-AFOI-hUANqXWPX8H`3dN`ddf6qw;;E;;A5iK9B=~0(L^*D zoF`s_xea5`OYl71?cJEw8$unMK2pP~-F`0S?Br`K7dl+P5G(oiW`on*nb{rBwHRrr zB3y`*xp#Zn=)J3gEYQsDdQI4}A*rg+5sH}kW?_mpK}MaRaXvypp@8oZ+wJx!Oy3@n z<>W|>DDQiQ6FW(#@sE=Jm6G4{hT}qNf|P%9IBv=t)cBQOVcOckTCwp~c1CWd``6tJ z`tJs;-rliRPewDND-e~r55#TQ_xc7Z9_*_VwpOqgj&!fGOut7BTUoeqo)c)l1)>wa z>hK(bGUAm*c3thY1%ES}RsA{3(4ci+q`MCfO4y8pp7xOsG(K5#I}p?XBERY#H0ZPs zdTD&e_Bz>6Xee}W{`owf#+{G!SncrZMfy*~aU5#{B^CtOytI;5yWiRz`4pHa?yY86 zVY4wZA@@_m1dWE|oV2ltP=20cnLM?=+hwxwjHDw#7moRyc3w=cgIk`5uIOOMz*?#4d)U3@TY%Z!=Te$UR z!-{i}x7|rv`ogBG;ZkxT;WMSAk(r%5UU_NtM}Sa%@(qiHS}N#2V$+b*A819pKc}II zguA`|$Y9pDT&lorj%@Lg=l=9QXf)bviY0m=VTmh zLPyeKu_D^YCj5og7~p}te|%@@Y+K!7h;qiKikm{%bT7nv@yIzMyxFsWXn=aTKUt<&8W(JIP#@!jX3U1gVvTrv6f~tkO9_m8Rt**QC6=8$my;oy zZl8AoU-7F5B!m(cRWaoj+bxNZ zG?;j-o=s0kjot?-PpNV`tYycS#f6p6YQx|1E>8PMN5^McswZctm4wy#kgu_@Dz zu2}U%@Nu{Ea70Bs+5py|wnPl?n=L#$j(E@@&AkR!9MQw7-Ce2p4tLH4rOr*9%>Km{ zHJ9lRP8@S5v0uFSYI|8{eaHr_;hG7Grf?GOX4I)e=Ivri(+8G$?}Rst|9!)G!@gN*^%zLfTpLG+bqoP z)IUr$5po>1qRK!ikZ&EWV(JwCO0kN!re+~Qkun0>gW$GWBdxJAEH*Jn_jgS=zvs@DKUs!Ak2~KB6WO&jqA7h;)*;MflE6ST)H zwbQe{pavb5?&fk}mwxG3hR+xeHmDLnNOa9ySA`NHn;mv`ZfPj>3Oe4@dFAipR5ffP zpL3>*;zU!*FWS{i_R&wQ>(UAvs9Ep$tpFP%#B-ll?P|oWt?XKH-UckA4K}TFCcog>{1(jwiR5+X=s3}?>lXYal4wa#?7;9h(MExMEV{PkgQS>EJwK5?}e)X+gIyD4Ungx`)u zx8g9Ikh=cMKpkU!m}7x1$8Wl?ss+4+6j0#Tw0U~fhx{Zc@0%g0bW zLcKb+GONewa{fG z9?t#>MXvAZLSlHLb|riN5k;0=#RvKFB!7mlw&0P#99XIX#iy?6nGG zjK0)FlU+F+Cl#_IEvT+He9VA!N4QQN;Z5r(DK&?I2IZ|^;+QLD-I{>sI&0#W5(;5u z4nyc}sB09DiS89M;N!e*sxP?DdUe}YSUi1|faJF8Pw)?W_$QsRe-07H6<2wgH6Cqa z6pzr}k~Ib+)r}v;W+Jmwppjx(=L^p*9B$(wy7W$Aol__zK1jHP&l3FkUNIqfyHzl$0smrPY^=Z4DLwya{rw7c!HukE(BMTC(jR+1NoJiIsUOnA-&EG;!z!KE_NC~E9dNwfo@+XR z2pt*sHCR?4>Q{2I!vFXhYdRI{P) zemR51Yk=fx=9E))({*-#4p4x9o(mHuFbZ@ade<>}uv7cRhNC3snVz1sr1_RAVT{$wZrcrZJl~9H z76JMQe_w1Zrr}WWBsuI`lnMca{)#*q!)$zfUUxQ;m@2U=v$*UC-oVPccme%ZT7JUN zD@Px4j=bu6>l5u1y$_z)E_^SIq38%clp=uk8pTtDAdjQIb(Xz@@cEcxb8EdCGYJIu z`$)e;1_31_`Tl>5>ccIeyxl0{9R66EA!RKdB4P%Qfh}ntIQ|WXlMv5j+Um8-8ZS~$X=Qk^9-&?K$f{0rUvhOJ> z;QbEYy(iEV2EM>lM@a%XNdU%yUDZ0#^ke)yZVRgA8xjnNlt1=mp7sYnDb}5psYa?# zHkObsX$ma^*{}|Seuyjn?Iv}*Lob7|$(v!RKBOqQKc^2tB*qwd<^*72ErNDh6xO=t zmKd%m(BQx5Ai&XMOyYuv+@Lsu>{NG>C3uSz!gm=p$799;cwmoJ#?mZbskx?{#MZd` zExBbg-+7z6W?@9y<@WkRB3jSLvnr-1N$+WL=-AA{9`o;pL-cO9!41iu5n%pnt?V=o~(_{8!(;h`2P?;{GkF`OX_HSZ-V<|@>HMsLSl8_*_LN1RHTLedZ=5U7D62o-jxDzQpm!F_*MyuH6!?C z|7;7Ax_VmvwI&g@(I8##+@6L^;0|%lU2>yso4I}Y)@i@yCuC!LG?uiC=FIo z`*x$0|70i-t>DK04tpL44YmdZbI~Y3-Eu4u{A3KUlhoiAGpb4EuB7;K-w}9*!hKYw z(bT97bS%)1PTb`+?5V$+d@{7%WA-BXBkYLm+Ur?;M)uSM6W&phJa))DY)ymT802xi z4Wbfwb9};lHj;Q?WjPalYt?KA%hy+PWjNA5dvDZlF(5~VwJlo6GB&84P9$yDz=n;BMe!0@UyoKY1~3B|9RpgbUq-qQ%D6sIU=ae=%u(sN_!s_#8%6*jK@2A%j=RDj(AF1k)^L!j)O^*L%eW-`55(ze8HN@=$-fM04=N?V|2T~n8UHs;`%78+& zrv$)0ZfJhTPmLPfTKGHFqtG?%COK%WCG(8M$Q4h8f2I9EEIK3lur{l%mLVLFW&1z^ z$j0jnvQ&KC*hGx19eI-&lR%&`1^@_8W`i_K8b~}~Lj^imx;NNr_>4JhWvQSf#D^5W zM^m3e=0I2_{IA0TE54zzM!9>ehx3vh0h!w%lEci?25=YaYIeC z9_4?Z7J7PYmC}3Icgtq--H%51d=o`7>Zg+{{?d>Y-k^6TiyL#qQjYgtOu-%QUh7** z5a&-z{=#(X9JW@cZr29atEc0YCE;vKzQ)ZeZN`AJKjlR1#!w*g6ivu;x;n! zxNS(VJTX&8=wxf|S5yE)76_f4wy3Ao9VdDF;;;YUaJP>e7HE9(F~goJ&)xNk64hmE z`V~1jF=GBuLCdzQ_X`=Ql_74M$DIq+E2Z{rZ4CeRWbsZeiha9YdiwOjLJkYr=M*#B z(0dFx@8%k3eC8$mRE2z2lQciIdUXUfJS%4DS&Nr{zrRq{f#tTJEYZFmWR)Mf!UMOO zr)-TDA^lb}K@wa8&b63q7`W8WV(0j~Js#tI1YP#+NSEWp4N~cd&KNs9-|2sg18Pzv zwQ-N>hF46Z(W(yZLk!<4SX>w(v7~f)e7-bN{Ahey#nY{%eq>Pnzv;0z#)rL$)%C_F z{PoqFT0U0;z7>;W1Mm@}zZj<%?K2b`tXTYrF_Xj+*yrKD`ME8oMG1En56`pD+li84 z=5=JRVE*0{ zs{%%m8g^z_R@n_}*g#TYWs3rx4Qs}Co0sJ5`j!Oyl5KQn%&44$wnMPKW$J6s zHhJ8HrpFBy-WG2ue$vSB~$s9hN!i8vIlOt#6YC$Hk7+lp7DyP z;3ceWf;XteIT$jRN zA{OTqDmi70hQ^GI6`R1ZP*t%y@A8KhIAsyb%^>FUxbHD8Snb}s1 zjH;HpP`mvH>7%TH`F!Qf+n!gBKep`i&i(_(?Hs)+t?4f&p8ZpaxQCKdmlsEx4_BG4 zb+cs9EGYP2dkb#>!UI_PHG}uyIC)fkh^|e61@E+-QJ~gqNd-uEx$z+dVT7ce!={jTLq6EyK9JFRL;(u{0V?fR*|?2_Vnk7eNQ-cK zi$lSkbZvMxkC7~7oq(!~UXnWXOOlblRI$-4QIiuaCgy{+;YsS*6*Y!fmIeA9Yd;)m zhQ2d$7V&&-mB3-p_*CJqjn@*CmjFh0*4-wwXVMEm7-J?~OE*S_7DAv&%4Ep`if0Nq zK4vAH;{yj&ub^G-Hzw^h>yKSQ`kRF;-XZ2uLaCK%73Fvm8>_NlU;ENl@3B>8%mvVk zbu2za;K#hB5BFSjE1X{e#Ot~l>M*(RKZ(^MtePy);2ET;-fw~Fp}7+4@a(+?>&9;f z3TZpxwZ=#R;ATTv0r$W1ki*#p>vuevuVe*{Jyt+0boYX zI9n?U5gEnW+XOQ3DYz#H>*v_G}_pW1!?D%wxgazZlot~rXg`wS_f%5TAP*K_Xe z6b=n%w=tcA!PCLhLY?rYh!Ul{4 zOkA=`nqJx(NW7y{HgAIi39fWRr#Yuf)emDo`yIBP_r6__b8SZS#dX=LLfK3H%mGzH z#75GhKyB7|`Xm|A#uh>3w7C&S)ZlQqm#nuktXjkKX{4ls7Yv#+O+{~NqtFTOwDe73 zCxs10hr{()W;8tB4(%#+{M1BF`A#T%_htf^X7bhxXA^)uRy7|AAW$d`+NV(Mg@<{l zbDMXqh<|WcV|rY?a=bM+7HbS$6dUpw_b>4MX`Ohpxd=y~X-M6#{w^4T0 zWHF$Pn2Wv9Of!Ie@xPFo9>X~x(}wUN9MyMapBEK+alH)|HOC5=EGZJ09&t<8>F_eD zxLXo=8u&E-ec~}3elS$w=<6RE`f?4W<}~SCsM=rPW+@s<6FUC*HjdMeIp3Q1ezaA1 z9Peth9P1nTn~@Zk7avZ$$k0Lko1LoP2`?Tm3b9ryuYxeNoE&0&_CLHUX3`aDUDiGg zK9R)Y1LdcxX4SnALG9sPhHkx~d6ji_VKNU#PDR_nXA)3b%Q#vBcU}z>*lOorN~3_S zk`k9%foZ}0j$_NRslo6_ zHPvk-dk%>kkg{@AmR;a{QEHo+a4o-@vQI18Cy@Jck`~R6Zcf6vEUdy(zwAG$#TD`CoZeMnpte@?Efr$e*rh2?`U%Z zceh16jbv&avXr#Fiq`ZLoM|==1)9*U%fDjHAjkfx-%=C+yJVu} zF0Z>tD2e}=qwLm=yXmRzTG?cZ_(Bl)QZuSMU9x`)=)Y*2eKZCR5(W5m0w%cjn2`tK zBfkCjMA;DP6cuITUWERGVcuwXKW6l5iBz}q74KIOc0H9v8Id^z@+`-tv4TLXtCePG zEXew7UIq8jwq&qBf&Y zCCqMKjZvTh{>Cj9vblr+pcY==V74WsSKgsW7a1-VpMwdcZTNBF2UQ>2XGJJj1KXFT z)ZKR09gCpPp3v3E!JbT5XYf*OUJxkEuvw_xFJsaUndo)X$O@QXvV|y2ef}{(cbLU^ zn=-`SxkYU=$sKwUW6efC8Z-)!*$`CPDHyao>$C{I;&Z3z>Q|+$j{p9|AZRCN1v!At z&u|Pv6O#a=1u$-|-=S2}iy!dJ0_n)-d#1*89)9@i?payD##+C!U#IzW0|3!=-`Xei z3E9lb_R74V_X_fZ)svT{mXv@j=<&6_;m+he{ccWag*p#!H++s#RUo676t3CO6pR+N z--b8AHdeE*P_8OEgGaMI*crcBJ~dmMJMGxLkVY%ukpSW)(Od--S&|jhmSw^*#?|M?1jU|p$Y)s&&=n3# z;c{RlBj#Q|-tzuG0BQr2Y#%b)E_U}62%$dN(-Zvt3jFEmZ^tSRj&x=MtnJHLclI(4 z>-NzH%?Ll{3c9z61p}O?7qK?xNv^d;HjBz3i(`sw06x$)x{JlVMyVn>ok2yu8Smd} zu(+%d+YJ9ghdtY#L1BhHVs_Slfgfx;V=P!6W#gR+P^f0*C#q*QVib=i8xe`XP#$;~ z`Bmfr{N51`hC}jkBt4pPyjMy8bWr5Ix>`;sDD$1v#aXzUMHXbEty^m1W4-kK3M~)* z!pQ(m-1tYGKm&$TZ&PTReUv&%2gD>iZ8R1oqGu@%d=lbi2aAAf_@%3&pzSSnWN|S! z6{cG1^~HZag5RhWZsQ&R$~TT^wh(TNL|N0R=*rI3V#hUDJSG3yl`|<^_E*cF zvKf{Sg9nuP5ofe#TL&D;1+8}?#P`xY{k0H@{ZDuRcB%NF$a>R zhL0{xf0JYmRKVFOx;HJ|nn}?JMAwK+>|lWRgKO%pT(nXwjfd$rJdO3E1p&$Z=fXu#Q93ITM12+XihD*TCpRg^D(@m;m22KCTv*hjmeNX z+?fm}c4ryeacIURj)-|lXDCVr{|4dfo%i(1(fXG@Mb-P?2rkiPOKaDxBs+i?u-Wj0 ze>>FG{9s56i?Bfc6qsh~4nk;BzHm9h2@>S62vu5k%!Zp%Lw5L6rd+}3Lq)^WRg^KH zY+$iuO@~$bN4z#lJsr+TiXVWtO+hCbQ+_3%*_c)jr(b5?Tk3-L5~66A-_K?Lz5usW z|3(32sgDU|Nt|jd$v&qPIYK%&>Z+Dq30B+kkrTm zg=RDlRT}piQ~nPQWY||%i2PS`kpF0%L=(3~X`x5M(dgL>Aq)3;-z+LgQ-a>uWe#jL z%e`lcbS5!@otV4tbe#b&uAx@J7Dw#OqbQum-w8Mo9vfa1*(4s};-FfJruwn@{{h>E z{ruMMAI)&n8b^Vp+Wp!3{x~#9lkt0z+HrP@t&o5O12y8(g5g8usV>FI-iuQ&7g^3^zYHGkv+wk3)v{1qV?6pVrSi_wY9+z9{&993!=PRYW5(cyD>XB)(X1 zZ7y~vYMVWwjC6kIGN&0^9s9iI1@Q3#&l76VXo7ji|J*jfVoO0{{Vp$_u|&hBE^;o1 z!Qnpz3u%b`UvF!=gZ@1y}IJ{W9@bno4Sp|Q9s>;p~e4}wY121C`~b7~j;{{oy0 zS0=(siVa?%zhOH*MQ4?^04N>peY%R+&ziO%;kH}3-2T>&HeU`MbuWs^Y$V+cptitN zCEPAa3wHv3#AR@od=w9?ha`VVcgnW^$nTVY@BgNHw$s|1Hd+!jkV z@a$;&qO^e!JD>U;qDvEQ@#02je%l^ufDbF}4giO=Gj*85U+K>|_#!-_Ewv`(%dL-X z?hk8qs}m3Byg7Bf#>7onRch%=DCV8{sX zFQ6|3M$ti(-b{Qp)*!pc7%<;od=JA@u;?11!y9T&2kY&kdn+7s#8TfzY6Tn3YqM7S zVWnqTQ)Pt($TjJd9HFHI7NSyETCy`)r&1OcqOPSA>B$rr@(lf8-u61pEjz9$_LWw! ze}~Bnf$MuC(PLJh%_2XOq@*vf_MYTg+*md)Q-c=XFBhYzrGnxdcDO*+;Wl`A54zt z;*0rQ`#;S#WcwtIiy}pYTt3CjAYoj~Lx3e33@9&{Q@t;uQRK+?9nZO$JW1OYT%z8+ zL$M#Yibip2CFBY_t^oT!^F>j0QO5R$DppF-DoE|kgmW+F`6F_7HXe1KTrId z=P_zt3S~j3inUjCAvHDZmTi_l1!wwwBMYSz!l7bIOIt)Qn&5Y44zJXxK>65e`8Ntu zPy-?aC*g_LQi_~=uTw7{aT}|)Jse)c1;)!W{Hplu&Wow?_rRV%wU*~3UrLB*Khhbr zqUfhVAtfd8u|V#j*+&67izg(412ps^6HbhSRQphKpIKF*+RH8^K9v}Gpd|{}Dc))UgoDfm~{ARArC~~PFUm@5` z8JjVm&zBD@AKoJ;WH{){r6T1h6tPCDyjusgc;CXqy~DLZ@4Gg#;^kCTxlw+h;5@iN z;)4eud$z;hvmH>`y;57gOUq)RqPCUh^SRKJm(v~Nuocee4STTSLXK>6xToS+Q9H^H z=m1|&GI1uQ7j1$$Em1%_wXx&jM`k7o4yQMiqcYT;fFS-123&gJ#ohj13tlOW)w;^( z4;eq~oNJ5AlGmw9z!KdB4Ya>wIM#JF(vl+Z@I!qtmc~ z;f(`s;SxVyYOp^To$F{~$b7ga0~5DsIv#N8VHZz zUSeN!K2!-2SH-0N3CagpSPfMEp8Vt{OHiVmP9Ue{Br)G`cY;>FdOZP>HGHz8a}AJz z*V?=~!`E{)OrZV<4J#EghC9?qnU1)|IYVw>`gO@ktnKwHkKNlMxt_1I`6gQJRse{| zI2NI!OoT@k{Rs|z85UEF%Ul!9i?5ZXT76`P9&VZ&gm0|Zzfr>hb<+Nqw~``Nr9J!x z@1!O2&8eee^hH+_V0HybchbgBr}PPk(|gS920KGnc| zl6@UOeQ&b`1r-$opSPfAhQR6-_qOfVH|c^8g2q*)^M9wIO`WWIeTRW%$Qf~hy*oc@ zqj=gI+VsCP1g{y$j35Vxc5q69_3C}R3x4FG1ud=TE(R}p)p9KE=bMRogDITM!7Leh zhR}pBGBLIjva;+(WLp`tI-o=+TNzKae2@Cs%-i0cX6?(ZF&W-oPV#Mi%mfhk-|DhU3aG}Zj$`euCloNL z=?!PdnN;WNGQj5X+B)B|#hJ=OPGl!=|IUjSAxDIn=|`0A@UD7rSAa+zJ8&#p!(uRg z@6|5FL_UaPJ!tA3BgsS93hN^QBN*x#oM@T+#iQRS?-QJE40Yh*)mzuYE2?bjSm2vf zzFV?Nx-X$%kSo-baMsatL@Z)CN%8vLfdMTi}m)(jcMh=JPD4LqyKD!eOMq~siMr(18 zbl9+uzHFlEB@TL`j9 z`m}hPvkQ2-O(-DLp=l~m_Dq1U>BaC?n*AY*0R~3`P|ZOLt%()h_yG|A-)g1f3KI&6 zl2;PDZK*lr>qq+KN0A&l@40y@=382lA}tuS626< zU~0AszmoldODR%vQafaF^gESjLZe`S$>jZLAv}=x(~fsq_Z3>;C_<6aB85@i)KQ1& zOE!XU%4p4c3Ziie|5h?Gr-FWi0eV6#jR5u;lBpgjZQs~BP09VZiOq=s zuxE_iP+cttDGe^21d7HmL$=J&;U)%x0%y(9G^cRpo6hIPyLH&^c2(f#@X_+(@f)Lh z-#!LKN>^PS8HQAO-~B+tVDpdjsLMymNZVZgsBi*TNV2pfX6{R*MB;&@V8TuD3A~w; z3)3q2$pSI=912j6F7fBf$jfUHI8F$I^UM(>s5Vs#DuL3J?mpbQyDH-{DzIa3UMGSO zg^|X9E6)+wfnHs;18N;(v*q}0fu*Qj=ly&`$)+R}g0^@$Yq$13(wVe2;K1a?s>TdU zsYd&c-Lw8lGB$HjEUhWZ-S^mf$akmXTKjVUkxOgEX?yaf^fihgiipbm^55H9_8Usw z)iSaW$`rihxtnt-VER{9(h|g<^oZK{GclF;oyN+v_p=(a!12WVX82gg#xJ{d4;`?i z=wj@ZwfsraKLV(A)I1q{7KMv0;pkg$kNYMbb?&u5NlwN@=Grpv9_*UdM;@n|91 zS%^5!5+lF$K5j2#laLq2P5GbLnd9AB-N_NlBUb8YC8mTT79mi+PL5lJ9p=|uXKtW^ zw}o<4fC>WfSKpt!H)yNHwXBnxiP^j`MWaFGzYVLmoNdzJhyAF2mE(DECN*6F29)4q zQ)_gs@m$cbBuy{;HBqf;lAn|g(2J2~gyf(IUK|29^IAVX5P3jg69_T_NhCgk(xXV= zaHicK>=dT6h^AKHUlw^wk#CJ%5c;Al1_YJaGf3K&qyg77Xf!!Q!MqL#-yZ?_f!^*# z61Dtjk%gWm@oWh%u=bnbWEeuh+J%9aOe=#28Sdjy_}cWtr8x-(637L+9!ds@vRWGB z4yuOVKQ&!P{P#jI`767rHXEOVWOfsRLm0!CXNyj=(;0EoPSsrM4!cJ0YyG5g)w5h?$sjbpfKmiLU<$fBOYxNfAmr{_K?>o>aB1i`+#m3yOs&1zTsWeeDk$^rwc zbS~N=FI=p2bnP)3{>R^^1!^(s3JNOkl7<`nel*h;Iy+Dr z$TKfNN?q!P4jRMqn`@7$cOKs|HSmx|N5?n?srQu*v?-j?)KWDPLpP9a8>mt0y+$rT zNh37JqpM0i?|-KT0y9n*Dx90`_GMznxsoS?Y%HnL7f*JavA+ccf$+8Q%qNalzX~F#084c&U)| z+nS?i^v}rrjEnL8yUy6q#frjDka2N5LZIL*G#@K4{?R$OAGw3;26oZous_Q%pq7kr zQWk#%jL-^j*pK|!MWH#!US6!s0I^0=9ghM^aDs;24R@8k`L!k=Yr`_RO0}AQwngY$ zw}pTZ4AAe#ZVe(zA1F^q;PJ^YeB^k}U?T*g*b5+S{k^fi_4YcKHa{GnnQx3+Zau|P zbeWEeNA8f++jS$?T~MB2W&iR2Ne0U$_xS9u%?tmex@=7Wp{Mt0{~ANW4>R@)h1fxy zp7$MQi`N6-HGY=U%uA$x+SY$OO=?R&5Pj35J3LIEIye=K_nc%=J2w?5>6jM-?3shV zzx+hy+dI=C&F1*jKLFWMOHh|kDhU{|K$KzF`L*7|BEDnLNeGsyoDsa>@>H%c12cdN z*gX=QmvEfgQC2Xlk>4O zNkgsA4fAfeTI=Rh$v>qJc8@7c3*Cw6glI=k?7}nGoLaYp=%Bvd^jaZNR|LiDUt!f5 zfj9#?Pc3~zl^ciCd-xIegRZG%oDx>{foH#>t;>C~2HOv2r#HhOV`-D`t0<6lx3_JL z7jqE7FCe+==6!&X(vb#8jtubR^2zvyF_d~zk#xH&x&=IZV3VLQb$4t+5i&B0NC}Lb zCynr~r)PhJ$t(gZjmXSh83Q6{r_@MFSEeLvNxN-EN$g$-_61dy8oEzvxzt&i8$YNK z@6IT>HZwS9<{_iHp6ySXjxhcBlUsFWiRf*(JAemzWjW0+?t1jJ*A_07HoTd_m-uK$ zO?V0uRbSVEdns1=z4}NCvLFf^VTBfw4j?B22=Svy+tj}Cb7NI8#5miv z=mNHzrq8PzaCGzcP^R<>b&k6`Jw3d-K%a*Cip^nl8Mwv+GHYwNJ|4tq*ynB`fqz+9 zwPOlkc_&GJnN*oA{t4~UAb zOb1{ig#C~Fp#u-LAWr;JO0g_#U~WL#DPQuro?j9q;tVU*c_E5jVSMn%YQ?Yqd{?`< z$Io&ve5iBEY@=njRgWLna;bSK=nLpz0f!a6*Y`n}kzW4Xq=_8J+b^^zBA%_CXICI- z3FI&YzeBUp1YW*j8#cx$5NW|QF0bFLjeO40r{Z_mB|9w!whkHhdyY@G(S)o-(u^xl zE>DT28i;^jMst(bbMVMmxVwoU1A(2abtV{qjSK>RX&qyh>Y)T&&i=p~(U_Yd9v7qb z$vF4}68^{2mqDL`ivc07=-e(3BoiREpX~UlWp>iR^NMu*IO}S^yBTa12CGX=oXuJV zvDmpacmT+liQ5D!_gN7kef0Dl{60PDs{Ni}BTqgTC8IGm=wAY}(f?=Jz3S;Tx|Of* zxbGK3I~}yXir#W{DIxf`58`($0;8|IVYEFHA8gC3N*t)Q3Atg@ zm1!BWND;Jeh2=ZK&w5Oc9K8&sjNRDFjS-J`UVy$FU|X6RtsNDY)lZUGF`uFdL&c}x z_zL9?^iFd_g6@g%W2q?%sdbQihufFC0W``Yct;#v^l&(MQeMD5DSM^5i50( z<<+Zl$2rmiJ`k$uE#PwOvzf%SQJR00Q|h!@FFcR438t07vf5>k_eW-FUXxf&cZ_%1 z3(k#df6Zr11ZGIL^?UZo{00FrwyfX!n_Wicig= zt1(A(Qk$gTgJ>j-%MH^ey0{+;OPfgl^`o904TY}L)}oKOU2enf?>BsZvd;w{i1G2^ z%qxGjdrn3bejx~=7{kF*hC1i9tXKlDl_TSmdwHycf1RjUf8jb`M?2MDR_l`m7H!Gw z0eZ4=b89yxDfafiGvi2D?Q`^Xotvru#1qaH-hnIs)^#7Y2lYt>ixxR234Yb-C_E{? ztFtWa$Aoeg;k{t>0uE#X@<-zdZV+|ByQ$T83 z6i+U%gn)yxxz@6{=7|RWCGh)G5GQFe5xP~So2Lfs9;KyKRYeNCY%|U}HI9Fg%A1_| z_1#V>iT4DY5VE#aK8elwsep6|Z3137#ln%sf-`;XMJsv;#Vp@ZD;0SN8(rz z33R$K$M7eBd~wiH%%{v5DxP>f&zdpf`U6vB^Yv-y+~`r>&1N7{q|JUms8YYDUlhD- zJmiHBPpI)BgH_292mdf3qA)io>+=P1w^%+>OPQicGjJVb$nORTS;ydoZ;ITto;#8% zq1$lPYfgNo;~l}vjch4u>^ol! z3g@SRNlOmr_`gxyz9_ovm8IVJ;nwWvn`1Ap#`o*tGmB93s@;xumF#x*_ds$1#YtQ_$iP$AzY&FgnsK zEW#Y6NH>z^eZJ?IviMvR4AL@v`SfS?LT+i~+@H5Fo>!efr!_e9C)mA3S@AZI7tT%Q z(>v=%Ib}F|1kQ1ed~GwMp};L z7H+sj9~d0`A8UCU?#R<1Ykj~h#x_T*V2vrN2siZwvj?f#J4Bb>%b1Kk z|6P)}@ZXE*R3)zfM|ZXp*2ZNL?peni(L(ziGTppT?$@(~mHcJAVzENiSIKq{7&@DL zsWN7cQ?a59%0HkHk^`c_Nb8Yt6C3)oz>NfSKv9jlP#1?ex{>ZhUchm_24ZZ|Wcg1p zmGaG}>8*jaT4}N;WzwXM(#LH4KEZaOnINXv?bU{#d(Y-dAlR4^1a?|45nQTkKOY;ZPaPDYsw znxw=?K>a=rzXQ1CGrq~}K<=q9+YZfL2VLGe?}G+%wR{7F?zTV6bl?Zlj9;XO(ISu( zw&l)2s&-5@rVEl5vJArr4Dm^H$e2c!oCAmfdwVv8z$)V-C;|dAm-R(5%YX}iH9E%t z@d`P})xi>h^hFlQN}`1A<{gU<4u|n4tX}aIgzTNrE4$^)CsWH#gwk|J%Hl$OaSLUO zL95?U2S~=*{fFK36F2@CK}m%34ywW(e;UVsUd;6vZXC$`S2Rq z*KtH@0@EcGd@#M^7ys~&a_Z0Vt1DQX)83uz`8)?CK{n_U)idNI>e+Avez7bKhh`ds zvEco)VMs2I)q2liI_YWlXOzRlJwL0{(L@$H;d8cuQuAKCyzS1v3hpH_Fe(B=u7;*F zGng$^(diV6I{1IS1MOEQ%7qdLYO0%{*@5sc^tk~5AStTkVIlfxEF&y0UPA*ZTR=y` zz5eU=Wn8Rzl)$EL_RB<5wl=F!nBnnUrPjDp&*it@zVwmj@~n_ojYPg)kPSir!@Or9 z(a%y7?PCgIeglNURbQ9;z^&aZgU=UQ;9D82_V-KfI?PU+iDalq4_7ikTLIqkeT#)Y zhG7;*kQ-&szWQt3L9V=hTNIW$m-b%HR88&WV#f7C7J_s>6hC1sNgVTozkvO9nU1O> zQw{Z*psx~{hOg5*WOgBX;iGzjt<(l?N9RQ*5D+2lEu^7=had~`RV(w>h;#etK+BBf zyLg3o-zfa&_N(YmkRFA%m$hEWJMFGz(G(o0MCyaDpE`yN@1K|OcMIig#8GHqE+t8A zg+;zb?`t886DTLY`ij@?QQ_xr`B*3bN%j;j~EO4#fbDDaQ!ak?vOah0l@p~NN8v(7`1u)98k` zXd{=*6;{JiqR|f8?9|E*FAk=xa?F6!&x7$Ye)B>u_jWH?2^0QQi(w0c(14rkApO&L z^959fyZtdKvJ8O^!%oC}H^6nHtdQK-PJJd>vN|*FKv&?-73>&v(_BgYOev~v6L+HOjb;@^ zs>;fW;oLvK>#xT=T%CsK3M(-TWOj*8IA!vj+;mQn;*qMEOB+uq& z1BQsr-MO>|+p8y#cv?xQ?PXy?UoYiZ9f21qJe(K|?13eQdi@lM>RE&5{)DIZg$0jJ zwBoGW%hN;LLuO|&=@2@Ojv%-DMs0J;0S{P%LEYVWZ99V`|viOd%g}4d)e(bv~GdMbcmNc zMZo4A0=<(uoW16RlN@yvg!fOBehJFE;uunTMWo_d!i5wmWwfLsGB^KH?kP^BUuwcQ zLii^sbz`vk0tfP2t-Cb$-O&J-NINvgjCjnfO=FNB3+(ZWn%ZxgUKFX)-xe7|Q>*0p zK-~BUPzenuPRt<@)te=j0Zjo8u0!_hBYu~Hz}`1FAw`FHo=rHIrPm`h9Ay z9K-FsK3R=#fhl%e0E z@kAAD<|YJn>AZdfH%RUW<$cCCcq+Zq!)W1?AW~`z0B?s^chK%@k8VW$a0wFREL~+Nm8ku1arVzu!p`yNb z(EgSc2@;0HHgt47**zjP7tL`zZ9tN*>TmOr>S$-Si-f>|dh0Rp999?`=-$#mT3n*6 zI^Vr9h+OIlhC#tnnV;B@L)?Y?MM1*4#q>vxVb98>yaa=Tp}`bwHsOQxr9z?QOP>ax zrM+3VdjgIe_f*7vp4E@FTvjV{joKlCQg}#Ox2YDpf__k#W-Sc|aNjLe?ZOl(;#tE# z8YW!cbtu1NNe1k_+dQwE%CzT4u!>!hh+xgnY*#j5zOFSUHJm?z4A5C!Z=5s`3N7)_CL6`6cX+_SjRZuKcCqn~^){njse!mn8HF>vA z8?L2igtlbNaE4cNHP!Ql(&qY*Id`jr^1DX8ye?yH!IC(0?vw0%`L11WD@Mc`A(&Uw zwoknFf%TN}k3dMSL4WW#cwR@yUh}1wQFoS=y__Uz#0DxkFPVX|HfK`&0 zb0#?r%D}FHv6D6w`J3ZTfjS8Z?5H?k=F7DfW7ZnCta5s}XfRKf9O{>agEf$cyIm@= z4Hm7m=^Mu>KgM8^foWlt*Gg%wIa$xISks-by)*A$~r?a+f8tez+6M}(^pEP}y zaI14lI*y4^;Emvm>+1j5Ut3DH@+bhg18|Yea;RH;{Kjk1sP4pIa51al=Y;5F|;MtwB;pHMQ*Dg8Z#qF1i0sOq`ONW;bkhERSuP>He18NxgAl zBkXAPnh)YcWpF}wI!9v~?auBZ&&>Z7ma;^0X)Tc*1I-cQn=2f#ZwkyV{bxAgo)2qg>47O^i>>VGqd21INE6XAVs57xjiWe~2YG-@_q5HncC~d{i9@oOcm%oU+2DKPc; z3Td|^m$e4qg2wpy^&yo#1BlmfU)Y7c4tqFg671DLoF0{rOx9&wN8>+OVHHh7Kc{K5 zemb+?g&w|jm70)qk#~hY^yqy^ii{iXu)usmLcsMlpWhxqP?OAtrRPs4Fc}`4he>OM zRw;|fle~h{MJg@veizh^If5%zZx#0emg&-mDU<12KeK;1LywwZ>`|i~Q%mY0rw^;2 z?8R{*+RXw%X`Z+|HawdH&5!^ep+EJCxyj#1{yILzqs#SxvKYZ@F|txA8TYVn>x+}M zdG#pRtC-7H#`lM#O6Q37&Lb z3kwj4#5?6pH_4@EM_E41SVG{@yZ5^59s(iwdC}7}yIh-gqLuem6f_|;{Jw649b=X9 zvZ5PB^;gCc6jC)G@ZBzF`FhxU<1x3HH{2&cfHJB}Il)FxVu190YCETM-O9wvS>~a2 zkr}L?Id=#`(l6D~XH=kYog^zwMQmXK`C>u?@6^u*aO28jGiwY#Qg+GFW=XA0viVq0 zxxBdyxqLvD(}EN&FfsoB(yyX~9Yv;2@k}-ey-w2Q!g0{V0wGPYr1#ecOH;m&V20v#{Im<++ExQozzC;XsoAZQRcDZ-k z$sV<79GgM6w7jFO{4HKMaF}L8p&HymDmPS#RU$IlTcv&IYKqxSf!^>7mB+)$U%VDZ zDzS}wM+(05fXhi1=#%)vA;}45BlCup&yV?AlA@3j2!G2xu9+N*g}}2*F)FE)2kzdd zWPYwt)dGU%^Hg+;6{Aw^ACY#x4q}DE5_?7_t;6yXOf)CI-fdxQh5#!7-P?E%9}ss8 zm=9nmrtL6BsL$CByE?v8_mP}A5-e+(_yv+^L4V@=59%|_^p4@Gx_5t8u#bHpKHljt z(iS{9MN`F@In?C5WD$N7QxU-xEFE4>y#A`1Mc+)G{+DsbNCrht_Su)HM9}#PPVB6r z+F|k-cYl28hUt$^(EFCVpD51;z2*61985C`=gbU6Z;qT3u#5%K0mXlQC_1VDTFtDe zN{saFd+Pkv*T4~)N3HCvur;}9y>Petl;_gB6F=ra@$X^KF^e)aJnx9)dkb0dP#RaM zpXs=;m~h&GF&i`CAM_90RfFrhQd4i`Anr~eA zp!4p>B(tcz;H2CaHjsa5NC4dPf0n3U!CP zr3k(Das>pxK6j*r4TH5vyC2`y9)Sp1pLoYY{SMxVDe!Ex8yAWc_DKvdPZ)zWQQ5ZA zrICf?oMU1hOQp*eXlb>~a#auh}NtpXu5C;Qj~h>wYxX@4@f7=DL2{_xpLj->-LvhwFqZY(NE@sHYefI(l*(g39^=qaKMR8L zgWtw?oIn|$8SR$j(0V+4d3&403y!4D`MO^2%Cu^+)CxJLJyie!ptD z2Mt}E#7n;QlkZ;wou})j|8OnY$QsTl{bHy1B-@fyv286DGCWxlfZD zjtis1FRlr5^XFl?nFu$=u!65`UX?EE1JdXqbD&pz#d}qG&Vj04otBBZvn%SUgJ!Zq^Uvl2rDO_U<^tZ)I9QR8*8Zh7R zaEZ_jhS=LmBMc)@?PksnnJQtG9S(ht0oQ#pR z*h%;=E`!_Rp^n2!nKH9BxUf0>;?UXV9>4EocMl$gP%}immJ4~ks{z($C+J$8hgvI( z8azL|4Hw*RtCRM^y~L`tJ)rQXPwZvfo}9mK+IMCa8#ydGH`!8DwCUQoCAB?cydcl+ zO@Ea+>7jO)Y#}06*fI^38q`?up4CYYcp~r+#XvMxD~7+Il;3|m+`;Vg`e z3;7)S->Mtyr&vs8^$bK=7jNdUWI#`zo6TY1z~7&Jruz?qqv|TSBH<2AncMSi9ag~5 zzky1|Xm~O3W;tH1q|i^FCl5lxJLc+l$QB5#@q;(VD--iQu?QN^&{I!@6%?63A zYO$lse0G`VJDuKj>t#-uZ*Qk6`5l6j;kvb=Bh8ZdaVj`Kggr?SY&2?QR}y|SmQab6n>>#iKl?SctJKd%x7nz5&dYrZorL`uEyS21EnB(>vUQVbc^|= zWxbW43=Qo+iKXF_A)w+Uthafgn?MlThG_8lOf4%Qpu#0=_EZ2Y8kLgnIbjbdgpJyc zh9rbx=a%&g<_vShM?zVi-~QJUMbqUo1Ks-EVk~Apay}KZa$ttWqsC@jW?v;b8tGtd z*SJ2?MK?`IakfeDaAjn*D z{}BWyc`-DrLa(6iIs`KQF_{@D8bJY%aNt1ebsx|Gbc ziRUz#qA?h^8DO@juHE=P36AESFw;}#RU1UB$$%7txppM2f5cm^HzbzfNEP{q+>}*Q29aise|>iZGBN*GB{9kzsdbL zdG`c|1!rjZ)l5TzZi37QDD3Gy>38&8Y3F<(t&FRFn)M8BF=(Q3(|s5VF2MSg~1KF^hz< z#qG^2=9}(qNOoUGPd7RPUVQj+^fSQIGG(o_m+aLG=d+W~=p%A$Zb6-*v(VGr6ym+2?Cfl3B;ex68+or@S~e5P(1~+;kk>)sZW=#@sps%y zO+>;(`=7B@pibJB({Zdc>LX%*1(JRTW}0#U-`*kZEX>q^l@cf67indkZ&KcDZy=8l z+8yQZ0tQV+MwgYsXMA|gIRVEun3I`Q$=Z+f$9-&8C5Gq zTc+4O9&+1%==5jaH(4rTO6k7C_^A$Zk~NZI+5HSJ(s1!%hq9F+?SU-ol6Zn?`ii+Q zwZCtxnnO0n>2HN<4Lj3dQek(N_OPA1rDaQbExN7SXSK?x8JQRTsd7iURQH^96_lGz z`p5&Sao}m7kls(ovJeHCiX7MXt=BYx-9_l!5B*HBga2d-T?}P zUjPkQ{+iT8vKVEq(H%KH>!)D)bf86-7Ue#5zVh8C1)b+7)#L`^6PO2YoC$g@$5 diff --git a/src/main/resources/static/images/spring_boot_gray.png b/src/main/resources/static/images/spring_boot_gray.png deleted file mode 100644 index bedfa745359faa5381c2e795e5a5d687c9728cd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29212 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 diff --git a/src/main/resources/static/images/spring_boot_green.png b/src/main/resources/static/images/spring_boot_green.png deleted file mode 100644 index 375f324ff7e7d9abd2d85b8c341361475dab7464..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3776 zcmai1X*iVq+a@K-k|aB=lr0P@%aCnSvWASYhM6IuFwEGJeMywH?7IxrkSx$;BPK8|0CjTJ=jh}01-E-pcf z>!x;GT-@@&!_CJFAlK5$s=2s?k}OQG-435wnWY5F^otOK6TTjkIUwh%GFN2c!50~l z6(VM0#4FHsT88`ZU&kckOl1ea3-i360xwmB&qkP>U}ZyQJaWwY2va zb|e0LcAuMF0e2GgB6lZagb22>Ehri-N}9(vj($Aw$>o>`3<0ygdw;L*T;<`jGcm}! z|#DTF`>QwwjdTOQ{kd zYHzC~X@`-)Z)`9|ER!R%nR+F%5kwUV1WJ^Q8r;QXb zRtN%md(7@K9eIH#?yGd;)?OLCvM4%H!uNSEH$1!I|@e>@u9`Z zy4RQopo(#{L(Nv?j@XHmQq;e=2ulbn`tM;`=oz}J(XuI7ui5G#{^&j<>HntUSQ0}0 zo4*N6P^ZC%cH|x{jtg(H+`K)w{fX$S%$P^TVwjNaz^5mEKG-68}4%9!c` z`&&`eObkoE46ve8ric#NFZ)8k?T7e&(#^tLcjFtr)51VtpXruK+y9^*+lkxipYtQ(ad@@uVel3z|wT zj#(DI#w2gUUy(L<-Z&4#vB$l*W%@Ml)A1Le?xGQD%3Fg~Jz9&n2kXr>j2M=E!YDN~G$7G9`BfU%@Q@kW3LF0C@RMXIovlX|Hg-GFm+1*v~t zdbH-j$rHe|HZpF+{i?;!@?#D8@h)b%w)y?(oeYJZ-74eF3W+)j#&whJ>*d6- zj4eZLih6F6JH|qfRMIK}b#mDER=-%s$N{?$j#uI|{*@)jnWpWX@Mvrs{mo->NgeQc!N|qoR`|!K_`PQb@Nn~G{HL8Ci``b0KAUel;0uQ9 zilz3YTMY=jcy4;9ruO=zGFnxjZKx@Qf^lr7IVPkNpZ)A`#Y<5NhMEA6&}P~r-g}<< z(_pEop;%3M@xtSHxY@Enm`0$Fz9iw!%s%+MSO^b~lr;E6R?SeXV6*&+eQA+b#L%R| zMDxpE-{}-LEK<|re87>qhtQW^eB%dMc7cQP z(AibYqASUybZdB|r8adx7M!(H-n%5x!I9!IJ8vo*K$p(Vfxph~7-d>p(r5T2j?*+USX4i#3;CDF8*X@A2c z4NuEX^%o7DdtV!xlyr^mR0vB1jjH-bpf~hift_D|uikUgJ2{_kQL||tD)2sxI(?&N zZrwmorQ{YElB1mG=Fj|@zoT_oreXR^ON;Hs!;iue>>wECFNf&j%_!$75~Hl;Ij)-h zJH{3Bm(P{C%I{FPOn9~9GDF#IV|L+&+Do&M|-o-mTO1Jkw}X z3mZi5(e}(vv?z1NF*gd#Wc4*uc)t!bZm5izB`c)W?0%NNqrI}7Suf~!98Qgs{KS3bus*bQIV*c-YzSSS3tNe3)J#&^RF2dqam?FzBZ|ix4Mvo7_x&Pl}eVT-q9S+aQr&h8+^U_$!L1Q8C)5VS`%G zXYJx2kqaK|b>6`s3DS3tewX`KQz)R-KWX>JMrb7!C6DG=r@~~;7-vO#LoHalW9(gp*5k~dfH^&?x1MNcN=B5 zOL?G|M93T}^0mtKcRW~^EFHX6TojD*`C{})gWJMQUj8p^l>@SDnG@NE0J%dqH16y! z9WzPU4=ihOKP3!-ZH>maXB5o(SMva%G())lxv?RO4<*myFAJqdRT>zVY|6L(Q|xfb zo2Kw&l^Y;EhMTDB?JNXzVeMx+6cG2!+jQtws=#4je4_VZorW?lj#{ab zQTTVE><0WOhbyM$cFPi52(Uj6qdQtNJ>)(ibpHJ2k zX;v!*__F(nhxYjw!`H&#F8 zr60#Fq9I2S>+(0Y_kJj8CvFWl>UX-#WNx}!o`gnht%;BLtez3rV^DW1EKFYgb^~Xe zzvB8bV_vqe)c=X)dpQ#r+!)1i!JpE7)w=Ms_73jEU1Qd9s;yUwx~O06ZTrFxNQDyO zL8{1Xx$7SmG8rXl~xpU?UIJg%5FSTNpLjV8( diff --git a/src/main/resources/templates/admin/common/head.html b/src/main/resources/templates/admin/common/head.html deleted file mode 100644 index cf02a549..00000000 --- a/src/main/resources/templates/admin/common/head.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/templates/admin/common/top.html b/src/main/resources/templates/admin/common/top.html deleted file mode 100644 index 7284ad94..00000000 --- a/src/main/resources/templates/admin/common/top.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/main/resources/templates/admin/home.html b/src/main/resources/templates/admin/home.html deleted file mode 100644 index cb71b13c..00000000 --- a/src/main/resources/templates/admin/home.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/templates/admin/resource/detail.html b/src/main/resources/templates/admin/resource/detail.html deleted file mode 100644 index b9b60ad5..00000000 --- a/src/main/resources/templates/admin/resource/detail.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - -
-
- -
-
-
- -
- -
-
- -
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-
- - - 목록 - 삭제 -
-
-
-
-
- - \ No newline at end of file diff --git a/src/main/resources/templates/admin/resource/list.html b/src/main/resources/templates/admin/resource/list.html deleted file mode 100644 index 54e38de6..00000000 --- a/src/main/resources/templates/admin/resource/list.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - -
-
- -
- - - - - - - - - - - - - - - - - - -
리소스명리소스타입HttpMethod순서
- -
-
- - \ No newline at end of file diff --git a/src/main/resources/templates/admin/role/detail.html b/src/main/resources/templates/admin/role/detail.html deleted file mode 100644 index 01568ad7..00000000 --- a/src/main/resources/templates/admin/role/detail.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - -
-
- -
-
-
- -
- -
-
- -
- -
- -
-
-
-
- - 목록 -
-
-
-
-
- - \ No newline at end of file diff --git a/src/main/resources/templates/admin/role/list.html b/src/main/resources/templates/admin/role/list.html deleted file mode 100644 index 442b81a6..00000000 --- a/src/main/resources/templates/admin/role/list.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - -
-
- -
- - - - - - - - - - - - - - -
권한명권한설명
- -
-
- - \ No newline at end of file diff --git a/src/main/resources/templates/admin/user/detail.html b/src/main/resources/templates/admin/user/detail.html deleted file mode 100644 index dcb60158..00000000 --- a/src/main/resources/templates/admin/user/detail.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - -
-
- -
-
-
- -
- -
-
- -
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-
- - 목록 -
-
-
-
-
- - \ No newline at end of file diff --git a/src/main/resources/templates/admin/user/list.html b/src/main/resources/templates/admin/user/list.html deleted file mode 100644 index 558c8151..00000000 --- a/src/main/resources/templates/admin/user/list.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - -
-
- -
- - - - - - - - - - - - - - - - - - -
이름이메일나이권한
-
-
- - - \ No newline at end of file diff --git a/src/main/resources/templates/aop/liveaop.html b/src/main/resources/templates/aop/liveaop.html deleted file mode 100644 index 6944d580..00000000 --- a/src/main/resources/templates/aop/liveaop.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - -
- -
-

-
-

Welcome To Live AOP !!

-
-
-
- - \ No newline at end of file diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html index 1679845d..43646679 100644 --- a/src/main/resources/templates/home.html +++ b/src/main/resources/templates/home.html @@ -29,34 +29,6 @@ - - - - - - - - - -
- - \ No newline at end of file diff --git a/src/main/resources/templates/layout/footer.html b/src/main/resources/templates/layout/footer.html index ce6b8df5..f446b7fb 100644 --- a/src/main/resources/templates/layout/footer.html +++ b/src/main/resources/templates/layout/footer.html @@ -1,12 +1,10 @@ -
-

Copyright 2019 Core Spring Security. All Right Reserved

+

Copyright 2020 Core Spring Security. All Right Reserved

- \ No newline at end of file diff --git a/src/main/resources/templates/layout/header.html b/src/main/resources/templates/layout/header.html index 36d8f377..a3e33fc2 100644 --- a/src/main/resources/templates/layout/header.html +++ b/src/main/resources/templates/layout/header.html @@ -1,9 +1,9 @@ - + Home + diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html index 01eb2575..bd2a3e51 100644 --- a/src/main/resources/templates/login.html +++ b/src/main/resources/templates/login.html @@ -11,7 +11,6 @@

아이디와 비밀번호를 입력해주세요

diff --git a/src/main/resources/templates/user/login/denied.html b/src/main/resources/templates/user/login/denied.html deleted file mode 100644 index 89abc6a6..00000000 --- a/src/main/resources/templates/user/login/denied.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - -
- -
-

-
-

Access Denied

-
-
-
- - \ No newline at end of file diff --git a/src/main/resources/templates/user/login/invalidSession.html b/src/main/resources/templates/user/login/invalidSession.html deleted file mode 100644 index 47b3392e..00000000 --- a/src/main/resources/templates/user/login/invalidSession.html +++ /dev/null @@ -1,13 +0,0 @@ - - - -home - - -
-

expired

- login -
- - - \ No newline at end of file diff --git a/src/main/resources/templates/user/login/list.html b/src/main/resources/templates/user/login/list.html deleted file mode 100644 index bd365853..00000000 --- a/src/main/resources/templates/user/login/list.html +++ /dev/null @@ -1,57 +0,0 @@ - - - Member Register - - - - - - - - - -
-

회원 목록

-
-
- -
-
-
- - - - - - - - - - - - - - - - - - - - - -
이름이메일나이권한
- -
-
- - - \ No newline at end of file diff --git a/src/main/resources/templates/user/login/logout.html b/src/main/resources/templates/user/login/logout.html deleted file mode 100644 index ae46c6a5..00000000 --- a/src/main/resources/templates/user/login/logout.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - -home - - - -
-

error

- -

success

-


- login -
- - - \ No newline at end of file diff --git a/src/main/resources/templates/user/login/register.html b/src/main/resources/templates/user/login/register.html index e81d7e4d..691cdd31 100644 --- a/src/main/resources/templates/user/login/register.html +++ b/src/main/resources/templates/user/login/register.html @@ -2,20 +2,7 @@
- -
diff --git a/src/main/resources/templates/user/login/successRegister.html b/src/main/resources/templates/user/login/successRegister.html index e5062a1d..aacc6966 100644 --- a/src/main/resources/templates/user/login/successRegister.html +++ b/src/main/resources/templates/user/login/successRegister.html @@ -1,5 +1,4 @@ - From e9cc7af85c6c4edb09c84129172570ead4ce0106 Mon Sep 17 00:00:00 2001 From: onjsdnjs Date: Wed, 25 Dec 2019 20:59:09 +0900 Subject: [PATCH 03/28] lecture --- .../corespringsecurity/security/configs/SecurityConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/security/corespringsecurity/security/configs/SecurityConfig.java b/src/main/java/io/security/corespringsecurity/security/configs/SecurityConfig.java index 164bfb24..e9dc8f7d 100644 --- a/src/main/java/io/security/corespringsecurity/security/configs/SecurityConfig.java +++ b/src/main/java/io/security/corespringsecurity/security/configs/SecurityConfig.java @@ -24,6 +24,7 @@ protected void configure(final HttpSecurity http) throws Exception { CharacterEncodingFilter filter = new CharacterEncodingFilter(); http .authorizeRequests() + .antMatchers("/","/users").permitAll() .anyRequest().authenticated() .and() From 5a4ccefced5f4d40b24e3543cf2b3a00bd4037ed Mon Sep 17 00:00:00 2001 From: onjsdnjs Date: Wed, 25 Dec 2019 21:00:21 +0900 Subject: [PATCH 04/28] lecture --- .../corespringsecurity/security/configs/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/security/corespringsecurity/security/configs/SecurityConfig.java b/src/main/java/io/security/corespringsecurity/security/configs/SecurityConfig.java index e9dc8f7d..8142c689 100644 --- a/src/main/java/io/security/corespringsecurity/security/configs/SecurityConfig.java +++ b/src/main/java/io/security/corespringsecurity/security/configs/SecurityConfig.java @@ -24,7 +24,7 @@ protected void configure(final HttpSecurity http) throws Exception { CharacterEncodingFilter filter = new CharacterEncodingFilter(); http .authorizeRequests() - .antMatchers("/","/users").permitAll() + .antMatchers("/").permitAll() .anyRequest().authenticated() .and() From 8e72690ef262f6a4bfdc81c51c2f1f2bc9c212c7 Mon Sep 17 00:00:00 2001 From: onjsdnjs Date: Thu, 26 Dec 2019 23:42:40 +0900 Subject: [PATCH 05/28] lecture --- src/main/resources/templates/login.html | 31 ------------- .../templates/user/login/register.html | 44 ------------------- .../templates/user/login/successRegister.html | 13 ------ 3 files changed, 88 deletions(-) delete mode 100644 src/main/resources/templates/login.html delete mode 100644 src/main/resources/templates/user/login/register.html delete mode 100644 src/main/resources/templates/user/login/successRegister.html diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html deleted file mode 100644 index bd2a3e51..00000000 --- a/src/main/resources/templates/login.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - -
-
- -
- - \ No newline at end of file diff --git a/src/main/resources/templates/user/login/register.html b/src/main/resources/templates/user/login/register.html deleted file mode 100644 index 691cdd31..00000000 --- a/src/main/resources/templates/user/login/register.html +++ /dev/null @@ -1,44 +0,0 @@ - - - -
- - -
-
-
-
- -
- -
-
- -
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-
- -
-
-
-
-
- - \ No newline at end of file diff --git a/src/main/resources/templates/user/login/successRegister.html b/src/main/resources/templates/user/login/successRegister.html deleted file mode 100644 index aacc6966..00000000 --- a/src/main/resources/templates/user/login/successRegister.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - -home - - -
-

success

- login -
- - \ No newline at end of file From 674e1be49b1895170ed075cea97b6e278d05de27 Mon Sep 17 00:00:00 2001 From: onjsdnjs Date: Fri, 27 Dec 2019 14:28:54 +0900 Subject: [PATCH 06/28] lecture --- src/main/resources/templates/layout/top.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/resources/templates/layout/top.html b/src/main/resources/templates/layout/top.html index 250738e6..669e3e7a 100644 --- a/src/main/resources/templates/layout/top.html +++ b/src/main/resources/templates/layout/top.html @@ -6,10 +6,6 @@ From 53ba1684ba6ec829cd230bc9a3514adfd05def6b Mon Sep 17 00:00:00 2001 From: onjsdnjs Date: Fri, 27 Dec 2019 15:18:14 +0900 Subject: [PATCH 07/28] lecture' --- src/main/resources/templates/home.html | 18 +-------------- .../resources/templates/layout/footer.html | 6 ++--- src/main/resources/templates/layout/left.html | 23 +++++++++++++++++++ 3 files changed, 27 insertions(+), 20 deletions(-) create mode 100644 src/main/resources/templates/layout/left.html diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html index 43646679..9ed45afb 100644 --- a/src/main/resources/templates/home.html +++ b/src/main/resources/templates/home.html @@ -8,23 +8,7 @@ diff --git a/src/main/resources/templates/layout/footer.html b/src/main/resources/templates/layout/footer.html index f446b7fb..27bda64a 100644 --- a/src/main/resources/templates/layout/footer.html +++ b/src/main/resources/templates/layout/footer.html @@ -1,9 +1,9 @@
-