SEC-2095: Document Servlet API support

This commit is contained in:
Rob Winch 2013-08-30 12:20:35 -05:00
parent 664220f304
commit 246c632f3a
2 changed files with 168 additions and 0 deletions

View File

@ -128,6 +128,7 @@
</partintro>
<xi:include href="security-filter-chain.xml"/>
<xi:include href="core-filters.xml"/>
<xi:include href="servlet-api.xml"/>
<xi:include href="basic-and-digest-auth.xml"/>
<xi:include href="remember-me-authentication.xml"/>
<xi:include href="csrf.xml"/>

View File

@ -0,0 +1,167 @@
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="servletapi"
xmlns:xlink="http://www.w3.org/1999/xlink">
<info>
<title>Servlet API integration</title>
</info>
<para>This section describes how Spring Security is integrated with the Servlet API. The
<link xlink:href="https://github.com/SpringSource/spring-security/blob/master/samples/servletapi-xml">servletapi-xml</link> sample application demonstrates
the usage of each of these methods.</para>
<section xml:id="servletapi-25">
<title>Servlet 2.5+ Integration</title>
<section xml:id="servletapi-remote-user">
<title>HttpServletRequest.getRemoteUser()</title>
<para>The <link xlink:href="http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getRemoteUser()">HttpServletRequest.getRemoteUser()</link>
will return the result of <literal>SecurityContextHolder.getContext().getAuthentication().getName()</literal> which is typically the current
username. This can be useful if you want to display the current username in your application. Additionally, checking if this
is null can be used to indicate if a user has authenticated or is anonymous. Knowing if the user is authenticated or not can
be useful for determining if certain UI elements should be shown or not (i.e. a log out link should only be displayed if the
user is authenticated).</para>
</section>
<section xml:id="servletapi-user-principal">
<title>HttpServletRequest.getUserPrincipal()</title>
<para>The <link xlink:href="http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getUserPrincipal()">HttpServletRequest.getUserPrincipal()</link>
will return the result of <literal>SecurityContextHolder.getContext().getAuthentication()</literal>. This means it is an <interfacename>Authentication</interfacename>
which is typically an instance of <classname>UsernamePasswordAuthenticationToken</classname> when using username and password based authentication. This can be useful if
you need additional information about your user. For example, you might have created a custom <interfacename>UserDetailsService</interfacename>
that returns a custom <interfacename>UserDetails</interfacename> containing a first and last name for your user. You could obtain this information with the
following:</para>
<programlisting language="java"><![CDATA[Authentication auth = httpServletRequest.getUserPrincipal();
// assume integrated custom UserDetails called MyCustomUserDetails
// by default, typically instance of UserDetails
MyCustomUserDetails userDetails = (MyCustomUserDetails) auth.getPrincipal();
String firstName = userDetails.getFirstName();
String lastName = userDetails.getLastName();
]]></programlisting>
<note>
<para>It should be noted that it is typically bad practice to perform so much logic throughout your application. Instead, one should centralize it to reduce
any coupling of Spring Security and the Servlet API's.
</para>
</note>
</section>
<section xml:id="servletapi-user-in-role">
<title>HttpServletRequest.isUserInRole(String)</title>
<para>The <link xlink:href="http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#isUserInRole(java.lang.String)">HttpServletRequest.isUserInRole(String)</link>
will determine if <literal>SecurityContextHolder.getContext().getAuthentication().getAuthorities()</literal> contains a
<interfacename>GrantedAuthority</interfacename> with the role passed into <literal>isUserInRole(String)</literal>. Typically users should not pass in the "ROLE_" prefix
into this method since it is added automatically. For example, if you want to determine if the current user has the authority "ROLE_ADMIN", you could use the
the following:</para>
<programlisting language="java"><![CDATA[boolean isAdmin = httpServletRequest.isUserInRole("ADMIN");]]></programlisting>
<para>This might be useful to determine if certain UI components should be displayed. For example, you might display admin links only if the current
user is an admin.</para>
</section>
</section>
<section xml:id="servletapi-3">
<title>Servlet 3+ Integration</title>
<para>The following section describes the Servlet 3 methods that Spring Security integrates with.</para>
<section xml:id="servletapi-authenticate">
<title>HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse)</title>
<para>The <link xlink:href="http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#authenticate%28javax.servlet.http.HttpServletResponse%29">HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse)</link>
method can be used to ensure that a user is authenticated. If they are not authenticated, the configured AuthenticationEntryPoint will be used to request the user to authenticate
(i.e. redirect to the login page).</para>
</section>
<section xml:id="servletapi-login">
<title>HttpServletRequest.login(String,String)</title>
<para>The <link xlink:href="http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#login%28java.lang.String,%20java.lang.String%29">HttpServletRequest.login(String,String)</link>
method can be used to authenticate the user with the current <interfacename>AuthenticationManager</interfacename>. For example, the following would attempt to
authenticate with the username "user" and password "password":</para>
<programlisting language="java"><![CDATA[try {
httpServletRequest.login("user","password");
} catch(ServletException e) {
// fail to authenticate
}]]></programlisting>
<note>
<para>It is not necessary to catch the ServletException if you want Spring Security to process the failed authentication attempt.</para>
</note>
</section>
<section xml:id="servletapi-logout">
<title>HttpServletRequest.logout()</title>
<para>The <link xlink:href="http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#logout%28%29">HttpServletRequest.logout()</link>
method can be used to log the current user out.</para>
<para>Typically this means that the SecurityContextHolder will be cleared out, the HttpSession will be invalidated, any "Remember Me" authentication will be
cleaned up, etc. However, the configured LogoutHandler implementations will vary depending on your Spring Security configuration. It is important to note
that after HttpServletRequest.logout() has been invoked, you are still in charge of writing a response out. Typically this would involve a redirect to the
welcome page.</para>
</section>
<section xml:id="servletapi-start-runnable">
<title>AsyncContext.start(Runnable)</title>
<para>The <link xlink:href="http://docs.oracle.com/javaee/6/api/javax/servlet/AsyncContext.html#start%28java.lang.Runnable%29">AsynchContext.start(Runnable)</link>
method that ensures your credentials will be propagated to the new Thread. Using Spring Security's concurrency support, Spring Security overrides
the AsyncContext.start(Runnable) to ensure that the current SecurityContext is used when processing the Runnable. For example, the following
would output the current user's Authentication:</para>
<programlisting language="java"><![CDATA[final AsyncContext async = httpServletRequest.startAsync();
async.start(new Runnable() {
public void run() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
try {
final HttpServletResponse asyncResponse = (HttpServletResponse) async.getResponse();
asyncResponse.setStatus(HttpServletResponse.SC_OK);
asyncResponse.getWriter().write(String.valueOf(authentication));
async.complete();
} catch(Exception e) {
throw new RuntimeException(e);
}
}
});]]></programlisting>
</section>
<section xml:id="servletapi-async">
<title>Async Servlet Support</title>
<para>If you are using Java Based configuration, you are ready to go. If you are using XML configuration, there are
a few updates that are necessary. The first step is to ensure you have updated your web.xml to use at least the 3.0 schema
as shown below:</para>
<programlisting language="xml"><![CDATA[<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
</web-app>]]></programlisting>
<para>Next you need to ensure that your springSecurityFilterChain is setup for processing asynchronous requests.</para>
<programlisting language="xml"><![CDATA[<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ASYNC</dispatcher>
</filter-mapping>]]></programlisting>
<para>That's it! Now Spring Security will ensure that your SecurityContext is propagated on asynchronous requests too.</para>
<para>So how does it work? If you are not really interested, feel free to skip the remainder of this section, otherwise read on. Most of this
is built into the Servlet specification, but there is a little bit of tweaking that Spring Security does to ensure things work with
asynchronous requests properly. Prior to Spring Security 3.2, the SecurityContext from the SecurityContextHolder was automatically saved as soon
as the HttpServletResponse was committed. This can cause issues in a Async environment. For example, consider the following:</para>
<programlisting language="java"><![CDATA[httpServletRequest.startAsync();
new Thread("AsyncThread") {
@Override
public void run() {
try {
// Do work
TimeUnit.SECONDS.sleep(1);
// Write to and commit the httpServletResponse
httpServletResponse.getOutputStream().flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();]]></programlisting>
<para>The issue is that this Thread is not known to Spring Security, so the SecurityContext is not propagated to it. This means when we commit the
HttpServletResponse there is no SecuriytContext. When Spring Security automatically saved the SecurityContext on committing the HttpServletResponse it
would lose our logged in user.</para>
<para>Since version 3.2, Spring Security is smart enough to no longer automatically save the SecurityContext on commiting the HttpServletResponse
as soon as HttpServletRequest.startAsync() is invoked.</para>
</section>
</section>
<section xml:id="servletapi-31">
<title>Servlet 3.1+ Integration</title>
<para>The following section describes the Servlet 3.1 methods that Spring Security integrates with.</para>
<section xml:id="servletapi-authenticate">
<title>HttpServletRequest#changeSessionId()</title>
<para>The <link xlink:href="http://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html#changeSessionId()">HttpServletRequest.changeSessionId()</link>
is the default method for protecting against <link linkend="ns-session-fixation">Session Fixation</link> attacks in Servlet 3.1 and higher.</para>
</section>
</section>
</chapter>