feat: mens-admin 토큰 인증 feat
parent
3ca0b7eae6
commit
5f792f10e7
@ -1,116 +0,0 @@
|
||||
package egovframework.com.jwt;
|
||||
|
||||
import egovframework.com.cmm.LoginVO;
|
||||
import egovframework.com.cmm.util.EgovStringUtil;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.SignatureException;
|
||||
import io.jsonwebtoken.UnsupportedJwtException;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import kr.xit.core.consts.Constants.JwtToken;
|
||||
import kr.xit.core.spring.util.MessageUtil;
|
||||
import kr.xit.core.support.utils.Checks;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
/**
|
||||
* fileName : JwtAuthenticationFilter
|
||||
* author : crlee
|
||||
* date : 2023/06/11
|
||||
* description :
|
||||
* ===========================================================
|
||||
* DATE AUTHOR NOTE
|
||||
* -----------------------------------------------------------
|
||||
* 2023/06/11 crlee 최초 생성
|
||||
*/
|
||||
@Slf4j
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Autowired
|
||||
private EgovJwtTokenUtil jwtTokenUtil;
|
||||
@Autowired
|
||||
private MessageUtil messageUtil;
|
||||
@Autowired
|
||||
private JwtVerification verification;
|
||||
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
|
||||
boolean verificationFlag = true;
|
||||
|
||||
// step 1. request header에서 토큰을 가져온다.
|
||||
String jwtToken = EgovStringUtil.isNullToString(request.getHeader(JwtToken.HEADER_NAME.getCode()));
|
||||
if(Checks.isEmpty(jwtToken)){
|
||||
setError(response, jwtToken, messageUtil.getMessage("fail.auth.header.invalid", new String[]{jwtToken}));
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// token validation
|
||||
if(!verification.isVerification(jwtToken)){
|
||||
setError(response, jwtToken, messageUtil.getMessage("fail.auth.header.invalid", new String[]{jwtToken}));
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
};
|
||||
|
||||
// step 2. 토큰에 내용이 있는지 확인해서 id값을 가져옴
|
||||
// Exception 핸들링 추가처리 (토큰 유효성, 토큰 변조 여부, 토큰 만료여부)
|
||||
// 내부적으로 parse하는 과정에서 해당 여부들이 검증됨
|
||||
String id = null;
|
||||
|
||||
try {
|
||||
|
||||
id = jwtTokenUtil.getUserIdFromToken(jwtToken);
|
||||
if (id == null) {
|
||||
logger.debug("jwtToken not validate");
|
||||
verificationFlag = false;
|
||||
}
|
||||
logger.debug("===>>> id = " + id);
|
||||
} catch (IllegalArgumentException | ExpiredJwtException | MalformedJwtException | UnsupportedJwtException | SignatureException e) {
|
||||
setError(response, jwtToken, "Unable to verify JWT Token: " + e.getMessage());
|
||||
verificationFlag = false;
|
||||
}
|
||||
|
||||
LoginVO loginVO = new LoginVO();
|
||||
if( verificationFlag ){
|
||||
logger.debug("jwtToken validated");
|
||||
loginVO.setId(id);
|
||||
loginVO.setUserSe( jwtTokenUtil.getUserSeFromToken(jwtToken) );
|
||||
loginVO.setUniqId( jwtTokenUtil.getInfoFromToken("uniqId",jwtToken) );
|
||||
loginVO.setOrgnztId( jwtTokenUtil.getInfoFromToken("orgnztId",jwtToken) );
|
||||
loginVO.setName( jwtTokenUtil.getInfoFromToken("name",jwtToken) );
|
||||
|
||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(loginVO, null,
|
||||
Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"))
|
||||
);
|
||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"MismatchedQueryAndUpdateOfCollection", "unchecked"})
|
||||
private void setError(HttpServletResponse response, final String jwtToken, final String errMsg) throws IOException {
|
||||
log.error(errMsg);
|
||||
// response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
// response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
// response.setCharacterEncoding(StandardCharsets.UTF_8.displayName());
|
||||
|
||||
// JSONObject resJson = new JSONObject();
|
||||
// resJson.put("code", 401);
|
||||
// resJson.put("message", errMsg);
|
||||
// response.getWriter().write(resJson.toJSONString());
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
package egovframework.com.security;
|
||||
|
||||
import egovframework.com.jwt.JwtAuthenticationEntryPoint;
|
||||
import egovframework.com.jwt.JwtAuthenticationFilter;
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* fileName : SecurityConfig
|
||||
* author : crlee
|
||||
* date : 2023/06/10
|
||||
* description :
|
||||
* ===========================================================
|
||||
* DATE AUTHOR NOTE
|
||||
* -----------------------------------------------------------
|
||||
* 2023/06/10 crlee 최초 생성
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig implements WebMvcConfigurer {
|
||||
|
||||
//Http Methpd : Get 인증예외 List
|
||||
private String[] AUTH_GET_WHITELIST = {
|
||||
"/schedule/daily", //일별 일정 조회
|
||||
"/schedule/week", //주간 일정 조회
|
||||
"/schedule/{schdulId}", //일정 상세조회
|
||||
};
|
||||
|
||||
// 인증 예외 List
|
||||
@Value("${app.spring.security.white-list}")
|
||||
private String[] AUTH_WHITELIST;
|
||||
|
||||
@Override
|
||||
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
|
||||
argumentResolvers.add(new CustomAuthenticationPrincipalResolver());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtAuthenticationFilter authenticationTokenFilterBean() throws Exception {
|
||||
return new JwtAuthenticationFilter();
|
||||
}
|
||||
|
||||
|
||||
// @Bean
|
||||
// protected CorsConfigurationSource corsConfigurationSource() {
|
||||
// CorsConfiguration configuration = new CorsConfiguration();
|
||||
//
|
||||
// configuration.setAllowedOriginPatterns(Arrays.asList("*"));
|
||||
// configuration.setAllowedMethods(Arrays.asList("HEAD","POST","GET","DELETE","PUT"));
|
||||
// configuration.setAllowedOrigins(Arrays.asList(ORIGINS_WHITELIST));
|
||||
// configuration.setAllowedHeaders(Arrays.asList("*"));
|
||||
// configuration.setAllowCredentials(true);
|
||||
//
|
||||
// UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
// source.registerCorsConfiguration("/**", configuration);
|
||||
// return source;
|
||||
// }
|
||||
@Bean
|
||||
protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
|
||||
return http
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.antMatchers(AUTH_WHITELIST).permitAll()
|
||||
.antMatchers(HttpMethod.GET,AUTH_GET_WHITELIST).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
).sessionManagement((sessionManagement) ->
|
||||
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
)
|
||||
.cors().and()
|
||||
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class)
|
||||
.exceptionHandling(exceptionHandlingConfigurer ->
|
||||
exceptionHandlingConfigurer
|
||||
.authenticationEntryPoint(new JwtAuthenticationEntryPoint())
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package kr.xit.biz.auth.mapper;
|
||||
|
||||
import egovframework.com.cmm.LoginVO;
|
||||
import org.egovframe.rte.psl.dataaccess.mapper.Mapper;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description :
|
||||
*
|
||||
* packageName : kr.xit.biz.auth.mapper
|
||||
* fileName : IAuthApiMapper
|
||||
* author : limju
|
||||
* date : 2023-05-11
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-05-11 limju 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Mapper
|
||||
public interface IAuthApiMapper {
|
||||
LoginVO actionLogin(LoginVO vo);
|
||||
// LoginVO searchId(LoginVO vo);
|
||||
// LoginVO searchPassword(LoginVO vo);
|
||||
// void updatePassword(LoginVO vo);
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package kr.xit.core.spring.auth;
|
||||
|
||||
import java.util.List;
|
||||
import kr.xit.core.exception.BizRuntimeException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : 권한 정보를 생성하고 검증 하는 utilis class
|
||||
* packageName : kr.xit.core.spring.auth
|
||||
* fileName : CustomAuthorityUtils
|
||||
* author : julim
|
||||
* date : 2023-11-29
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-11-29 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
public class CustomAuthorityUtils {
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* role 값을 기반으로 권한 정보를 생성하여 List<GrantedAuthority> 타입으로 변환
|
||||
* 권한 정보는 “ROLE_USER”, “ROLE_ADMIN” 형식으로 생성
|
||||
* @param role 권한 : ROLE_USR|ROLE_ADMIN 형태
|
||||
* @return List<GrantedAuthority> </pre>
|
||||
*/
|
||||
public static List<GrantedAuthority> createAuthorities(String role) {
|
||||
return List.of(new SimpleGrantedAuthority("ROLE_" + role));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 입력된 role 값이 유효한 권한인지 검증
|
||||
* @param role String USER|ADMIN
|
||||
*/
|
||||
public static void verifiedRole(String role) {
|
||||
if (role == null) {
|
||||
throw BizRuntimeException.create("fail.jwt.role.notExists");
|
||||
} else if (!role.equals("USER") && !role.equals("ADMIN")) {
|
||||
throw BizRuntimeException.create("fail.jwt.role.invalid", new String[]{role});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
package kr.xit.core.spring.auth;
|
||||
|
||||
import egovframework.com.cmm.model.LoginVO;
|
||||
import java.util.Collection;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : UserDetails 인터페이스를 구현한 클래스
|
||||
* Spring Security에서 관리하는 User 정보를 관리
|
||||
* packageName : kr.xit.core.spring.auth
|
||||
* fileName : CustomUserDetails
|
||||
* author : julim
|
||||
* date : 2023-11-29
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-11-29 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@ToString
|
||||
public class CustomUserDetails implements UserDetails {
|
||||
private String id;
|
||||
private String email;
|
||||
private String role;
|
||||
private String password;
|
||||
|
||||
private CustomUserDetails(LoginVO loginVO) {
|
||||
this.id = loginVO.getId();
|
||||
this.email = loginVO.getEmail();
|
||||
this.password = loginVO.getPassword();
|
||||
//this.role = loginVO.getRole();
|
||||
}
|
||||
|
||||
private CustomUserDetails(String email, String role) {
|
||||
this.email = email;
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
private CustomUserDetails(String email, String password, String role) {
|
||||
this.email = email;
|
||||
this.password = password;
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public static CustomUserDetails of(LoginVO loginVO) {
|
||||
return new CustomUserDetails(loginVO);
|
||||
}
|
||||
|
||||
public static CustomUserDetails of(String email, String role) {
|
||||
return new CustomUserDetails(email, role);
|
||||
}
|
||||
|
||||
public static CustomUserDetails of(String email, String password, String role) {
|
||||
return new CustomUserDetails(email, password, role);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 권한을 생성하고 List<GrantedAuthority> 타입으로 반환
|
||||
* @return Collection<? extends GrantedAuthority>
|
||||
* </pre>
|
||||
*/
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return CustomAuthorityUtils.createAuthorities(role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 계정이 만료되지 않았는가?
|
||||
* @return true|false
|
||||
*/
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 계정이 잠긴 상태인가?
|
||||
* @return true| false
|
||||
*/
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 인증정보가 만료되지 않았는가?
|
||||
* @return true| flase
|
||||
*/
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 계정이 활성화 상태인가?
|
||||
* @return true|false
|
||||
*/
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,154 @@
|
||||
package kr.xit.core.spring.auth;
|
||||
|
||||
import java.util.List;
|
||||
import kr.xit.core.spring.auth.filter.JwtAuthenticationFilter;
|
||||
import kr.xit.core.spring.auth.handler.CustomAccessDeniedHandler;
|
||||
import kr.xit.core.spring.auth.handler.CustomAuthenticationFailureHandler;
|
||||
import kr.xit.core.spring.auth.handler.CustomAuthenticationSuccessHandler;
|
||||
import kr.xit.core.spring.auth.jwt.JwtTokenProvider;
|
||||
import kr.xit.core.spring.auth.mapper.IAuthMapper;
|
||||
import kr.xit.core.spring.util.AES128Config;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : Spring security 설정
|
||||
* packageName : kr.xit.core.spring.auth
|
||||
* fileName : SecurityConfig
|
||||
* author : julim
|
||||
* date : 2023-11-29
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-11-29 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfig implements WebMvcConfigurer {
|
||||
//Http Methpd : Get 인증예외 List
|
||||
private String[] AUTH_GET_WHITELIST = {
|
||||
"/schedule/daily", //일별 일정 조회
|
||||
"/schedule/week", //주간 일정 조회
|
||||
"/schedule/{schdulId}", //일정 상세조회
|
||||
};
|
||||
|
||||
// 인증 예외 List
|
||||
@Value("${app.spring.security.white-list}")
|
||||
private String[] AUTH_WHITELIST;
|
||||
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
private final AES128Config aes128Config;
|
||||
private final IAuthMapper authMapper;
|
||||
|
||||
@Override
|
||||
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
|
||||
argumentResolvers.add(new CustomAuthenticationPrincipalResolver());
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* SecurityFilterChain 설정
|
||||
* -> 애플리케이션의 보안 설정을 구성
|
||||
* <code>headers().frameOptions().sameOrigin()</code> : X-Frame-Options 헤더 설정을 SAMEORIGIN으로 설정하여, 웹 페이지를 iframe으로 삽입하는 공격 방지를 위한 설정
|
||||
* <code>http.csrf().disable()</code> : jwt를 사용하기 때문에 CSRF(Cross-Site Request Forgery) 공격 방지 기능을 사용하지 않는다.
|
||||
* <code>http.cors().configurationSource(corsConfigurationSource())</code> : CORS(Cross-Origin Resource Sharing)를 활성화 및 설정 - 공통에서 처리
|
||||
* <code>http.formLogin().disable()</code> : 폼 기반 로그인 방식 비활성화
|
||||
* <code>http.httpBasic().disable()</code> : HTTP 기본 인증 방식 비활성화
|
||||
* <code>sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)</code> :인증에 사용할 세션을 생성하지 않도록 설정
|
||||
* <code>exceptionHandling()</code> :예외 처리를 설정
|
||||
* <code>authenticationEntryPoint(new CustomAuthenticationEntryPoint())</code> :인증되지 않은 사용자가 보호된 리소스에 접근할 때 호출할 엔드포인트 설정
|
||||
* <code>accessDeniedHandler(new CustomAccessDeniedHandler())</code> :인가되지 않은 사용자가 보호된 리소스에 접근할 때 호출할 핸들러 설정
|
||||
* <code>apply(new CustomFilterConfigurer())</code> :사용자 정의 필터 적용
|
||||
* <code>authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll())</code> :모든 HTTP 요청에 대해 접근 허용
|
||||
* @param http HttpSecurity
|
||||
* @return SecurityFilterChain
|
||||
* </pre>
|
||||
*/
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.headers().frameOptions().sameOrigin()
|
||||
.and()
|
||||
.csrf().disable()
|
||||
//.cors().configurationSource(corsConfigurationSource())
|
||||
.cors().and()
|
||||
.formLogin().disable()
|
||||
.httpBasic().disable()
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
.and()
|
||||
.exceptionHandling()
|
||||
.authenticationEntryPoint(new CustomAuthenticationEntryPoint())
|
||||
.accessDeniedHandler(new CustomAccessDeniedHandler())
|
||||
.and()
|
||||
.apply(new CustomFilterConfigurer(authMapper))
|
||||
.and()
|
||||
// TODO: 추후 권한 별 페이지 접근제어 설정 예정
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.antMatchers(AUTH_WHITELIST).permitAll()
|
||||
//.antMatchers(AUTH_WHITELIST).authenticated()
|
||||
.antMatchers(HttpMethod.GET,AUTH_GET_WHITELIST).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : 보안 설정을 정의하는 HttpSecurity를 구성
|
||||
* -> 인증 및 권한 부여를 위해 사용되는 필터 체인을 구성하는 데 사용
|
||||
* - AbstractHttpConfigurer 를 상속하며 configure() 메서드를 재정의
|
||||
* packageName : kr.xit.core.spring.auth
|
||||
* fileName : CustomFilterConfigurer
|
||||
* author : julim
|
||||
* date : 2023-11-29
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-11-29 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class CustomFilterConfigurer extends
|
||||
AbstractHttpConfigurer<CustomFilterConfigurer, HttpSecurity> {
|
||||
private final IAuthMapper authMapper;
|
||||
|
||||
@Override
|
||||
public void configure(HttpSecurity httpSecurity) throws Exception {
|
||||
log.info("SecurityConfiguration.CustomFilterConfigurer.configure excute");
|
||||
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(
|
||||
AuthenticationManager.class);
|
||||
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager,
|
||||
jwtTokenProvider, aes128Config, authMapper);
|
||||
//JwtVerificationFilter jwtVerificationFilter = new JwtVerificationFilter(jwtTokenProvider, redisService);
|
||||
|
||||
jwtAuthenticationFilter.setFilterProcessesUrl("/auth/login");
|
||||
jwtAuthenticationFilter.setAuthenticationSuccessHandler(new CustomAuthenticationSuccessHandler());
|
||||
jwtAuthenticationFilter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler());
|
||||
|
||||
httpSecurity
|
||||
.addFilter(jwtAuthenticationFilter)
|
||||
// .addFilterAfter(jwtVerificationFilter, JwtAuthenticationFilter.class);
|
||||
// .addFilterAfter(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package kr.xit.core.spring.auth.filter;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import egovframework.com.cmm.model.LoginVO;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import kr.xit.core.exception.BizRuntimeException;
|
||||
import kr.xit.core.model.RefreshTokenDTO;
|
||||
import kr.xit.core.model.TokenDTO;
|
||||
import kr.xit.core.spring.auth.CustomUserDetails;
|
||||
import kr.xit.core.spring.auth.jwt.JwtTokenProvider;
|
||||
import kr.xit.core.spring.auth.mapper.IAuthMapper;
|
||||
import kr.xit.core.spring.util.AES128Config;
|
||||
import kr.xit.core.support.utils.DateUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : 로그인을 담당/ 토큰 발급을 진행
|
||||
* UsernamePasswordAuthenticationFilter를 상속 받는 경우
|
||||
* 1. 필터를 거치고 다음 필터로 동작을 넘기지 않는다.
|
||||
* -> 따라서 인증 성공 여부에 따른 메서드 successfulAuthentication/unSuccessfulAuthentication 구현 필요
|
||||
* 2. 해당 필터는 /login에 접근할 때만 동작
|
||||
* -> 따라서 특정 Url에서 필터가 동작하길 원한다면 setFilterProcessesUrl()로 Url를 설정해야 한다
|
||||
* 3. 위와 같은 특징 때문에 Jwt 필터를 따로 만들어줘야 한다.
|
||||
* packageName : kr.xit.core.spring.auth.filter
|
||||
* fileName : JwtAuthorizationFilter
|
||||
* author : julim
|
||||
* date : 2023-11-29
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-11-29 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
|
||||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
private final AES128Config aes128Config;
|
||||
private final IAuthMapper authMapper;
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public Authentication attemptAuthentication(HttpServletRequest request,
|
||||
HttpServletResponse response) throws AuthenticationException {
|
||||
// ServletInputStream을 LoginDto 객체로 역직렬화
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
LoginVO loginVO = objectMapper.readValue(request.getInputStream(), LoginVO.class);
|
||||
UsernamePasswordAuthenticationToken authenticationToken =
|
||||
new UsernamePasswordAuthenticationToken(loginVO.getEmail(), loginVO.getPassword());
|
||||
|
||||
return authenticationManager.authenticate(authenticationToken);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
|
||||
CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal();
|
||||
|
||||
Map<String,Object> cliams = new HashMap<>();
|
||||
cliams.put("role", customUserDetails.getRole());
|
||||
TokenDTO tokenDto = jwtTokenProvider.generateTokenDto(cliams, customUserDetails.getUsername());
|
||||
|
||||
String accessToken = tokenDto.getAccessToken();
|
||||
String refreshToken = tokenDto.getRefreshToken();
|
||||
String encryptedRefreshToken = aes128Config.encryptAes(refreshToken);
|
||||
jwtTokenProvider.accessTokenSetHeader(accessToken, response);
|
||||
jwtTokenProvider.refresshTokenSetHeader(encryptedRefreshToken, response);
|
||||
|
||||
LoginVO loginVO = authMapper.selectUserById(customUserDetails.getId())
|
||||
.orElseThrow(() -> BizRuntimeException.of("fail.auth.login.user"));
|
||||
|
||||
//TODO:: 로그인 성공시 Refresh Token 저장
|
||||
Date date = jwtTokenProvider.getExpirationDateFromToken(refreshToken);
|
||||
authMapper.saveRefreshToken(
|
||||
RefreshTokenDTO.builder()
|
||||
.id(loginVO.getId())
|
||||
.refreshToken(refreshToken)
|
||||
.refreshTokenExpiresIn(DateUtils.parseToLong(date))
|
||||
.build()
|
||||
);
|
||||
//...(loginVO.getId(), refreshToken, Duration.ofMillis(DateUtils.parseToLong(date)));
|
||||
|
||||
this.getSuccessHandler().onAuthenticationSuccess(request, response, authentication);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package kr.xit.core.spring.auth.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
|
||||
|
||||
private String denied_url;
|
||||
|
||||
public CustomAccessDeniedHandler() {
|
||||
this.denied_url = "egovframework/com/cmm/error/csrfAccessDenied";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response,
|
||||
AccessDeniedException accessDeniedException) throws IOException, ServletException {
|
||||
response.sendRedirect(request.getContextPath() + denied_url);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package kr.xit.core.spring.auth.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.InsufficientAuthenticationException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
|
||||
throws IOException, ServletException, IOException {
|
||||
|
||||
String errorMessage = "Invalid Username or Password";
|
||||
|
||||
if(exception instanceof BadCredentialsException){
|
||||
errorMessage = "Invalid Username or Password";
|
||||
}else if(exception instanceof InsufficientAuthenticationException){
|
||||
errorMessage = "Invalid Secret Key";
|
||||
}
|
||||
|
||||
setDefaultFailureUrl("/auth/login?error=true&exception=" + exception.getMessage());
|
||||
|
||||
super.onAuthenticationFailure(request, response, exception);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package kr.xit.core.spring.auth.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.DefaultRedirectStrategy;
|
||||
import org.springframework.security.web.RedirectStrategy;
|
||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
|
||||
import org.springframework.security.web.savedrequest.RequestCache;
|
||||
import org.springframework.security.web.savedrequest.SavedRequest;
|
||||
|
||||
public class CustomAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
|
||||
private final RequestCache requestCache = new HttpSessionRequestCache();
|
||||
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||
Authentication authentication) throws IOException, ServletException {
|
||||
|
||||
setDefaultTargetUrl("/auth/login");
|
||||
SavedRequest savedRequest = requestCache.getRequest(request, response);
|
||||
|
||||
// 사용자가 권한이 필요한 자원에 접근해 인증 예외가 발생해 인증을 처리하는 것이 아닌 경우
|
||||
// SavedRequest 객체가 생성되지 않는다.
|
||||
if (savedRequest != null) {
|
||||
String targetUrl = savedRequest.getRedirectUrl();
|
||||
redirectStrategy.sendRedirect(request, response, targetUrl);
|
||||
} else {
|
||||
redirectStrategy.sendRedirect(request, response, getDefaultTargetUrl());
|
||||
}
|
||||
super.clearAuthenticationAttributes(request);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package kr.xit.core.spring.auth.mapper;
|
||||
|
||||
import egovframework.com.cmm.model.LoginVO;
|
||||
import java.util.Optional;
|
||||
import kr.xit.core.model.RefreshTokenDTO;
|
||||
import org.egovframe.rte.psl.dataaccess.mapper.Mapper;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description :
|
||||
*
|
||||
* packageName : kr.xit.core.spring.auth.mapper
|
||||
* fileName : IAuthMapper
|
||||
* author : limju
|
||||
* date : 2023-11-29
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-11-29 limju 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Mapper
|
||||
public interface IAuthMapper {
|
||||
<T> LoginVO login(T t);
|
||||
Optional<LoginVO> selectUserById(String id);
|
||||
|
||||
int saveRefreshToken(final RefreshTokenDTO dto);
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package kr.xit.core.spring.auth.service;
|
||||
|
||||
import egovframework.com.cmm.model.LoginVO;
|
||||
import kr.xit.core.exception.BizRuntimeException;
|
||||
import kr.xit.core.spring.auth.CustomUserDetails;
|
||||
import kr.xit.core.spring.auth.mapper.IAuthMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@Transactional
|
||||
@RequiredArgsConstructor
|
||||
public class CustomUserDetailsService implements UserDetailsService {
|
||||
private final IAuthMapper mapper;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
return mapper.selectUserById(username)
|
||||
.map(this::createUserDetails)
|
||||
.orElseThrow(() -> BizRuntimeException.of("fail.auth.login.user"));
|
||||
}
|
||||
|
||||
private UserDetails createUserDetails(LoginVO member) {
|
||||
return CustomUserDetails.of(member);
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="kr.xit.biz.auth.mapper.IAuthApiMapper">
|
||||
<!-- 일반 로그인 -->
|
||||
<select id="actionLogin" resultType="egovframework.com.cmm.LoginVO">
|
||||
<if test="userSe = 'USR'">
|
||||
/** auth-mysql-mapper|actionLogin-로그인|julim */
|
||||
SELECT user_id AS id
|
||||
, user_nm AS name
|
||||
, password
|
||||
, ihidnum
|
||||
, email_adres AS email
|
||||
, 'USR' AS userSe
|
||||
, orgnzt_id
|
||||
, esntl_id
|
||||
FROM xit_user_info
|
||||
WHERE user_id = #{id}
|
||||
AND password = #{password}
|
||||
AND user_sttus_code = 'P'
|
||||
|
||||
</if>
|
||||
</select>
|
||||
|
||||
</mapper>
|
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="kr.xit.core.spring.auth.mapper.IAuthMapper">
|
||||
<!-- 일반 로그인 -->
|
||||
<select id="login" resultType="egovframework.com.cmm.model.LoginVO">
|
||||
<if test="userSe = 'USR'">
|
||||
/** auth-mysql-mapper|login-로그인|julim */
|
||||
SELECT user_id AS id
|
||||
, user_nm AS name
|
||||
, password
|
||||
, ihidnum
|
||||
, email_adres AS email
|
||||
, 'USR' AS userSe
|
||||
, orgnzt_id
|
||||
, esntl_id
|
||||
FROM xit_user_info
|
||||
WHERE user_id = #{id}
|
||||
AND password = #{password}
|
||||
AND user_sttus_code = 'P'
|
||||
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<select id="selectUserById" resultType="egovframework.com.cmm.model.LoginVO">
|
||||
<if test="userSe = 'USR'">
|
||||
/** auth-mysql-mapper|selectUserById-사용자조회|julim */
|
||||
SELECT user_id AS id
|
||||
, user_nm AS name
|
||||
, password
|
||||
, ihidnum
|
||||
, email_adres AS email
|
||||
, 'USR' AS userSe
|
||||
, orgnzt_id
|
||||
, esntl_id
|
||||
FROM xit_user_info
|
||||
WHERE user_id = #{id}
|
||||
AND user_sttus_code = 'P'
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<insert id="saveRefreshToken">
|
||||
|
||||
</insert>
|
||||
|
||||
</mapper>
|
Loading…
Reference in New Issue