테스트를 위해 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

















댓글 ( 4)
댓글 남기기