SEC-2325 Added JSP tags for CSRF meta tags and form fields

This commit is contained in:
beamerblvd 2014-02-06 21:56:47 -06:00 committed by Rob Winch
parent 26cee61b98
commit a3e0475998
9 changed files with 457 additions and 6 deletions

View File

@ -3124,8 +3124,7 @@ public class WebSecurityConfig extends
[[csrf-include-csrf-token-form]]
===== Form Submissions
The last step is to ensure that you include the CSRF token in all PATCH, POST, PUT, and DELETE methods. This can be done using the _csrf request attribute to obtain the current CsrfToken. An example of doing this with a JSP is shown below:
The last step is to ensure that you include the CSRF token in all PATCH, POST, PUT, and DELETE methods. One way to approach this is to use the `_csrf` request attribute to obtain the current `CsrfToken`. An example of doing this with a JSP is shown below:
[source,xml]
----
@ -3140,9 +3139,11 @@ The last step is to ensure that you include the CSRF token in all PATCH, POST, P
</form>
----
An easier approach is to use <<the-csrffield-tag,the csrfField tag>> from the Spring Security JSP tag library.
[NOTE]
====
If you are using Spring MVC <form:form> tag or http://www.thymeleaf.org/whatsnew21.html#reqdata[Thymeleaf 2.1+], the `CsrfToken` is automatically included for you if you replace `@EnableWebSecurity` with `@EnableWebMvcSecurity` using the `CsrfRequestDataValueProcessor`.
If you are using Spring MVC `<form:form>` tag or http://www.thymeleaf.org/whatsnew21.html#reqdata[Thymeleaf 2.1+], and you replace `@EnableWebSecurity` with `@EnableWebMvcSecurity`, the `CsrfToken` is automatically included for you (using the `CsrfRequestDataValueProcessor`).
====
[[csrf-include-csrf-token-ajax]]
@ -3162,6 +3163,8 @@ If you using JSON, then it is not possible to submit the CSRF token within an HT
<!-- ... -->
----
Instead of manually creating the meta tags, you can use the simpler <<the-csrfmetatags-tag,csrfMetaTags tag>> from the Spring Security JSP tag library.
You can then include the token within all your Ajax requests. If you were using jQuery, this could be done with the following:
[source,javascript]
@ -5034,6 +5037,88 @@ The permissions are passed to the `PermissionFactory` defined in the application
This tag also supports the `var` attribute, in the same way as the `authorize` tag.
=== The csrfField Tag
If CSRF protection is enabled, this tag inserts a hidden form field with the correct name and value for the CSRF protection token. If CSRF protection is not enabled, this tag outputs nothing.
Normally Spring Security automatically inserts a CSRF form field for any `<form:form>` tags you use, but if for some reason you cannot use `<form:form>`, `csrfField` is a handy replacement.
You should place this tag within an HTML `<form></form>` block, where you would normally place other input fields. Do NOT place this tag within a Spring `<form:form></form:form>` block—Spring Security handles Spring forms automatically.
[source,xml]
----
<form method="post" action="/do/something">
<sec:csrfField />
Name:<br />
<input type="text" name="name" />
...
</form>
----
=== The csrfMetaTags Tag
If CSRF protection is enabled, this tag inserts meta tags containing the CSRF protection token form field and header names and CSRF protection token value. These meta tags are useful for employing CSRF protection within JavaScript in your applications.
You should place `csrfMetaTags` within an HTML `<head></head>` block, where you would normally place other meta tags. Once you use this tag, you can access the form field name, header name, and token value easily using JavaScript. JQuery is used in this example to make the task easier.
[source,xml]
----
<!DOCTYPE html>
<html>
<head>
<title>CSRF Protected JavaScript Page</title>
<meta name="description" content="This is the description for this page" />
<sec:csrfMetaTags />
<script type="text/javascript" language="javascript">
var csrfParameter = $("meta[name='_csrf_parameter']").attr("content");
var csrfHeader = $("meta[name='_csrf_header']").attr("content");
var csrfToken = $("meta[name='_csrf']").attr("content");
// using XMLHttpRequest directly to send an x-www-form-urlencoded request
var ajax = new XMLHttpRequest();
ajax.open("POST", "http://www.example.org/do/something", true);
ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded data");
ajax.send(csrfParameter + "=" + csrfToken + "&name=John&...");
// using XMLHttpRequest directly to send a non-x-www-form-urlencoded request
var ajax = new XMLHttpRequest();
ajax.open("POST", "http://www.example.org/do/something", true);
ajax.setRequestHeader(csrfHeader, csrfToken);
ajax.send("...");
// using JQuery to send an x-www-form-urlencoded request
var data = {};
data[csrfParameter] = csrfToken;
data["name"] = "John";
...
$.ajax({
url: "http://www.example.org/do/something",
type: "POST",
data: data,
...
});
// using JQuery to send a non-x-www-form-urlencoded request
var headers = {};
headers[csrfHeader] = csrfToken;
$.ajax({
url: "http://www.example.org/do/something",
type: "POST",
headers: headers,
...
});
<script>
</head>
<body>
...
</body>
</html>
----
If CSRF protection is not enabled, `csrfMetaTags` outputs nothing.
[[jaas]]
== Java Authentication and Authorization Service (JAAS) Provider

