feat: spring security 적용

dev
gitea-관리자 12 months ago
parent 99653d60ef
commit 8d8ae287e3

@ -1,7 +1,7 @@
package kr.xit.core.biz.web;
import egovframework.com.cmm.LoginVO;
import egovframework.com.cmm.jwt.config.EgovJwtTokenUtil;
import egovframework.com.jwt.EgovJwtTokenUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;

@ -48,6 +48,10 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>

@ -1,5 +1,10 @@
package egovframework.com.cmm.jwt.config;
package egovframework.com.jwt;
import egovframework.com.cmm.LoginVO;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.security.Key;
@ -9,19 +14,12 @@ import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import kr.xit.core.consts.ErrorCode;
import kr.xit.core.exception.BizRuntimeException;
import kr.xit.core.spring.config.properties.JwtProperties;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import egovframework.com.cmm.LoginVO;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import kr.xit.core.consts.ErrorCode;
import kr.xit.core.exception.BizRuntimeException;
//security 관련 제외한 jwt util 클래스
@Component
@ -36,6 +34,25 @@ public class EgovJwtTokenUtil implements Serializable{
this.jwtProp = jwtProperties;
}
public String getUserIdFromToken(String token) {
Claims claims = getClaimFromToken(token);
return claims.get("id").toString();
}
public String getUserSeFromToken(String token) {
Claims claims = getClaimFromToken(token);
return claims.get("userSe").toString();
}
public String getInfoFromToken(String type, String token) {
Claims claims = getClaimFromToken(token);
return claims.get(type).toString();
}
public Claims getClaimFromToken(String token) {
final Claims claims = getAllClaimsFromToken(token);
return claims;
}
//retrieve username from jwt token
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);

@ -0,0 +1,53 @@
package egovframework.com.jwt;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import kr.xit.core.model.ApiResponseDTO;
import kr.xit.core.support.utils.JsonUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
/**
* 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);
// 403 에러
@SuppressWarnings("rawtypes")
ApiResponseDTO apiResponseDTO = ApiResponseDTO.builder()
.success(false)
.code(String.valueOf(HttpStatus.FORBIDDEN.value()))
.message("인가된 사용자가 아닙니다")
.build();
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON.toString());
response.setCharacterEncoding("UTF-8");
response.getWriter().println(JsonUtils.toJson(apiResponseDTO));
}
}

@ -0,0 +1,89 @@
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.support.utils.Checks;
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
*/
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));
if(Checks.isEmpty(jwtToken)){
chain.doFilter(req, res);
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) {
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,12 +1,4 @@
package egovframework.com.cmm.jwt.config;
import javax.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
package egovframework.com.jwt;
import egovframework.com.cmm.LoginVO;
import egovframework.com.cmm.util.EgovStringUtil;
@ -14,7 +6,12 @@ import egovframework.com.cmm.util.EgovUserDetailsHelper;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
import javax.servlet.http.HttpServletRequest;
import kr.xit.core.consts.Constants;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@RequiredArgsConstructor
@Component

@ -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,95 @@
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;
private static final String[] ORIGINS_WHITELIST = {
"http://localhost:3000",
"http://localhost:8081",
};
@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();
}
}

@ -81,6 +81,9 @@ public class WebMvcConfig implements WebMvcConfigurer {
private final CorsProperties corsProperties;
@Value("${app.spring.security.white-list}")
private String[] EXCLUDE_AUTHS;
/**
* MappingJackson2XmlHttpMessageConverter
* @param converters
@ -97,12 +100,7 @@ public class WebMvcConfig implements WebMvcConfigurer {
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthentificationInterceptor())
.addPathPatterns("/**/*")
.excludePathPatterns(
"/api/core/*",
"/swagger-ui.html",
"/swagger-ui/*",
"/api-docs/*"
);
.excludePathPatterns(EXCLUDE_AUTHS);
registry.addInterceptor(localeChangeInterceptor());
}

@ -1,16 +1,12 @@
package kr.xit.core.spring.config.properties;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@Getter
@Setter
@NoArgsConstructor
@ConfigurationProperties //(prefix = "app.token")
//@ConstructorBinding
@ConfigurationProperties(prefix = "app.token")
public class JwtProperties {
private String typ;

@ -1,8 +1,8 @@
package kr.xit.core.spring.util;
import com.fasterxml.jackson.databind.ObjectMapper;
import egovframework.com.cmm.jwt.config.EgovJwtTokenUtil;
import egovframework.com.cmm.jwt.config.JwtVerification;
import egovframework.com.jwt.EgovJwtTokenUtil;
import egovframework.com.jwt.JwtVerification;
import kr.xit.core.spring.config.properties.CorsProperties;
import kr.xit.core.spring.config.support.ApplicationContextProvider;
import lombok.AccessLevel;

@ -3,6 +3,21 @@
#-----------------------------------------------------------------------
app:
# spring security 사용여부
spring:
security:
white-list:
/,
/login/**,
/api/**,
/batch/**,
/auth/login-jwt,
/auth/login,
/api-docs/**,
/swagger-resources,
/swagger-ui.html,
/swagger-ui/**
# 암호화 알고리즘
encrypt:
alg: SHA-256

Loading…
Cancel
Save