설정 및 파일 위치 변경

This commit is contained in:
kimre
2022-05-30 15:52:40 +09:00
parent 5077696e46
commit acbd7d1352
14 changed files with 172 additions and 237 deletions

View File

@@ -157,7 +157,7 @@ public class LoginController {
loginSuccessHandler.process(request, response, authentication);
// access 토큰 생성
jwtSvc.generatePrivateToken(response, authentication, true);
jwtSvc.generatePrivateToken(response, authentication);
String nextUrl = getReturnUrl(request, response);
logger.debug("login SUCCESS - nextUrl = [{}]", nextUrl);
@@ -174,7 +174,7 @@ public class LoginController {
rCode = loginSuccessHandler.process(request, response, authentication);
// access 토큰 생성
jwtSvc.generatePrivateToken(response, authentication, true);
jwtSvc.generatePrivateToken(response, authentication);
String nextUrl = getReturnUrl(request, response);
logger.debug("login SUCCESS - nextUrl = [{}]", nextUrl);

View File

@@ -42,29 +42,6 @@ public class JwtAuthCookieFilter extends JwtAuthFilter {
return token;
}
@Override
public String getRefreshToken(HttpServletRequest request) {
String payload = null, signature = null;
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
// 1. 쿠키에서 jwt 토큰 header.paload 부분 읽기
if (jwtProps.getPart3().equals(cookie.getName())) {
payload = cookie.getValue();
}
// 2. 쿠키에서 jwt 토큰 signature 부분 읽기
else if (jwtProps.getPart4().equals(cookie.getName())) {
signature = cookie.getValue();
}
}
}
if (cookies == null || payload == null || signature == null) {
return null;
}
String token = payload + "." + signature;
return token;
}
@Override
public void onValidateSuccess(HttpServletRequest request, HttpServletResponse response, Claims claims) {
// 토큰 업데이트 - Sliding Sessions

View File

@@ -1,6 +1,7 @@
package kr.co.uplus.ez.common.jwt;
import static kr.co.uplus.ez.config.SecurityConfig.LOGIN_API_URL;
import static kr.co.uplus.ez.config.SecurityConfig.PUBLIC_API_URL;
import java.io.IOException;
import java.util.Map;
@@ -15,7 +16,6 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.impl.TextCodec;
@@ -32,22 +32,31 @@ public abstract class JwtAuthFilter extends OncePerRequestFilter {
}
public abstract String getToken(HttpServletRequest request);
public abstract String getRefreshToken(HttpServletRequest request);
public abstract void onValidateSuccess(HttpServletRequest request, HttpServletResponse response, Claims claims);
public abstract void onValidateException(HttpServletRequest request, HttpServletResponse response, JwtException exception);
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
//if (WebUtils.isResourceRequest(request) || WebUtils.isMatchedUriPattern(request, PUBLIC_API_URL, LOGIN_API_URL)) {
if (WebUtils.isResourceRequest(request) || WebUtils.isMatchedUriPattern(request, LOGIN_API_URL)) {
if (WebUtils.isResourceRequest(request) || WebUtils.isMatchedUriPattern(request, "/login",
"/api/v1/bo/login/**",
"/v2/api-docs",
"/swagger-resources",
"/swagger-resources/**",
"/configuration/ui",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**",
"/v3/api-docs/**",
"/swagger-ui/**",
"/")) {
chain.doFilter(request, response);
return;
}
// 쿠키에서 토큰 추출 (client - server token)
String token = getToken(request);
String log = request.getRequestURL().toString();
if(token == null) {
chain.doFilter(request, response); // go to the next filter in the filter chain
return;
@@ -76,35 +85,9 @@ public abstract class JwtAuthFilter extends OncePerRequestFilter {
// 6. 사용자 인증 처리 (Now, user is authenticated)
SecurityContextHolder.getContext().setAuthentication(auth);
}
}catch(JwtException e) {
if (e instanceof ExpiredJwtException) {
String reToken = getRefreshToken(request);
try {
// 4. 토큰 검증
claims = Jwts.parser()
.setSigningKey(TextCodec.BASE64.decode(jwtProps.getKeyString()))
.parseClaimsJws(reToken)
.getBody();
String subject = claims.getSubject();
if(subject != null) {
onValidateSuccess(request, response, claims);
return;
}else {
throw new JwtException(null);
}
}catch(JwtException e1) {
// 쿠키 삭제
onValidateException(request, response, e1);
}
}
else {
WebUtils.responseJson(response, HttpServletResponse.SC_UNAUTHORIZED);
onValidateException(request, response, e);
}
//onValidateException(request, response, e);
//throw e;
}catch (Exception e) {
}
catch(JwtException e) {
onValidateException(request, response, e);
throw e;
}

View File

@@ -35,10 +35,4 @@ public class JwtAuthHeaderFilter extends JwtAuthFilter {
public void onValidateException(HttpServletRequest request, HttpServletResponse response, JwtException exception) {
}
@Override
public String getRefreshToken(HttpServletRequest request) {
return "";
}
}

View File

@@ -0,0 +1,41 @@
package kr.co.uplus.ez.common.jwt;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import kr.co.uplus.ez.common.data.Const;
import kr.co.uplus.ez.common.utils.WebUtils;
import kr.co.uplus.ez.config.SecurityConfig;
public class JwtExceptionFilter extends OncePerRequestFilter {
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
}
catch (JwtException e) {
if (WebUtils.isJwtTokenExist(request) && WebUtils.isMatchedUriPattern(request, SecurityConfig.REST_API_URLS)) {
if (e instanceof ExpiredJwtException)
WebUtils.responseJson(response, Const.SESSION_EXPIRED);
else
WebUtils.responseJson(response, HttpServletResponse.SC_UNAUTHORIZED);
}
else {
throw e;
}
}
catch (Exception e) {
throw e;
}
}
}

