Merge pull request #39 from Chung10Kr/contribution-018
Apply Spring Security, Implement JwtAuthenticationFilter, Remove Duplicate Token Authentication Codemain
commit
4564a608ad
@ -0,0 +1,53 @@
|
||||
package egovframework.com.jwt;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import egovframework.com.cmm.EgovWebUtil;
|
||||
import egovframework.com.cmm.LoginVO;
|
||||
import egovframework.com.cmm.ResponseCode;
|
||||
import egovframework.com.cmm.service.ResultVO;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* fileName : JwtAuthenticationEntryPoint
|
||||
* author : crlee
|
||||
* date : 2023/06/11
|
||||
* description :
|
||||
* ===========================================================
|
||||
* DATE AUTHOR NOTE
|
||||
* -----------------------------------------------------------
|
||||
* 2023/06/11 crlee 최초 생성
|
||||
*/
|
||||
|
||||
@Component
|
||||
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
|
||||
|
||||
ResultVO resultVO = new ResultVO();
|
||||
resultVO.setResultCode(ResponseCode.AUTH_ERROR.getCode());
|
||||
resultVO.setResultMessage(ResponseCode.AUTH_ERROR.getMessage());
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
//Convert object to JSON string
|
||||
String jsonInString = mapper.writeValueAsString(resultVO);
|
||||
|
||||
|
||||
|
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||
response.setContentType(MediaType.APPLICATION_JSON.toString());
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.getWriter().println(jsonInString);
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
package egovframework.com.jwt;
|
||||
|
||||
import egovframework.com.cmm.LoginVO;
|
||||
import egovframework.let.utl.fcc.service.EgovStringUtil;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.SignatureException;
|
||||
import io.jsonwebtoken.UnsupportedJwtException;
|
||||
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;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* fileName : JwtAuthenticationFilter
|
||||
* author : crlee
|
||||
* date : 2023/06/11
|
||||
* description :
|
||||
* ===========================================================
|
||||
* DATE AUTHOR NOTE
|
||||
* -----------------------------------------------------------
|
||||
* 2023/06/11 crlee 최초 생성
|
||||
*/
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Autowired
|
||||
private EgovJwtTokenUtil jwtTokenUtil;
|
||||
public static final String HEADER_STRING = "Authorization";
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
boolean verificationFlag = true;
|
||||
|
||||
// step 1. request header에서 토큰을 가져온다.
|
||||
String jwtToken = EgovStringUtil.isNullToString(req.getHeader(HEADER_STRING));
|
||||
|
||||
|
||||
// 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) {
|
||||
logger.debug("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(req));
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
|
||||
|
||||
chain.doFilter(req, res);
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
package egovframework.com.jwt.config;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import egovframework.let.utl.fcc.service.EgovStringUtil;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.SignatureException;
|
||||
import io.jsonwebtoken.UnsupportedJwtException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class JwtVerification {
|
||||
|
||||
@Autowired
|
||||
private EgovJwtTokenUtil jwtTokenUtil;
|
||||
|
||||
public boolean isVerification(HttpServletRequest request) {
|
||||
|
||||
boolean verificationFlag = true;
|
||||
|
||||
// step 1. request header에서 토큰을 가져온다.
|
||||
String jwtToken = EgovStringUtil.isNullToString(request.getHeader("authorization"));
|
||||
|
||||
|
||||
// step 2. 토큰에 내용이 있는지 확인해서 username값을 가져옴
|
||||
// Exception 핸들링 추가처리 (토큰 유효성, 토큰 변조 여부, 토큰 만료여부)
|
||||
// 내부적으로 parse하는 과정에서 해당 여부들이 검증됨
|
||||
String username = null;
|
||||
|
||||
try {
|
||||
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
|
||||
} catch (IllegalArgumentException | ExpiredJwtException | MalformedJwtException | UnsupportedJwtException | SignatureException e) {
|
||||
log.debug("Unable to verify JWT Token: " + e.getMessage());
|
||||
verificationFlag = false;
|
||||
return verificationFlag;
|
||||
}
|
||||
|
||||
log.debug("===>>> username = " + username);
|
||||
|
||||
// step 3. 가져온 username 유무 체크
|
||||
if (username == null) {
|
||||
log.debug("jwtToken not validate");
|
||||
verificationFlag = false;
|
||||
return verificationFlag;
|
||||
}
|
||||
|
||||
log.debug("jwtToken validated");
|
||||
|
||||
return verificationFlag;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
package egovframework.com.security;
|
||||
|
||||
import egovframework.com.cmm.LoginVO;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
/**
|
||||
* fileName : CustomAuthenticationPrincipalResolver
|
||||
* author : crlee
|
||||
* date : 2023/07/13
|
||||
* description :
|
||||
* ===========================================================
|
||||
* DATE AUTHOR NOTE
|
||||
* -----------------------------------------------------------
|
||||
* 2023/07/13 crlee 최초 생성
|
||||
*/
|
||||
public class CustomAuthenticationPrincipalResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return parameter.hasParameterAnnotation(AuthenticationPrincipal.class) &&
|
||||
parameter.getParameterType().equals(LoginVO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
|
||||
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication == null ||
|
||||
authentication.getPrincipal() == null ||
|
||||
"anonymousUser".equals(authentication.getPrincipal())
|
||||
) {
|
||||
return new LoginVO();
|
||||
}
|
||||
|
||||
return authentication.getPrincipal();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
package egovframework.com.security;
|
||||
|
||||
import egovframework.com.jwt.JwtAuthenticationEntryPoint;
|
||||
import egovframework.com.jwt.JwtAuthenticationFilter;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
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.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* fileName : SecurityConfig
|
||||
* author : crlee
|
||||
* date : 2023/06/10
|
||||
* description :
|
||||
* ===========================================================
|
||||
* DATE AUTHOR NOTE
|
||||
* -----------------------------------------------------------
|
||||
* 2023/06/10 crlee 최초 생성
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
// 인증 예외 List
|
||||
private String[] AUTH_WHITELIST = {
|
||||
"/",
|
||||
"/login/**",
|
||||
"/uat/uia/actionLoginJWT.do",//JWT 로그인
|
||||
"/uat/uia/actionLoginAPI.do",//일반 로그인
|
||||
"/cmm/main/**.do", // 메인페이지
|
||||
"/cmm/fms/FileDown.do", //파일 다운로드
|
||||
"/cmm/fms/getImage.do", //갤러리 이미지보기
|
||||
"/cop/smt/sim/egovIndvdlSchdulManageDailyListAPI.do", //일별 일정 조회
|
||||
"/cop/smt/sim/egovIndvdlSchdulManageWeekListAPI.do", //주간 일정 조회
|
||||
"/cop/smt/sim/egovIndvdlSchdulManageDetailAPI.do", //일정 상세조회
|
||||
|
||||
"/cop/bbs/selectUserBBSMasterInfAPI.do", //게시판 마스터 상세 조회
|
||||
"/cop/bbs/selectBoardListAPI.do", //게시판 목록조회
|
||||
"/cop/bbs/selectBoardArticleAPI.do", //게시물 상세조회
|
||||
|
||||
/* swagger v2 */
|
||||
"/v2/api-docs",
|
||||
"/swagger-resources",
|
||||
"/swagger-resources/**",
|
||||
"/swagger-ui.html",
|
||||
"/swagger-ui/**"
|
||||
};
|
||||
private static final String[] ORIGINS_WHITELIST = {
|
||||
"http://localhost:3000",
|
||||
};
|
||||
|
||||
@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()
|
||||
.anyRequest().authenticated()
|
||||
).sessionManagement((sessionManagement) ->
|
||||
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
)
|
||||
.cors().and()
|
||||
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class)
|
||||
.exceptionHandling(exceptionHandlingConfigurer ->
|
||||
exceptionHandlingConfigurer
|
||||
.authenticationEntryPoint(new JwtAuthenticationEntryPoint())
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package egovframework.com.security;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* fileName : WebMvcConfig
|
||||
* author : crlee
|
||||
* date : 2023/07/13
|
||||
* description :
|
||||
* ===========================================================
|
||||
* DATE AUTHOR NOTE
|
||||
* -----------------------------------------------------------
|
||||
* 2023/07/13 crlee 최초 생성
|
||||
*/
|
||||
@Configuration
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
@Override
|
||||
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
|
||||
argumentResolvers.add(new CustomAuthenticationPrincipalResolver());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,101 @@
|
||||
package egovframework.let.uat.uia.web;
|
||||
|
||||
import egovframework.com.cmm.ResponseCode;
|
||||
import egovframework.com.cmm.service.ResultVO;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.springframework.http.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* fileName : EgovLoginApiContollerTest
|
||||
* author : crlee
|
||||
* date : 2023/06/19
|
||||
* description :
|
||||
* ===========================================================
|
||||
* DATE AUTHOR NOTE
|
||||
* -----------------------------------------------------------
|
||||
* 2023/06/19 crlee 최초 생성
|
||||
*/
|
||||
@TestInstance(TestInstance. Lifecycle.PER_CLASS)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
public class EgovLoginApiControllerTest {
|
||||
|
||||
@Value("${server.servlet.context-path}")
|
||||
String CONTEXT_PATH;
|
||||
String URL = "http://localhost";
|
||||
|
||||
@LocalServerPort
|
||||
int randomServerPort;
|
||||
String SERVER_URL;
|
||||
|
||||
@BeforeAll
|
||||
void init(){
|
||||
this.SERVER_URL = String.format("%s:%s%s", URL,randomServerPort,CONTEXT_PATH);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@DisplayName("인증 성공")
|
||||
void hasToken(){
|
||||
String token = getToken();
|
||||
ResponseEntity<ResultVO> result = callApi(token);
|
||||
Assertions.assertThat( result.getStatusCode() ).isEqualTo( HttpStatus.OK );
|
||||
Assertions.assertThat( result.getBody().getResultCode() ).isEqualTo( ResponseCode.SUCCESS.getCode() );
|
||||
Assertions.assertThat( result.getBody().getResultMessage() ).isEqualTo( ResponseCode.SUCCESS.getMessage() );
|
||||
}
|
||||
@Test
|
||||
@DisplayName("인증 실패 - Token null")
|
||||
void noToken(){
|
||||
ResponseEntity<ResultVO> result = callApi(null);
|
||||
Assertions.assertThat( result.getStatusCode() ).isEqualTo( HttpStatus.UNAUTHORIZED );
|
||||
Assertions.assertThat( result.getBody().getResultCode() ).isEqualTo( ResponseCode.AUTH_ERROR.getCode() );
|
||||
Assertions.assertThat( result.getBody().getResultMessage() ).isEqualTo( ResponseCode.AUTH_ERROR.getMessage() );
|
||||
}
|
||||
@Test
|
||||
@DisplayName("인증 실패 - Wrong Token")
|
||||
void wrongToken(){
|
||||
ResponseEntity<ResultVO> result = callApi("123123123123123T&*#$SDF123");
|
||||
Assertions.assertThat( result.getStatusCode() ).isEqualTo( HttpStatus.UNAUTHORIZED );
|
||||
Assertions.assertThat( result.getBody().getResultCode() ).isEqualTo( ResponseCode.AUTH_ERROR.getCode() );
|
||||
Assertions.assertThat( result.getBody().getResultMessage() ).isEqualTo( ResponseCode.AUTH_ERROR.getMessage() );
|
||||
}
|
||||
String getToken(){
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
Map<String,Object> params = new HashMap<>();
|
||||
params.put("id","admin");
|
||||
params.put("password","1");
|
||||
params.put("userSe","USR");
|
||||
|
||||
HttpEntity request = new HttpEntity(params,headers);
|
||||
TestRestTemplate rest = new TestRestTemplate();
|
||||
|
||||
ResponseEntity<HashMap> res = rest.exchange(this.SERVER_URL + "/uat/uia/actionLoginJWT.do", HttpMethod.POST,request , HashMap.class);
|
||||
assertThat( res.getStatusCode() ).isEqualTo( HttpStatus.OK );
|
||||
|
||||
HashMap<String,Object> body = (HashMap<String,Object>) res.getBody();
|
||||
assertThat( body.get("jToken") ).isNotNull();
|
||||
assertThat( body.get("resultCode") ).isEqualTo("200");
|
||||
assertThat( body.get("resultMessage") ).isEqualTo("성공 !!!");
|
||||
String token = body.get("jToken").toString();
|
||||
return token;
|
||||
}
|
||||
ResponseEntity<ResultVO> callApi(String token){
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Authorization", token);
|
||||
HttpEntity request = new HttpEntity(headers);
|
||||
TestRestTemplate rest = new TestRestTemplate();
|
||||
|
||||
return rest.exchange(this.SERVER_URL + "/uat/esm/jwtAuthAPI.do", HttpMethod.POST, request,ResultVO.class);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue