admin: JWT 기반 인증 방식으로 변경

This commit is contained in:
geonhos 2024-05-14 13:47:57 +09:00
parent b81ab51a11
commit b3e255bda6
25 changed files with 371 additions and 225 deletions

View File

@ -1,8 +1,9 @@
package com.bpgroup.poc.admin.security; 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.CustomAuthenticationFilter;
import com.bpgroup.poc.admin.security.authentication.CustomAuthenticationProvider; 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 lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; 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.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; 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.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 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.header.writers.XXssProtectionHeaderWriter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@ -32,6 +35,9 @@ public class SecurityConfig {
@Bean @Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.sessionManagement(t -> t.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
// 보안 기본 설정 // 보안 기본 설정
http.headers(c -> c http.headers(c -> c
.frameOptions(fo -> fo.sameOrigin()) // X-Frame-Options: Same Origin .frameOptions(fo -> fo.sameOrigin()) // X-Frame-Options: Same Origin
@ -50,6 +56,8 @@ public class SecurityConfig {
http.formLogin(AbstractHttpConfigurer::disable); // Form 로그인이 아닌 Json 로그인으로 분리 http.formLogin(AbstractHttpConfigurer::disable); // Form 로그인이 아닌 Json 로그인으로 분리
http.addFilterBefore(authenticationGenerateFilter(), UsernamePasswordAuthenticationFilter.class); // 로그인 관련 Filter 설정 http.addFilterBefore(authenticationGenerateFilter(), UsernamePasswordAuthenticationFilter.class); // 로그인 관련 Filter 설정
http.addFilterAfter(new JwtTokenValidatorFilter(), BasicAuthenticationFilter.class);
http.logout(c -> c http.logout(c -> c
.logoutRequestMatcher(new AntPathRequestMatcher(LOGOUT_PATH, "GET")) .logoutRequestMatcher(new AntPathRequestMatcher(LOGOUT_PATH, "GET"))
.logoutSuccessUrl(LOGIN_PATH) .logoutSuccessUrl(LOGIN_PATH)

View File

@ -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"};
}

View File

@ -1,86 +1,41 @@
package com.bpgroup.poc.admin.security.authentication; 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.Getter;
import lombok.ToString; import lombok.NoArgsConstructor;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@Getter @Getter
@NoArgsConstructor
public class AuthenticationDetail { public class AuthenticationDetail {
private Long id;
private String loginId; private String loginId;
private String name; private String name;
private String email; private String email;
private MultiValueMap<MenuOneDepth, MenuTwoDepth> menus = new LinkedMultiValueMap<>(); @Builder
public static AuthenticationDetail of(String loginId, String name, String email) {
public static AuthenticationDetail from(LoginResult result) {
AuthenticationDetail authenticationDetail = new AuthenticationDetail(); AuthenticationDetail authenticationDetail = new AuthenticationDetail();
authenticationDetail.id = result.getId(); authenticationDetail.loginId = loginId;
authenticationDetail.loginId = result.getLoginId(); authenticationDetail.name = name;
authenticationDetail.name = result.getName(); authenticationDetail.email = email;
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()
)
));
return authenticationDetail; return authenticationDetail;
} }
/** public String toJsonString() {
* EqualsAndHashCode annotattion이 없을 경우 MultiValueMap을 사용할 중복된 key를 찾지 못함 try {
*/ return new ObjectMapper().writeValueAsString(this);
@Getter } catch (JsonProcessingException e) {
@ToString throw new RuntimeException(e);
@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;
} }
} }
@Getter public static AuthenticationDetail fromJsonString(String jsonString) {
@ToString try {
private static class MenuTwoDepth { return new ObjectMapper().readValue(jsonString, AuthenticationDetail.class);
private String uri; } catch (JsonProcessingException e) {
private String name; throw new RuntimeException(e);
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;
} }
} }

View File

@ -1,4 +1,4 @@
package com.bpgroup.poc.admin.security.authentication.exception; package com.bpgroup.poc.admin.security.authentication;
import lombok.Getter; import lombok.Getter;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;

View File

@ -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.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;

View File

@ -1,6 +1,6 @@
package com.bpgroup.poc.admin.security.authentication; 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 com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;

View File

@ -1,5 +1,6 @@
package com.bpgroup.poc.admin.security.authentication; package com.bpgroup.poc.admin.security.authentication;
import com.bpgroup.poc.admin.security.authentication.service.LoginRequest;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;

View File