View File

@@ -14,8 +14,6 @@ public class JwtProperties {
private int privateTokenExpiration; // seconds
private String part1;
private String part2;
private String part3;
private String part4;
private int accessTokenExpiration; // seconds
private int refreshTokenExpiration; // seconds

View File

@@ -19,7 +19,6 @@ import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.TextCodec;
import kr.co.uplus.ez.api.login.LoginMapper;
import kr.co.uplus.ez.api.login.dto.AuthUser;
@Service
@@ -49,52 +48,6 @@ public class JwtService {
setTokenToCookie(response, token);
}
public void generateReSetToken(HttpServletResponse response, Authentication auth, boolean tokenFlag) {
if(tokenFlag) {
// AccessToken
Claims accessClaims = coreClaims(auth, jwtProps.getAccessTokenExpiration());
// 필요하면 다른 정보 추가
JwtData data = new JwtData();
data.setInfo("추가 claim 정보");
accessClaims.put("data", data);
String accessToken = generateToken(accessClaims);
// 쿠키에 토큰 추가 - 보안 강화
setTokenToCookie(response, accessToken);
}
}
public void generatePrivateToken(HttpServletResponse response, Authentication auth, boolean tokenFlag) {
if(tokenFlag) {
// AccessToken
Claims accessClaims = coreClaims(auth, jwtProps.getAccessTokenExpiration());
Claims refreshClaims = coreClaims(auth, jwtProps.getRefreshTokenExpiration());
// 필요하면 다른 정보 추가
JwtData data = new JwtData();
data.setInfo("추가 claim 정보");
accessClaims.put("data", data);
refreshClaims.put("data", data);
String accessToken = generateToken(accessClaims);
String refreshToken = generateToken(refreshClaims);
// 쿠키에 토큰 추가 - 보안 강화
setTokenToCookie(response, accessToken);
setRefreshTokenToCookie(response, refreshToken);
// RefreshToken 저장.
LoginMapper loginMapper = sqlSessionMaster.getMapper(LoginMapper.class);
AuthUser authUser = new AuthUser();
authUser.setOprtrId(auth.getName());
authUser.setRFrshTkn(refreshToken);
authUser.setRefreshTokenExpiration(jwtProps.getRefreshTokenExpiration());
loginMapper.updateAdmUser(authUser);
}
}
private Claims coreClaims(Authentication auth, int expire) {
String subject = auth.getName();
DateTime now = DateTime.now();
@@ -137,23 +90,6 @@ public class JwtService {
response.addCookie(part2);
}
private void setRefreshTokenToCookie(HttpServletResponse response, String refreshToken) {
int idx = refreshToken.lastIndexOf(".");
String payload = refreshToken.substring(0, idx);
String signature = refreshToken.substring(idx+1);
// header.paload 부분만 일반 쿠키에 저장 - JS로 읽기 가능
Cookie part3 = new Cookie(jwtProps.getPart3(), payload);
part3.setPath("/");
response.addCookie(part3);
// signature 부분만 httpOnly 쿠키에 저장 - JS로 읽기 불가능
Cookie part4 = new Cookie(jwtProps.getPart4(), signature);
part4.setHttpOnly(true);
part4.setPath("/");
response.addCookie(part4);
}
public void destroyPrivateToken(HttpServletRequest request, HttpServletResponse response) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
@@ -168,8 +104,8 @@ public class JwtService {
public void updatePrivateToken(HttpServletResponse response, Claims claims) {
DateTime now = DateTime.now();
//Date expiration = now.plusSeconds(jwtProps.getPrivateTokenExpiration()).toDate();
Date expiration = now.plusSeconds(jwtProps.getAccessTokenExpiration()).toDate();
Date expiration = now.plusSeconds(jwtProps.getPrivateTokenExpiration()).toDate();
// Date expiration = now.plusSeconds(jwtProps.getAccessTokenExpiration()).toDate();
claims.setIssuedAt(now.toDate()).setExpiration(expiration);
String token = generateToken(claims);
@@ -190,12 +126,11 @@ public class JwtService {
return generateToken(claims);
}
// public PublicToken generatePublicToken(Authentication auth) {
// String access = accessToken(auth);
// String refresh = refreshToken(auth);
// return new PublicToken(access, refresh);
// }
public PublicToken generatePublicToken(Authentication auth) {
String access = accessToken(auth);
String refresh = refreshToken(auth);
return new PublicToken(access, refresh);
}
public String accessToken(Authentication auth) {
Claims claims = coreClaims(auth, jwtProps.getAccessTokenExpiration());

View File

@@ -1,13 +1,13 @@
//package kr.co.uplus.ez.common.jwt;
//
//import lombok.AllArgsConstructor;
//import lombok.Data;
//import lombok.NoArgsConstructor;
//
//@Data
//@NoArgsConstructor
//@AllArgsConstructor
//public class PublicToken {
// private String accessToken;
// private String refreshToken;
//}
package kr.co.uplus.ez.common.jwt;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PublicToken {
private String accessToken;
private String refreshToken;
}

View File

@@ -23,26 +23,26 @@ import kr.co.uplus.ez.api.login.LoginSuccessHandler;
import kr.co.uplus.ez.common.data.ConfigProps;
import kr.co.uplus.ez.common.jwt.JwtAuthCookieFilter;
import kr.co.uplus.ez.common.jwt.JwtAuthHeaderFilter;
//import kr.co.uplus.ez.common.jwt.JwtExceptionFilter;
import kr.co.uplus.ez.common.jwt.JwtExceptionFilter;
import kr.co.uplus.ez.common.jwt.JwtProperties;
import kr.co.uplus.ez.common.security.VueStaticFilter;
import kr.co.uplus.ez.common.security.XssFilter;
import kr.co.uplus.ez.config.filter.VueStaticFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final String LOGIN_FORM_URL = "/login/**";
public static final String LOGIN_API_URL = "/api/v1/bo/login/*";
public static final String LOGIN_API_URL = "/api/v1/bo/login";
public static final String LOGIN_FAIL_URL = "/login?error=true";
public static final String LOGIN_SUCC_URL = "/";
private static final String API_URL = "/api/**";
public static final String TEST_PERMIT_URL = "/api/**/**";
public static final String PUBLIC_API_URL = "/api/v1/bo/**"; // 내부에서 인증없이 호출하는 API
public static final String[] REST_API_URLS = {API_URL};
public static final String[] REST_API_URLS = {API_URL, TEST_PERMIT_URL};
private static final String[] PERMIT_URL_ARRAY = {
"/login",
"/api/v1/bo/login/*",
"/api/v1/bo/login/**",
"/v2/api-docs",
"/swagger-resources",
"/swagger-resources/**",
@@ -56,12 +56,23 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
};
private static final String[] AUTH_URL_ARRAY = {
"/api/v1/bo/login/*",
"/api/v1/bo/comm/**",
"/api/v1/bo/custMgt/**",
"/api/v1/bo/sysMgt/**",
"/api/v1/bo/channelMgt/**",
"/api/v1/bo/sendNumMgt/**",
"/api/v1/bo/mntrng/**",
"/api/v1/bo/riskMgt/sendNum/**",
"/api/v1/bo/stats/**"
};
public static final String LOGIN_ID_PARAM = "userId";
@SuppressWarnings("unused")
private static final String LOGIN_PWD_PARAM = "userPwd";
public static final String AUTH_USER = "authUser";
@Autowired
private UserDetailsService userDetailsService;
@Autowired
@@ -69,6 +80,11 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtProperties jwtProps;
// @Autowired
// private AuthService authService;
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
@@ -84,7 +100,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
public void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(new VueStaticFilter(), UsernamePasswordAuthenticationFilter.class) // Vue에서 호출시 화면관련 URL은 / forward
.addFilterBefore(new XssFilter(cprops), UsernamePasswordAuthenticationFilter.class)
// .addFilterBefore(new XssFilter(cprops), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JwtExceptionFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JwtAuthHeaderFilter(jwtProps), UsernamePasswordAuthenticationFilter.class);
@@ -99,12 +116,12 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
.headers().frameOptions().disable()
.and()
.exceptionHandling()
.authenticationEntryPoint(new MixedAuthenticationEntryPoint(LOGIN_FORM_URL, PUBLIC_API_URL))
.authenticationEntryPoint(new MixedAuthenticationEntryPoint(LOGIN_FORM_URL, "/api/v1/bo/login/**"))
.and()
.authorizeRequests()
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll() // CORS preflight 요청은 인증처리를 하지 않도록 설정
.antMatchers(PERMIT_URL_ARRAY).permitAll()
.antMatchers(API_URL).authenticated()
.antMatchers(AUTH_URL_ARRAY).authenticated()
.anyRequest().authenticated();
}

View File

@@ -1,4 +1,4 @@
package kr.co.uplus.ez.common.security;
package kr.co.uplus.ez.config.filter;
import java.io.IOException;

View File

@@ -1,19 +1,19 @@
package kr.co.uplus.ez.config.filter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class WebFilterConfig {
@Bean
public FilterRegistrationBean<XssPreventFilter> getFilterRegistrationBean() throws Exception{
FilterRegistrationBean<XssPreventFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new XssPreventFilter());
registrationBean.setOrder(1);
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}
//package kr.co.uplus.ez.config.filter;
//
//import org.springframework.boot.web.servlet.FilterRegistrationBean;
//import org.springframework.context.annotation.Bean;
//import org.springframework.context.annotation.Configuration;
//
//@Configuration
//public class WebFilterConfig {
//
// @Bean
// public FilterRegistrationBean<XssPreventFilter> getFilterRegistrationBean() throws Exception{
// FilterRegistrationBean<XssPreventFilter> registrationBean = new FilterRegistrationBean<>();
// registrationBean.setFilter(new XssPreventFilter());
// registrationBean.setOrder(1);
// registrationBean.addUrlPatterns("/*");
// return registrationBean;
// }
//
//}

View File

@@ -1,41 +1,41 @@
package kr.co.uplus.ez.config.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.util.AntPathMatcher;
import kr.co.uplus.ez.common.xss.XssPreventer;
import lguplus.security.vulner.FilterServlet;
public class XssPreventFilter extends FilterServlet {
public FilterConfig filterConfig;
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
public void destroy() {
this.filterConfig = null;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException {
String path = ((HttpServletRequest) request).getServletPath();
if(!XssPreventer.EXCLUDE_URLS.stream()
.anyMatch(e -> new AntPathMatcher().match(e, path))) {
chain.doFilter(new XssPreventWrapper((HttpServletRequest) request), response);
} else {
chain.doFilter(request, response);
}
}
}
//package kr.co.uplus.ez.config.filter;
//
//import java.io.IOException;
//
//import javax.servlet.FilterChain;
//import javax.servlet.FilterConfig;
//import javax.servlet.ServletException;
//import javax.servlet.ServletRequest;
//import javax.servlet.ServletResponse;
//import javax.servlet.http.HttpServletRequest;
//
//import org.springframework.util.AntPathMatcher;
//
//import kr.co.uplus.ez.common.xss.XssPreventer;
//import lguplus.security.vulner.FilterServlet;
//
//public class XssPreventFilter extends FilterServlet {
//
// public FilterConfig filterConfig;
//
// public void init(FilterConfig filterConfig) throws ServletException {
// this.filterConfig = filterConfig;
// }
//
// public void destroy() {
// this.filterConfig = null;
// }
//
// @Override
// public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
// throws ServletException, IOException {
// String path = ((HttpServletRequest) request).getServletPath();
// if(!XssPreventer.EXCLUDE_URLS.stream()
// .anyMatch(e -> new AntPathMatcher().match(e, path))) {
// chain.doFilter(new XssPreventWrapper((HttpServletRequest) request), response);
// } else {
// chain.doFilter(request, response);
// }
// }
//
//}

View File

@@ -29,10 +29,8 @@ app.props:
private-token-expiration: 1800
part1: JwtPart1
part2: JwtPart2
part3: JwtPart3
part4: JwtPart4
access-token-expiration: 1800
refresh-token-expiration: 259200
access-token-expiration: 180
refresh-token-expiration: 1800
header: Authorization
prefix: "Bearer "

View File

@@ -14,8 +14,6 @@
, T1.HP_NO
, T1.AUT_CD
, T1.AUTHCHR_FAIL_CNT
, T1.RFRSH_TKN
, T1.RFRSH_TKN_DT
, T1.LAST_LOGIN_DT
, T1.LOGIN_FAIL_CNT
, T1.PWD_CHG_DT
@@ -196,12 +194,6 @@
<if test="authchrFailCnt != null and authchrFailCnt != ''">
, AUTHCHR_FAIL_CNT = #{authchrFailCnt}
</if>
<if test="rFrshTkn != null and rFrshTkn != ''">
, RFRSH_TKN = #{rFrshTkn}
</if>
<if test="refreshTokenExpiration != null and refreshTokenExpiration != ''">
, RFRSH_TKN_DT = DATE_ADD(NOW(), INTERVAL #{refreshTokenExpiration} SECOND)
</if>
<if test="lastLoginDt != null and lastLoginDt != ''">
, LAST_LOGIN_DT = NOW()
</if>