본문 바로가기
개발/Web

[Spring Framework] CSRF 적용

by ynzu🤍 2021. 12. 14.
반응형

지난 번에 spring boot에 csrf를 적용하는 포스팅을 올렸었는데  그냥 spring과 적용하는 방법이 달라 또 포스팅을 올려본다!

 

[Spring Boot] spring security - CSRF 적용 , +) ajax csrf 적용

1. gradle 혹은 maven에 'spring-boot-starter-security' 추가 implementation 'org.springframework.boot:spring-boot-starter-security' org.springframework.boot spring-boot-starter-security 2.5.5 Spring S..

ynzu-dev.tistory.com

 

  • web.xml에 dispactcher servlet을 등록한다.
<servlet> 
	<servlet-name>appServlet</servlet-name> 
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 
	<init-param> 
		<param-name>contextConfigLocation</param-name> 
		<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value> 
	</init-param> 
	<load-on-startup>1</load-on-startup> 
</servlet> 
<servlet-mapping> 
	<servlet-name>appServlet</servlet-name> 
	<url-pattern>*.do</url-pattern> 
</servlet-mapping>

 

  • servlet-context.xml에 csrf hidden이 form에 자동으로 추가해주는 class를 빈(bean)으로 등록한다
<beans:bean id="requestDataValueProcessor" class="com.xxxx.framework.processor.CSRFRequestDataValueProcessor"/>

 

  • CSRFRequestDataValueProcessor.java

- csrf hidden 데이터를 자동 추가해주는 클래스, RequestDataValueProcessor를 implements해야 한다.

public class CSRFRequestDataValueProcessor implements RequestDataValueProcessor{ 
	@Override 
	public Map<String, String> getExtraHiddenFields(HttpServletRequest request) { 
		Map<String, String> hiddenFields =  new HashMap<String, String>(); 
		HttpSession session = request.getSession(false); 
		if(session == null) { 
			return hiddenFields; 
		}		 
		String token = UUID.randomUUID().toString().replaceAll("-", ""); 		 
		session.setAttribute(FrameworkConstants.CSRF_TOKEN_NAME, token); 
		hiddenFields.put(FrameworkConstants.CSRF_PARAM_NAME, token); 
		return hiddenFields; 
	} 
	@Override 
	public String processAction(HttpServletRequest arg0, String arg1) {		 
		return null; 
	} 
	@Override 
	public String processFormFieldValue(HttpServletRequest arg0, String arg1, String arg2, String arg3) {		 
		return null; 
	} 
	@Override 
	public String processUrl(HttpServletRequest arg0, String arg1) {		 
		return null; 
	} 
}

 

  • servlet-context.xml 에 CSRF 체크 interceptor 등록
<mvc:interceptors> 
	<mvc:interceptor> 
		<mvc:mapping path="/**/*.do"/> 
		<beans:bean  class="com.xxx.framework.interceptor.CSRFInterceptor"> 
			<beans:property name="excludeURLs"> 
				<beans:array value-type="java.lang.String"> 
					<beans:value>login.do</beans:value> 
					<beans:value>logout.do</beans:value> 
					<beans:value>view.do</beans:value> 
				</beans:array>		  			 
			</beans:property> 
		</beans:bean > 
	</mvc:interceptor> 
</mvc:interceptors>

- login.do, logout.do, view.do는 csrf를 체크하지 않음!

- csrf를 체크하지 않을 url 혹은 패턴을 등록할 수 있다. 그리고 csrf 인터셉터에서 해당 url은 체크하지 않도록 로직을 짜면 됨!

 

  • CSRFInterceptor.java
public class CSRFInterceptor extends HandlerInterceptorAdapter{ 
	Logger log = Logger.getLogger(this.getClass().getName()); 
	private String excludeURL ; 
	private String[] excludeURLs; 
	@Override 
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 
		boolean result = true; 
		if(request.getMethod() == null) { 
			return result; 
		} 
		if(!request.getMethod().equalsIgnoreCase("post")) { 
			return result; 
		}else { 
			if(!isExcludeURL(request.getRequestURI())) { 
				if(!this.verifyCSRFToken(request)) { 
					this.log.error("Incorrect CSRF value"); 
					response.sendError(HttpServletResponse.SC_FORBIDDEN, "Bad Request"); 
					result = false; 
				}						 
			}					 
		} 
		return result; 
	} 
	private boolean verifyCSRFToken(HttpServletRequest request) { 
		boolean result = true; 
		if(request.getSession().getAttribute(FrameworkConstants.CSRF_TOKEN_NAME) != null) { 
			String sessionToken = (String)request.getSession().getAttribute(FrameworkConstants.CSRF_TOKEN_NAME); 
			String paramToken = request.getParameter(FrameworkConstants.CSRF_PARAM_NAME);			 
			if(paramToken == null || !sessionToken.equals(paramToken)) { 
				this.log.debug("Incorrect CSRF value"); 
				result = false; 
			} 
		}		 
		return result; 
	} 
	@Override 
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 
		super.postHandle(request, response, handler, modelAndView); 
	} 
	@Override 
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 
			throws Exception { 
		super.afterCompletion(request, response, handler, ex); 
	} 
	private boolean isExcludeURL(String url) { 
		boolean result = false; 
		if(url == null) { 
			return true; 
		} 
		url = url.toLowerCase(); 
		for (int i = 0; i < this.excludeURLs.length; i++) {				 
			if (url.indexOf(  this.excludeURLs[i].toLowerCase() ) < 0) { 
				continue; 
			} 
			result = true; 
			break; 
		} 
		return result; 
	} 
	public String getExcludeURL() { 
		return excludeURL; 
	} 
	public void setExcludeURL(String excludeURL) { 
		this.excludeURL = excludeURL; 
	} 
	public String[] getExcludeURLs() { 
		return excludeURLs.clone(); 
	} 
	public void setExcludeURLs(String[] excludeURLs) { 
		if (excludeURLs == null) { 
			this.excludeURLs = new String[0]; 
		} else { 
			this.excludeURLs = Arrays.copyOf(excludeURLs, excludeURLs.length); 
		} 
	}	 
}

 

확실히 spring boot가 훨씬 간단하다.. 너무 편해!

728x90
반응형

댓글