테스트를 위해 security-context.xml 에서 페이지 권한 설정을 permitAll 로 한다.
<http use-expressions="true"> <intercept-url pattern="/sample/**" access="permitAll"/> <intercept-url pattern="/ajaxTest/**" access="permitAll"/> <intercept-url pattern="/login/loginForm.do" access="permitAll"/> <intercept-url pattern="/admin/**" access="hasRole('ADMIN')"/> <intercept-url pattern="/login" access="hasRole('USER')" /> <intercept-url pattern="/" access="permitAll"/> <intercept-url pattern="/user/**" access="hasAnyRole('USER, ADMIN')" /> <intercept-url pattern="/passwordEncoderTest" access="permitAll"/> <form-login login-page="/login/loginForm.do" default-target-url="/" authentication-failure-url="/login/loginForm.do?error" username-parameter="id" password-parameter="password" /> <logout logout-url="/logout" logout-success-url="/" /> <access-denied-handler error-page="/login/accessDenied.do"/> <remember-me key="REMEMBER_ME_KEY"/> </http>
SecurityAjaxController
컨트롤 영역이다. ajax 를 데이터를 받는 것으로 특별한것이 없다.
/** * <pre> * 1. 패키지명 : net.macaronics.web.controller * 2. 타입명 : SecurityAjaxController.java * 3. 작성일 : 2017. 12. 1. 오후 6:12:29 * 4. 저자 : 최준호 * * </pre> * */ package net.macaronics.web.controller; import java.io.PrintWriter; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import net.macaronics.web.domain.UserVO; @Controller @RequestMapping("/ajaxTest") public class SecurityAjaxController { private static Logger logger =LoggerFactory.getLogger(SecurityAjaxController.class); @RequestMapping(value="/ajaxCsrfForm", method=RequestMethod.GET ) public String ajaxCsrfTestForm() throws Exception{ return "ajax/csrfTest"; } @RequestMapping(value="/ajaxCsrf", method=RequestMethod.POST) public void ajaxCsrfTest(HttpServletResponse response ,String username, String password, String hobby) throws Exception{ logger.info("넘겨온 값 {} , {} , {} " , username, password, hobby); PrintWriter out =response.getWriter(); out.println("success"); } }
스프링 시큐리티에서 제공하는 csrf 를 view 영역의 meta 의 content 에 el 태그 _csrf.token 과 _csrf.headerName 를 사용해서 데이터를 뿌려주자.
<meta id="_csrf" name="_csrf" content="${_csrf.token}"/> <!-- default header name is X-CSRF-TOKEN --> <meta id="_csrf_header" name="_csrf_header" content="${_csrf.headerName}"/>
html 소스를 보면 다음과 같은 형식으로 출력된다.
<meta id="_csrf" name="_csrf" content="b3309bf5-37b8-47bf-a7c3-e3b4ee97ccfc"> <!-- default header name is X-CSRF-TOKEN --> <meta id="_csrf_header" name="_csrf_header" content="X-CSRF-TOKEN">
메타 태그에 있는 csrf 토그과 header 를 다음과 같은 방법으로 javascript 의 변수에 저장하자.
var token = $("meta[name='_csrf']").attr("content"); var header = $("meta[name='_csrf_header']").attr("content");
ajax 사용시 beforSend 를 사용해서 header 와 csrf 토크값을 던져 주면 된다.
beforeSend : function(xhr){
xhr.setRequestHeader(header, token);
},
전체 소스는 다음과 같다.
csrfTest.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta id="_csrf" name="_csrf" content="${_csrf.token}"/> <!-- default header name is X-CSRF-TOKEN --> <meta id="_csrf_header" name="_csrf_header" content="${_csrf.headerName}"/> <script src="//code.jquery.com/jquery.min.js"></script> <script src="//code.jquery.com/ui/1.11.4/jquery-ui.min.js"></script> <title>ajax csrf Test</title> </head> <body> <form id="form1"> <input type="text" name="username"> <input type="text" name="password"> <input type="text" name="hobby"> <button id="btnAjax" type="button" >전송</button> </form> <div id="result"> </div> <script> var token = $("meta[name='_csrf']").attr("content"); var header = $("meta[name='_csrf_header']").attr("content"); $(function() { $("#btnAjax").click(function(){ var params=jQuery("#form1").serialize(); $.ajax({ url:"/ajaxTest/ajaxCsrf", type:"post", data:params, beforeSend : function(xhr){ xhr.setRequestHeader(header, token); }, success:function(result){ if($.trim(result)=='success'){ $("#result").html(params); } }, error:function(result){ alert("실패"); } }); }); }); </script> </body> </html>
테스트 성공시 화면
스프링 시큐리티에서 csrf 토큰을 제거한 후 전송해 보자.
$("#btnAjax").click(function(){ var params=jQuery("#form1").serialize(); $.ajax({ url:"/ajaxTest/ajaxCsrf", type:"post", data:params, /* beforeSend : function(xhr){ xhr.setRequestHeader(header, token); }, */ success:function(result){ if($.trim(result)=='success'){ $("#result").html(params); } }, error:function(result){ alert("실패"); } }); });
csrf 토큰을 보내는 beforeSend 를 제거하고 전송하면 콘솔 창에 다음과 같은 에러 메시지가 나타난다.
******* Macaroncis AOP Annotation.beforeTargetMethod executed. => INFO : config.aop.LogAdvice - MVC 영역 - Controller : , Type - net.macaronics.web.controller.SecurityAjaxController INFO : config.aop.LogAdvice - 클래스 명 - SecurityAjaxController, 메서드 명 - ajaxCsrfTestForm INFO : config.aop.LogAdvice - 파라미터 값 : [] ERROR: config.error.CommonException - commonErrorMessage() , Request method 'POST' not supported ERROR: config.error.CommonException - @ExceptionHandler(Exception.class) -- ★ ★ ★ 에러 메시지 시작 ★★★ getMessage() : Request method 'POST' not supported ★ 에러 시작 클래스 이름 className : org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping ★ 에러 시작 메서드 이름(methodName) : handleNoMatch, line : 225 STACKTRACE - className : org.springframework.web.servlet.handler.AbstractHandlerMethodMapping, 메서드 이름(methodName) : lookupHandlerMethod, line : 368 STACKTRACE - className : org.springframework.web.servlet.handler.AbstractHandlerMethodMapping, 메서드 이름(methodName) : getHandlerInternal, line : 308 STACKTRACE - className : org.springframework.web.servlet.handler.AbstractHandlerMethodMapping, 메서드 이름(methodName) : getHandlerInternal, line : 61 STACKTRACE - className : org.springframework.web.servlet.handler.AbstractHandlerMapping, 메서드 이름(methodName) : getHandler, line : 351 STACKTRACE - className : org.springframework.web.servlet.DispatcherServlet, 메서드 이름(methodName) : getHandler, line : 1131 STACKTRACE - className : org.springframework.web.servlet.DispatcherServlet, 메서드 이름(methodName) : doDispatch, line : 936 STACKTRACE - className : org.springframework.web.servlet.DispatcherServlet, 메서드 이름(methodName) : doService, line : 897 STACKTRACE - className : org.springframework.web.servlet.FrameworkServlet, 메서드 이름(methodName) : processRequest, line : 970 STACKTRACE - className : org.springframework.web.servlet.FrameworkServlet, 메서드 이름(methodName) : doPost, line : 872 STACKTRACE - className : javax.servlet.http.HttpServlet, 메서드 이름(methodName) : service, line : 648 STACKTRACE - className : org.springframework.web.servlet.FrameworkServlet, 메서드 이름(methodName) : service, line : 846 STACKTRACE - className : javax.servlet.http.HttpServlet, 메서드 이름(methodName) : service, line : 729 STACKTRACE - className : org.apache.catalina.core.ApplicationFilterChain, 메서드 이름(methodName) : internalDoFilter, line : 292 STACKTRACE - className : org.apache.catalina.core.ApplicationFilterChain, 메서드 이름(methodName) : doFilter, line : 207 STACKTRACE - className : org.apache.tomcat.websocket.server.WsFilter, 메서드 이름(methodName) : doFilter, line : 52 STACKTRACE - className : org.apache.catalina.core.ApplicationFilterChain, 메서드 이름(methodName) : internalDoFilter, line : 240 STACKTRACE - className : org.apache.catalina.core.ApplicationFilterChain, 메서드 이름(methodName) : doFilter, line : 207 STACKTRACE - className : org.apache.catalina.core.ApplicationDispatcher, 메서드 이름(methodName) : invoke, line : 716 STACKTRACE - className : org.apache.catalina.core.ApplicationDispatcher, 메서드 이름(methodName) : processRequest, line : 466 STACKTRACE - className : org.apache.catalina.core.ApplicationDispatcher, 메서드 이름(methodName) : doForward, line : 391 STACKTRACE - className : org.apache.catalina.core.ApplicationDispatcher, 메서드 이름(methodName) : forward, line : 318 STACKTRACE - className : org.springframework.security.web.firewall.RequestWrapper$FirewalledRequestAwareRequestDispatcher, 메서드 이름(methodName) : forward, line : 154
소스 : https://github.com/braverokmc79/spring_simple_blog
댓글 ( 0)
댓글 남기기