admin: JWT 기반 인증 방식으로 변경
This commit is contained in:
parent
b81ab51a11
commit
b3e255bda6
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"};
|
||||
}
|
||||
|
|
@ -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<MenuOneDepth, MenuTwoDepth> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.bpgroup.poc.admin.security.authentication;
|
||||
package com.bpgroup.poc.admin.security.authentication.service;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
@ -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() {
|
||||
|
|
@ -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) {
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -7,13 +7,13 @@
|
|||
<h2>메뉴</h2>
|
||||
<button class="closeButton"><img th:src="@{/images/ico_close.svg}"></button>
|
||||
<div th:if="${#authorization.expression('isAuthenticated()')}">
|
||||
<div th:each="entry : ${#authentication.details.getMenus().entrySet()}">
|
||||
<div th:each="menuGroup : ${menuInfos}">
|
||||
<a class="nav_tit"
|
||||
th:classappend="${pathInfo.activeClass(entry.key.getUri())}"
|
||||
th:text="${entry.key.getName()}">
|
||||
th:classappend="${pathInfo.activeClass(menuGroup.getUri())}"
|
||||
th:text="${menuGroup.getName()}">
|
||||
</a>
|
||||
<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}"
|
||||
th:classappend="${pathInfo.activeClass(path)}"><span th:text="${menu.getName()}"></span></a>
|
||||
</li>
|
||||
|
|
|
|||
Loading…
Reference in New Issue