diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/SecurityConfig.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/SecurityConfig.java index 74ec9a9..fc5c89f 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/SecurityConfig.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/SecurityConfig.java @@ -1,8 +1,9 @@ package com.bpgroup.poc.admin.security; -import com.bpgroup.poc.admin.security.authentication.LoginService; import com.bpgroup.poc.admin.security.authentication.CustomAuthenticationFilter; import com.bpgroup.poc.admin.security.authentication.CustomAuthenticationProvider; +import com.bpgroup.poc.admin.security.authentication.service.LoginService; +import com.bpgroup.poc.admin.security.jwt.JwtTokenValidatorFilter; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -12,8 +13,10 @@ import org.springframework.security.authentication.ProviderManager; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @@ -32,6 +35,9 @@ public class SecurityConfig { @Bean SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { + + http.sessionManagement(t -> t.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + // 보안 기본 설정 http.headers(c -> c .frameOptions(fo -> fo.sameOrigin()) // X-Frame-Options: Same Origin @@ -50,6 +56,8 @@ public class SecurityConfig { http.formLogin(AbstractHttpConfigurer::disable); // Form 로그인이 아닌 Json 로그인으로 분리 http.addFilterBefore(authenticationGenerateFilter(), UsernamePasswordAuthenticationFilter.class); // 로그인 관련 Filter 설정 + http.addFilterAfter(new JwtTokenValidatorFilter(), BasicAuthenticationFilter.class); + http.logout(c -> c .logoutRequestMatcher(new AntPathRequestMatcher(LOGOUT_PATH, "GET")) .logoutSuccessUrl(LOGIN_PATH) diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/SecurityFilterConstants.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/SecurityFilterConstants.java new file mode 100644 index 0000000..2486400 --- /dev/null +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/SecurityFilterConstants.java @@ -0,0 +1,5 @@ +package com.bpgroup.poc.admin.security; + +public class SecurityFilterConstants { + public static final String[] EXCLUDE_FILTER_STARTS_WITH_URI = {"/login", "/logout", "/error", "/css", "/js", "/images", "/favicon.ico", "/common/modal"}; +} diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/AuthenticationDetail.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/AuthenticationDetail.java index 674b30d..b79625f 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/AuthenticationDetail.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/AuthenticationDetail.java @@ -1,86 +1,41 @@ package com.bpgroup.poc.admin.security.authentication; -import lombok.EqualsAndHashCode; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Builder; import lombok.Getter; -import lombok.ToString; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; +import lombok.NoArgsConstructor; + @Getter +@NoArgsConstructor public class AuthenticationDetail { - - private Long id; private String loginId; private String name; private String email; - private MultiValueMap menus = new LinkedMultiValueMap<>(); - - public static AuthenticationDetail from(LoginResult result) { + @Builder + public static AuthenticationDetail of(String loginId, String name, String email) { AuthenticationDetail authenticationDetail = new AuthenticationDetail(); - authenticationDetail.id = result.getId(); - authenticationDetail.loginId = result.getLoginId(); - authenticationDetail.name = result.getName(); - authenticationDetail.email = result.getEmail(); - - result.getMenus().forEach(menu -> authenticationDetail.menus.add( - MenuOneDepth.of( - menu.getMenuGroupUri(), - menu.getMenuGroupName(), - menu.getMenuGroupSortOrder() - ), - MenuTwoDepth.of( - menu.getMenuUri(), - menu.getMenuName(), - menu.getMenuSortOrder() - ) - )); - + authenticationDetail.loginId = loginId; + authenticationDetail.name = name; + authenticationDetail.email = email; return authenticationDetail; } - /** - * EqualsAndHashCode annotattion이 없을 경우 MultiValueMap을 사용할 때 중복된 key를 찾지 못함 - */ - @Getter - @ToString - @EqualsAndHashCode - private static class MenuOneDepth { - private String uri; - private String name; - private Integer sortOrder; - - public static MenuOneDepth of( - String uri, - String name, - Integer sortOrder - ) { - MenuOneDepth menu = new MenuOneDepth(); - menu.uri = uri; - menu.name = name; - menu.sortOrder = sortOrder; - return menu; + public String toJsonString() { + try { + return new ObjectMapper().writeValueAsString(this); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); } - } - @Getter - @ToString - private static class MenuTwoDepth { - private String uri; - private String name; - private Integer sortOrder; - - public static MenuTwoDepth of( - String uri, - String name, - Integer sortOrder - ) { - MenuTwoDepth menu = new MenuTwoDepth(); - menu.uri = uri; - menu.name = name; - menu.sortOrder = sortOrder; - return menu; + public static AuthenticationDetail fromJsonString(String jsonString) { + try { + return new ObjectMapper().readValue(jsonString, AuthenticationDetail.class); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); } } diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/exception/AuthenticationFailException.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/AuthenticationFailException.java similarity index 85% rename from poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/exception/AuthenticationFailException.java rename to poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/AuthenticationFailException.java index 55bb1aa..d6f5e36 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/exception/AuthenticationFailException.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/AuthenticationFailException.java @@ -1,4 +1,4 @@ -package com.bpgroup.poc.admin.security.authentication.exception; +package com.bpgroup.poc.admin.security.authentication; import lombok.Getter; import org.springframework.security.core.AuthenticationException; diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/exception/AuthenticationFailReason.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/AuthenticationFailReason.java similarity index 71% rename from poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/exception/AuthenticationFailReason.java rename to poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/AuthenticationFailReason.java index eece553..06adc93 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/exception/AuthenticationFailReason.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/AuthenticationFailReason.java @@ -1,5 +1,8 @@ -package com.bpgroup.poc.admin.security.authentication.exception; +package com.bpgroup.poc.admin.security.authentication; +import com.bpgroup.poc.admin.security.authentication.service.exception.AdministratorNotFoundException; +import com.bpgroup.poc.admin.security.authentication.service.exception.DoNotHaveAnyMenuException; +import com.bpgroup.poc.admin.security.authentication.service.exception.InvalidPasswordException; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationFailureHandler.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationFailureHandler.java index 1a72347..43e5f04 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationFailureHandler.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationFailureHandler.java @@ -1,6 +1,6 @@ package com.bpgroup.poc.admin.security.authentication; -import com.bpgroup.poc.admin.security.authentication.exception.AuthenticationFailException; +import com.bpgroup.poc.admin.security.authentication.service.LoginResponse; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationFilter.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationFilter.java index b300331..9a9d5b6 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationFilter.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationFilter.java @@ -1,5 +1,6 @@ package com.bpgroup.poc.admin.security.authentication; +import com.bpgroup.poc.admin.security.authentication.service.LoginRequest; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationProvider.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationProvider.java index 3be661e..6f8eec6 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationProvider.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationProvider.java @@ -1,7 +1,7 @@ package com.bpgroup.poc.admin.security.authentication; -import com.bpgroup.poc.admin.security.authentication.exception.AuthenticationFailException; -import com.bpgroup.poc.admin.security.authentication.exception.AuthenticationFailReason; +import com.bpgroup.poc.admin.security.authentication.service.LoginResult; +import com.bpgroup.poc.admin.security.authentication.service.LoginService; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -30,13 +30,22 @@ public class CustomAuthenticationProvider implements AuthenticationProvider { } private UsernamePasswordAuthenticationToken buildAuthenticationToken(LoginResult result) { - UsernamePasswordAuthenticationToken authenticationToken = UsernamePasswordAuthenticationToken.authenticated( + UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated( result.getLoginId(), null, null ); - authenticationToken.setDetails(AuthenticationDetail.from(result)); - return authenticationToken; + + token.setDetails( + AuthenticationDetail.builder() + .loginId(result.getLoginId()) + .name(result.getName()) + .email(result.getEmail()) + .build() + .toJsonString() + ); + + return token; } @Override diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationSuccessHandler.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationSuccessHandler.java index 2be1a79..e52bac0 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationSuccessHandler.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationSuccessHandler.java @@ -1,35 +1,52 @@ package com.bpgroup.poc.admin.security.authentication; +import com.bpgroup.poc.admin.security.authentication.service.LoginResponse; +import com.bpgroup.poc.admin.security.jwt.JwtConstants; import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.MediaType; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import javax.crypto.SecretKey; import java.io.IOException; import java.nio.charset.StandardCharsets; public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException { - AuthenticationSuccessHandler.super.onAuthenticationSuccess(request, response, chain, authentication); - } - - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { response.setStatus(HttpServletResponse.SC_OK); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + // JWT 토큰을 쿠키에 추가 + String jwtToken = createJwtToken(authentication); + Cookie jwtCookie = new Cookie(JwtConstants.JWT_TOKEN_NAME, jwtToken); + jwtCookie.setHttpOnly(true); + jwtCookie.setPath("/"); + response.addCookie(jwtCookie); + String jsonResponse = new ObjectMapper().writeValueAsString( - LoginResponse.success( - "token" - ) + LoginResponse.success() ); response.getWriter().write(jsonResponse); } + + private static String createJwtToken(Authentication authentication) { + SecretKey key = Keys.hmacShaKeyFor(JwtConstants.JWT_KEY.getBytes(StandardCharsets.UTF_8)); + return Jwts.builder() + .issuer("BP Admin") + .subject("Jwt Token") + .claim("username", authentication.getName()) + .claim("details", authentication.getDetails()) + .issuedAt(new java.util.Date()) + .expiration(new java.util.Date(System.currentTimeMillis() + JwtConstants.EXPIRATION_TIME)) + .signWith(key) + .compact(); + } } diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/LoginResult.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/LoginResult.java deleted file mode 100644 index d375ac9..0000000 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/LoginResult.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.bpgroup.poc.admin.security.authentication; - -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.ToString; - -import java.util.HashSet; -import java.util.Set; - -@Getter -public class LoginResult { - private Long id; - private String loginId; - private String name; - private String email; - - private Set menus = new HashSet<>(); - - public static LoginResult of(Long id, String loginId, String name, String email, Set menus) { - LoginResult loginResult = new LoginResult(); - loginResult.id = id; - loginResult.loginId = loginId; - loginResult.name = name; - loginResult.email = email; - loginResult.menus = menus; - return loginResult; - } - - @Getter - @ToString - @EqualsAndHashCode - public static class MenuInfo { - private String menuGroupUri; - private String menuGroupName; - private Integer menuGroupSortOrder; - private String menuUri; - private String menuName; - private Integer menuSortOrder; - - public static MenuInfo of( - String menuGroupUri, - String menuGroupName, - Integer menuGroupSortOrder, - String menuUri, - String menuName, - Integer menuSortOrder - ) { - MenuInfo menuInfo = new MenuInfo(); - menuInfo.menuGroupUri = menuGroupUri; - menuInfo.menuGroupName = menuGroupName; - menuInfo.menuGroupSortOrder = menuGroupSortOrder; - menuInfo.menuUri = menuUri; - menuInfo.menuName = menuName; - menuInfo.menuSortOrder = menuSortOrder; - return menuInfo; - } - } - -} diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/LoginService.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/LoginService.java deleted file mode 100644 index 5fc8db3..0000000 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/LoginService.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.bpgroup.poc.admin.security.authentication; - -import com.bpgroup.poc.admin.security.authentication.exception.AdministratorNotFoundException; -import com.bpgroup.poc.admin.security.authentication.exception.DoNotHaveAnyMenuException; -import com.bpgroup.poc.admin.security.authentication.exception.InvalidPasswordException; -import com.bpgroup.poc.admin.domain.admin.entity.Administrator; -import com.bpgroup.poc.admin.domain.admin.entity.AdministratorRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.Comparator; -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -public class LoginService { - - private final AdministratorRepository administratorRepository; - private final PasswordEncoder passwordEncoder; - - @Transactional - public LoginResult login(String username, String password) throws AdministratorNotFoundException, InvalidPasswordException, DoNotHaveAnyMenuException { - Optional administrator = administratorRepository.findByLoginId(username); - if (administrator.isEmpty()) { - throw new AdministratorNotFoundException(username); - } - - if (!passwordEncoder.matches(password, administrator.get().getPassword())) { - throw new InvalidPasswordException(username); - } - - return LoginResult.of( - administrator.get().getId(), - administrator.get().getLoginId(), - administrator.get().getName(), - administrator.get().getLoginId(), - getMenus(administrator.get()) - ); - } - - private LinkedHashSet getMenus(Administrator administrator) throws DoNotHaveAnyMenuException { - try { - return administrator.getAdministratorRole().getRole().getRoleMenus().stream() - .map(roleMenu -> LoginResult.MenuInfo.of( - roleMenu.getMenu().getMenuGroup().getUri(), - roleMenu.getMenu().getMenuGroup().getName(), - roleMenu.getMenu().getMenuGroup().getSortOrder(), - roleMenu.getMenu().getUri(), - roleMenu.getMenu().getName(), - roleMenu.getMenu().getSortOrder() - )) - .sorted( - Comparator - .comparingInt(LoginResult.MenuInfo::getMenuGroupSortOrder) - .thenComparingInt(LoginResult.MenuInfo::getMenuSortOrder) - ) - .collect(Collectors.toCollection(LinkedHashSet::new)); - } catch (NullPointerException e) { - throw new DoNotHaveAnyMenuException(); - } - } - - -} diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/LoginRequest.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/service/LoginRequest.java similarity index 70% rename from poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/LoginRequest.java rename to poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/service/LoginRequest.java index 509f245..3811194 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/LoginRequest.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/service/LoginRequest.java @@ -1,4 +1,4 @@ -package com.bpgroup.poc.admin.security.authentication; +package com.bpgroup.poc.admin.security.authentication.service; import lombok.Data; diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/LoginResponse.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/service/LoginResponse.java similarity index 81% rename from poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/LoginResponse.java rename to poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/service/LoginResponse.java index 87c1129..ecb7325 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/LoginResponse.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/service/LoginResponse.java @@ -1,4 +1,4 @@ -package com.bpgroup.poc.admin.security.authentication; +package com.bpgroup.poc.admin.security.authentication.service; import lombok.Getter; import lombok.ToString; @@ -11,11 +11,10 @@ public class LoginResponse { private String resultMessage; private String token; - public static LoginResponse success(String token) { + public static LoginResponse success() { LoginResponse response = new LoginResponse(); response.resultCode = "0000"; response.resultMessage = "Success"; - response.token = token; return response; } diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/service/LoginResult.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/service/LoginResult.java new file mode 100644 index 0000000..4776041 --- /dev/null +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/service/LoginResult.java @@ -0,0 +1,20 @@ +package com.bpgroup.poc.admin.security.authentication.service; + +import lombok.Getter; + +@Getter +public class LoginResult { + private Long id; + private String loginId; + private String name; + private String email; + + public static LoginResult of(Long id, String loginId, String name, String email) { + LoginResult loginResult = new LoginResult(); + loginResult.id = id; + loginResult.loginId = loginId; + loginResult.name = name; + loginResult.email = email; + return loginResult; + } +} diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/service/LoginService.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/service/LoginService.java new file mode 100644 index 0000000..4769cd7 --- /dev/null +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/service/LoginService.java @@ -0,0 +1,41 @@ +package com.bpgroup.poc.admin.security.authentication.service; + +import com.bpgroup.poc.admin.domain.admin.entity.Administrator; +import com.bpgroup.poc.admin.domain.admin.entity.AdministratorRepository; +import com.bpgroup.poc.admin.security.authentication.service.exception.AdministratorNotFoundException; +import com.bpgroup.poc.admin.security.authentication.service.exception.DoNotHaveAnyMenuException; +import com.bpgroup.poc.admin.security.authentication.service.exception.InvalidPasswordException; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class LoginService { + + private final AdministratorRepository administratorRepository; + private final PasswordEncoder passwordEncoder; + + @Transactional + public LoginResult login(String username, String password) throws AdministratorNotFoundException, InvalidPasswordException, DoNotHaveAnyMenuException { + Optional administrator = administratorRepository.findByLoginId(username); + if (administrator.isEmpty()) { + throw new AdministratorNotFoundException(username); + } + + if (!passwordEncoder.matches(password, administrator.get().getPassword())) { + throw new InvalidPasswordException(username); + } + + return LoginResult.of( + administrator.get().getId(), + administrator.get().getLoginId(), + administrator.get().getName(), + administrator.get().getLoginId() + ); + } + +} diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/exception/AdministratorNotFoundException.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/service/exception/AdministratorNotFoundException.java similarity index 68% rename from poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/exception/AdministratorNotFoundException.java rename to poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/service/exception/AdministratorNotFoundException.java index 3835a87..df0afa8 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/exception/AdministratorNotFoundException.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/service/exception/AdministratorNotFoundException.java @@ -1,4 +1,4 @@ -package com.bpgroup.poc.admin.security.authentication.exception; +package com.bpgroup.poc.admin.security.authentication.service.exception; public class AdministratorNotFoundException extends Exception { public AdministratorNotFoundException(String message) { diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/exception/DoNotHaveAnyMenuException.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/service/exception/DoNotHaveAnyMenuException.java similarity index 63% rename from poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/exception/DoNotHaveAnyMenuException.java rename to poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/service/exception/DoNotHaveAnyMenuException.java index 5baf9fe..ca1e8a5 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/exception/DoNotHaveAnyMenuException.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/service/exception/DoNotHaveAnyMenuException.java @@ -1,4 +1,4 @@ -package com.bpgroup.poc.admin.security.authentication.exception; +package com.bpgroup.poc.admin.security.authentication.service.exception; public class DoNotHaveAnyMenuException extends Exception { public DoNotHaveAnyMenuException() { diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/exception/InvalidPasswordException.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/service/exception/InvalidPasswordException.java similarity index 66% rename from poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/exception/InvalidPasswordException.java rename to poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/service/exception/InvalidPasswordException.java index c00c208..ceb917a 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/exception/InvalidPasswordException.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/service/exception/InvalidPasswordException.java @@ -1,4 +1,4 @@ -package com.bpgroup.poc.admin.security.authentication.exception; +package com.bpgroup.poc.admin.security.authentication.service.exception; public class InvalidPasswordException extends Exception { public InvalidPasswordException(String message) { diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtConstants.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtConstants.java new file mode 100644 index 0000000..0da505a --- /dev/null +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtConstants.java @@ -0,0 +1,10 @@ +package com.bpgroup.poc.admin.security.jwt; + +/** + * TODO: 사용 시 별도 property 파일로 관리 필요 + */ +public class JwtConstants { + public static final String JWT_KEY = "8530b13adb4e420d9694b27570635b47"; + public static final String JWT_TOKEN_NAME = "JWT-TOKEN"; + public static final long EXPIRATION_TIME = 60 * 10 * 1000; +} diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtTokenValidatorFilter.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtTokenValidatorFilter.java new file mode 100644 index 0000000..3a8bcc6 --- /dev/null +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtTokenValidatorFilter.java @@ -0,0 +1,71 @@ +package com.bpgroup.poc.admin.security.jwt; + +import com.bpgroup.poc.admin.security.SecurityFilterConstants; +import com.bpgroup.poc.admin.security.authentication.AuthenticationDetail; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.crypto.SecretKey; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +@Slf4j +public class JwtTokenValidatorFilter extends OncePerRequestFilter { + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String jwt = getJwtFromCookie(request); + if (null != jwt) { + try { + SecretKey key = Keys.hmacShaKeyFor( + JwtConstants.JWT_KEY.getBytes(StandardCharsets.UTF_8)); + + Claims claims = Jwts.parser() + .verifyWith(key) + .build() + .parseSignedClaims(jwt) + .getPayload(); + String username = claims.get("username", String.class); + String details = claims.get("details", String.class); + UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, null, null); + auth.setDetails(AuthenticationDetail.fromJsonString(details)); + SecurityContextHolder.getContext().setAuthentication(auth); + } catch (Exception e) { + throw new BadCredentialsException("Invalid Jwt Token"); + } + } + + filterChain.doFilter(request, response); + } + + private static String getJwtFromCookie(HttpServletRequest request) { + Cookie[] cookies = request.getCookies(); + if (null != cookies) { + for (Cookie cookie : cookies) { + if (JwtConstants.JWT_TOKEN_NAME.equals(cookie.getName())) { + return cookie.getValue(); + } + } + } + + return null; + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + List paths = List.of(SecurityFilterConstants.EXCLUDE_FILTER_STARTS_WITH_URI); + return paths.stream().anyMatch(request.getRequestURI()::startsWith); + } + +} diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/web/advice/menu/MenuInfo.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/web/advice/menu/MenuInfo.java new file mode 100644 index 0000000..61a7eea --- /dev/null +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/web/advice/menu/MenuInfo.java @@ -0,0 +1,41 @@ +package com.bpgroup.poc.admin.web.advice.menu; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +import java.util.ArrayList; +import java.util.List; + +@Getter +public class MenuInfo { + + private String uri; + private String name; + private Integer sortOrder; + + List menuChildren = new ArrayList<>(); + + @Builder + public static MenuInfo of(String uri, String name, Integer sortOrder) { + MenuInfo menuInfo = new MenuInfo(); + menuInfo.uri = uri; + menuInfo.name = name; + menuInfo.sortOrder = sortOrder; + return menuInfo; + } + + public void addMenuChild(MenuChild menuChild) { + menuChildren.add(menuChild); + } + + @Getter + @ToString + @Builder + public static class MenuChild { + private String uri; + private String name; + private Integer sortOrder; + } + +} diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/web/advice/menu/MenuInfoControllerAdvice.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/web/advice/menu/MenuInfoControllerAdvice.java new file mode 100644 index 0000000..4dbe4bd --- /dev/null +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/web/advice/menu/MenuInfoControllerAdvice.java @@ -0,0 +1,20 @@ +package com.bpgroup.poc.admin.web.advice.menu; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ModelAttribute; + +import java.util.List; + +@ControllerAdvice(basePackages = "com.bpgroup.poc.admin.web.main") +@RequiredArgsConstructor +public class MenuInfoControllerAdvice { + + private final MenuQueryRepository menuQueryRepository; + + @ModelAttribute("menuInfos") + public List menuInfo() { + return menuQueryRepository.findAllByLoginId(); + } + +} diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/web/advice/menu/MenuQueryRepository.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/web/advice/menu/MenuQueryRepository.java new file mode 100644 index 0000000..bc50a50 --- /dev/null +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/web/advice/menu/MenuQueryRepository.java @@ -0,0 +1,73 @@ +package com.bpgroup.poc.admin.web.advice.menu; + +import com.querydsl.core.Tuple; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.*; + +import static com.bpgroup.poc.admin.domain.admin.entity.QAdministrator.administrator; +import static com.bpgroup.poc.admin.domain.admin.entity.QAdministratorRole.administratorRole; +import static com.bpgroup.poc.admin.domain.admin.entity.QMenu.menu; +import static com.bpgroup.poc.admin.domain.admin.entity.QMenuGroup.menuGroup; +import static com.bpgroup.poc.admin.domain.admin.entity.QRole.role; +import static com.bpgroup.poc.admin.domain.admin.entity.QRoleMenu.roleMenu; + +@Repository +@RequiredArgsConstructor +public class MenuQueryRepository { + + private final JPAQueryFactory queryFactory; + + public List findAllByLoginId() { + List results = queryFactory.select( + menuGroup.uri, + menuGroup.name, + menuGroup.sortOrder, + roleMenu.menu.uri, + roleMenu.menu.name, + roleMenu.menu.sortOrder + ) + .from(administrator) + .innerJoin(administrator.administratorRole, administratorRole) + .innerJoin(administratorRole.role, role) + .innerJoin(role.roleMenus, roleMenu) + .innerJoin(roleMenu.menu, menu) + .innerJoin(menu.menuGroup, menuGroup) + .where(administrator.loginId.eq("admin")) + .orderBy(menuGroup.sortOrder.asc(), roleMenu.menu.sortOrder.asc()) + .fetch(); + + LinkedHashMap menuInfoMap = makeMenuForm(results); + return new ArrayList<>(menuInfoMap.values()); + } + + private static LinkedHashMap makeMenuForm(List results) { + LinkedHashMap menuInfoMap = new LinkedHashMap<>(); + + for (Tuple tuple : results) { + String menuGroupUri = tuple.get(menuGroup.uri); + MenuInfo menuInfo = menuInfoMap.get(menuGroupUri); + + if (menuInfo == null) { + menuInfo = MenuInfo.builder() + .uri(tuple.get(menuGroup.uri)) + .name(tuple.get(menuGroup.name)) + .sortOrder(tuple.get(menuGroup.sortOrder)) + .build(); + menuInfoMap.put(menuGroupUri, menuInfo); + } + + MenuInfo.MenuChild menuChild = MenuInfo.MenuChild.builder() + .uri(tuple.get(roleMenu.menu.uri)) + .name(tuple.get(roleMenu.menu.name)) + .sortOrder(tuple.get(roleMenu.menu.sortOrder)) + .build(); + + menuInfo.addMenuChild(menuChild); + } + + return menuInfoMap; + } +} diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/web/advice/PathInfoControllerAdvice.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/web/advice/path/PathInfoControllerAdvice.java similarity index 95% rename from poc/admin/src/main/java/com/bpgroup/poc/admin/web/advice/PathInfoControllerAdvice.java rename to poc/admin/src/main/java/com/bpgroup/poc/admin/web/advice/path/PathInfoControllerAdvice.java index 044d070..e03de50 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/web/advice/PathInfoControllerAdvice.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/web/advice/path/PathInfoControllerAdvice.java @@ -1,4 +1,4 @@ -package com.bpgroup.poc.admin.web.advice; +package com.bpgroup.poc.admin.web.advice.path; import jakarta.servlet.http.HttpServletRequest; import lombok.Getter; diff --git a/poc/admin/src/main/resources/templates/layout/lnb.html b/poc/admin/src/main/resources/templates/layout/lnb.html index 05df06e..e95b771 100644 --- a/poc/admin/src/main/resources/templates/layout/lnb.html +++ b/poc/admin/src/main/resources/templates/layout/lnb.html @@ -7,13 +7,13 @@

메뉴