View File

@ -1,3 +1,19 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.taglibs;
import org.apache.commons.logging.Log;
@ -6,7 +22,7 @@ import org.apache.commons.logging.LogFactory;
import javax.servlet.jsp.tagext.Tag;
/**
* internal cconfiguration class for taglibs.
* internal configuration class for taglibs.
*
* Not for public use.
*

View File

@ -0,0 +1,49 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.taglibs.csrf;
import org.springframework.security.web.csrf.CsrfToken;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
import java.io.IOException;
/**
* An abstract tag for handling CSRF operations.
*
* @since 3.2.1
* @author Nick Williams
*/
public abstract class AbstractCsrfTag extends TagSupport {
@Override
public int doEndTag() throws JspException {
CsrfToken token = (CsrfToken)this.pageContext.getRequest().getAttribute(CsrfToken.class.getName());
if (token != null) {
try {
this.pageContext.getOut().write(this.handleToken(token));
} catch (IOException e) {
throw new JspException(e);
}
}
return EVAL_PAGE;
}
protected abstract String handleToken(CsrfToken token);
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.taglibs.csrf;
import org.springframework.security.web.csrf.CsrfToken;
/**
* A JSP tag that prints out a hidden form field for the CSRF token. See the JSP Tab Library documentation for more
* information.
*
* @since 3.2.1
* @author Nick Williams
*/
public class FormFieldTag extends AbstractCsrfTag {
@Override
public String handleToken(CsrfToken token) {
return "<input type=\"hidden\" name=\"" + token.getParameterName() + "\" value=\"" + token.getToken() +
"\" />\n";
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.taglibs.csrf;
import org.springframework.security.web.csrf.CsrfToken;
/**
* A JSP tag that prints out a meta tags holding the CSRF form field name and token value for use in JavaScrip code.
* See the JSP Tab Library documentation for more information.
*
* @since 3.2.1
* @author Nick Williams
*/
public class MetaTagsTag extends AbstractCsrfTag {
@Override
public String handleToken(CsrfToken token) {
return "<meta name=\"_csrf_parameter\" content=\"" + token.getParameterName() + "\" />\n" +
" <meta name=\"_csrf_header\" content=\"" + token.getHeaderName() + "\" />\n" +
" <meta name=\"_csrf\" content=\"" + token.getToken() + "\" />\n";
}
}

View File

@ -1,5 +1,19 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<?xml version="1.0" encoding="UTF-8" ?>
<!--
~ Copyright 2002-2014 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
@ -177,4 +191,35 @@
</attribute>
</tag>
<tag>
<description><![CDATA[
If CSRF protection is enabled, this tag inserts a hidden form field with the correct name and value for the
CSRF protection token. If CSRF protection is not enabled, this tag outputs nothing. Normally Spring Security
automatically inserts this form field for any <form:form> tags, but if for some reason you cannot use
<form:form> this tag is a handy replacement. You should place this tag within an HTML <form></form> block,
where you would normally place other <input>s. Do NOT place this tag within a Spring <form:form></form:form>
block—Spring Security handles Spring forms automatically.
]]></description>
<name>csrfField</name>
<tag-class>org.springframework.security.taglibs.csrf.FormFieldTag</tag-class>
<body-content>empty</body-content>
</tag>
<tag>
<description><![CDATA[
If CSRF protection is enabled, this tag inserts meta tags containing the CSRF protection token form
field and header names and CSRF protection token value. These tags are useful for employing CSRF protection
within JavaScript in your applications. You should place this tag within an HTML <head></head> block, where
you would normally place other meta tags. Once you use this tag, you can access the form field name using
the JQuery $("meta[name='_csrf_parameter']").attr("content") and the header name using
$("meta[name='_csrf_header']").attr("content"). Likewise, you can access the token value with
$("meta[name='_csrf']").attr("content"). You should use a form field when creating and submitting forms from
JavaScript, and you should use a header when sending AJAX requests. If CSRF protection is not enabled, this
tag outputs nothing.
]]></description>
<name>csrfMetaTags</name>
<tag-class>org.springframework.security.taglibs.csrf.MetaTagsTag</tag-class>
<body-content>empty</body-content>
</tag>
</taglib>

View File

@ -0,0 +1,91 @@
package org.springframework.security.taglibs.csrf;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockPageContext;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.DefaultCsrfToken;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
import java.io.UnsupportedEncodingException;
import static org.junit.Assert.*;
/**
* @author Nick Williams
*/
public class AbstractCsrfTagTests {
public MockTag tag;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
@Before
public void setUp() {
MockServletContext servletContext = new MockServletContext();
this.request = new MockHttpServletRequest(servletContext);
this.response = new MockHttpServletResponse();
MockPageContext pageContext = new MockPageContext(servletContext, this.request, this.response);
this.tag = new MockTag();
this.tag.setPageContext(pageContext);
}
@Test
public void testDoEndTag01() throws JspException, UnsupportedEncodingException {
this.tag.handleReturn = "fooBarBazQux";
int returned = this.tag.doEndTag();
assertEquals("The returned value is not correct.", TagSupport.EVAL_PAGE, returned);
assertEquals("The output value is not correct.", "", this.response.getContentAsString());
}
@Test
public void testDoEndTag02() throws JspException, UnsupportedEncodingException {
CsrfToken token = new DefaultCsrfToken("X-Csrf-Token", "_csrf", "abc123def456ghi789");
this.request.setAttribute(CsrfToken.class.getName(), token);
this.tag.handleReturn = "fooBarBazQux";
int returned = this.tag.doEndTag();
assertEquals("The returned value is not correct.", TagSupport.EVAL_PAGE, returned);
assertEquals("The output value is not correct.", "fooBarBazQux", this.response.getContentAsString());
assertSame("The token is not correct.", token, this.tag.token);
}
@Test
public void testDoEndTag03() throws JspException, UnsupportedEncodingException {
CsrfToken token = new DefaultCsrfToken("X-Csrf-Token", "_csrf", "abc123def456ghi789");
this.request.setAttribute(CsrfToken.class.getName(), token);
this.tag.handleReturn = "<input type=\"hidden\" />";
int returned = this.tag.doEndTag();
assertEquals("The returned value is not correct.", TagSupport.EVAL_PAGE, returned);
assertEquals("The output value is not correct.", "<input type=\"hidden\" />",
this.response.getContentAsString());
assertSame("The token is not correct.", token, this.tag.token);
}
private static class MockTag extends AbstractCsrfTag {
private CsrfToken token;
private String handleReturn;
@Override
protected String handleToken(CsrfToken token) {
this.token = token;
return this.handleReturn;
}
}
}

View File

@ -0,0 +1,45 @@
package org.springframework.security.taglibs.csrf;
import org.junit.Before;
import org.junit.Test;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.DefaultCsrfToken;
import static org.junit.Assert.*;
/**
* @author Nick Williams
*/
public class FormFieldTagTests {
public FormFieldTag tag;
@Before
public void setUp() {
this.tag = new FormFieldTag();
}
@Test
public void testHandleToken01() {
CsrfToken token = new DefaultCsrfToken("X-Csrf-Token", "_csrf", "abc123def456ghi789");
String value = this.tag.handleToken(token);
assertNotNull("The returned value should not be null.", value);
assertEquals("The output is not correct.",
"<input type=\"hidden\" name=\"_csrf\" value=\"abc123def456ghi789\" />\n",
value);
}
@Test
public void testHandleToken() {
CsrfToken token = new DefaultCsrfToken("X-Csrf-Token", "csrfParameter", "fooBarBazQux");
String value = this.tag.handleToken(token);
assertNotNull("The returned value should not be null.", value);
assertEquals("The output is not correct.",
"<input type=\"hidden\" name=\"csrfParameter\" value=\"fooBarBazQux\" />\n",
value);
}
}

View File

@ -0,0 +1,49 @@
package org.springframework.security.taglibs.csrf;
import org.junit.Before;
import org.junit.Test;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.DefaultCsrfToken;
import static org.junit.Assert.*;
/**
* @author Nick Williams
*/
public class MetaTagsTagTests {
public MetaTagsTag tag;
@Before
public void setUp() {
this.tag = new MetaTagsTag();
}
@Test
public void testHandleToken01() {
CsrfToken token = new DefaultCsrfToken("X-Csrf-Token", "_csrf", "abc123def456ghi789");
String value = this.tag.handleToken(token);
assertNotNull("The returned value should not be null.", value);
assertEquals("The output is not correct.",
"<meta name=\"_csrf_parameter\" content=\"_csrf\" />\n" +
" <meta name=\"_csrf_header\" content=\"X-Csrf-Token\" />\n" +
" <meta name=\"_csrf\" content=\"abc123def456ghi789\" />\n",
value);
}
@Test
public void testHandleToken02() {
CsrfToken token = new DefaultCsrfToken("csrfHeader", "csrfParameter", "fooBarBazQux");
String value = this.tag.handleToken(token);
assertNotNull("The returned value should not be null.", value);
assertEquals("The output is not correct.",
"<meta name=\"_csrf_parameter\" content=\"csrfParameter\" />\n" +
" <meta name=\"_csrf_header\" content=\"csrfHeader\" />\n" +
" <meta name=\"_csrf\" content=\"fooBarBazQux\" />\n",
value);
}
}