@ -1,7 +1,7 @@
package com.bpgroup.poc.admin.security.authentication; package com.bpgroup.poc.admin.security.authentication;
import com.bpgroup.poc.admin.security.authentication.exception.AuthenticationFailException; import com.bpgroup.poc.admin.security.authentication.service.LoginResult;
import com.bpgroup.poc.admin.security.authentication.exception.AuthenticationFailReason; import com.bpgroup.poc.admin.security.authentication.service.LoginService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@ -30,13 +30,22 @@ public class CustomAuthenticationProvider implements AuthenticationProvider {
} }
private UsernamePasswordAuthenticationToken buildAuthenticationToken(LoginResult result) { private UsernamePasswordAuthenticationToken buildAuthenticationToken(LoginResult result) {
UsernamePasswordAuthenticationToken authenticationToken = UsernamePasswordAuthenticationToken.authenticated( UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated(
result.getLoginId(), result.getLoginId(),
null, null,
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 @Override

View File

@ -1,35 +1,52 @@
package com.bpgroup.poc.admin.security.authentication; 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 com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.FilterChain; import io.jsonwebtoken.Jwts;
import jakarta.servlet.ServletException; import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.crypto.SecretKey;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler { public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override @Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException { public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
AuthenticationSuccessHandler.super.onAuthenticationSuccess(request, response, chain, authentication);
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_OK); response.setStatus(HttpServletResponse.SC_OK);
response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.name()); 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( String jsonResponse = new ObjectMapper().writeValueAsString(
LoginResponse.success( LoginResponse.success()
"token"
)
); );
response.getWriter().write(jsonResponse); 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();
}
} }

View File

@ -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<MenuInfo> menus = new HashSet<>();
public static LoginResult of(Long id, String loginId, String name, String email, Set<MenuInfo> 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;
}
}
}

View File

@ -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> 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<LoginResult.MenuInfo> 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();
}
}
}

View File

@ -1,4 +1,4 @@
package com.bpgroup.poc.admin.security.authentication; package com.bpgroup.poc.admin.security.authentication.service;
import lombok.Data; import lombok.Data;

View File

@ -1,4 +1,4 @@
package com.bpgroup.poc.admin.security.authentication; package com.bpgroup.poc.admin.security.authentication.service;
import lombok.Getter; import lombok.Getter;
import lombok.ToString; import lombok.ToString;
@ -11,11 +11,10 @@ public class LoginResponse {
private String resultMessage; private String resultMessage;
private String token; private String token;
public static LoginResponse success(String token) { public static LoginResponse success() {
LoginResponse response = new LoginResponse(); LoginResponse response = new LoginResponse();
response.resultCode = "0000"; response.resultCode = "0000";
response.resultMessage = "Success"; response.resultMessage = "Success";
response.token = token;
return response; return response;
} }

View File

@ -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;
}
}

View File

@ -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> 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()
);
}
}

View File

@ -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 class AdministratorNotFoundException extends Exception {
public AdministratorNotFoundException(String message) { public AdministratorNotFoundException(String message) {

View File

@ -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 class DoNotHaveAnyMenuException extends Exception {
public DoNotHaveAnyMenuException() { public DoNotHaveAnyMenuException() {

View File

@ -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 class InvalidPasswordException extends Exception {
public InvalidPasswordException(String message) { public InvalidPasswordException(String message) {

View File

@ -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;
}

View File

@ -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<String> paths = List.of(SecurityFilterConstants.EXCLUDE_FILTER_STARTS_WITH_URI);
return paths.stream().anyMatch(request.getRequestURI()::startsWith);
}
}

View File

@ -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<MenuChild> 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;
}
}

View File

@ -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> menuInfo() {
return menuQueryRepository.findAllByLoginId();
}
}

View File

@ -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<MenuInfo> findAllByLoginId() {
List<Tuple> 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<String, MenuInfo> menuInfoMap = makeMenuForm(results);
return new ArrayList<>(menuInfoMap.values());
}
private static LinkedHashMap<String, MenuInfo> makeMenuForm(List<Tuple> results) {
LinkedHashMap<String, MenuInfo> 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;
}
}

View File

@ -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 jakarta.servlet.http.HttpServletRequest;
import lombok.Getter; import lombok.Getter;

View File

@ -7,13 +7,13 @@
<h2>메뉴</h2> <h2>메뉴</h2>
<button class="closeButton"><img th:src="@{/images/ico_close.svg}"></button> <button class="closeButton"><img th:src="@{/images/ico_close.svg}"></button>
<div th:if="${#authorization.expression('isAuthenticated()')}"> <div th:if="${#authorization.expression('isAuthenticated()')}">
<div th:each="entry : ${#authentication.details.getMenus().entrySet()}"> <div th:each="menuGroup : ${menuInfos}">
<a class="nav_tit" <a class="nav_tit"
th:classappend="${pathInfo.activeClass(entry.key.getUri())}" th:classappend="${pathInfo.activeClass(menuGroup.getUri())}"
th:text="${entry.key.getName()}"> th:text="${menuGroup.getName()}">
</a> </a>
<ul class="nav_section"> <ul class="nav_section">
<li th:each="menu : ${entry.value}"> <li th:each="menu : ${menuGroup.getMenuChildren()}">
<a th:with="path= ${menu.getUri()}" th:href="${path}" <a th:with="path= ${menu.getUri()}" th:href="${path}"
th:classappend="${pathInfo.activeClass(path)}"><span th:text="${menu.getName()}"></span></a> th:classappend="${pathInfo.activeClass(path)}"><span th:text="${menu.getName()}"></span></a>
</li> </li>