servlet-integrations-servlet-api.md 10.5 KB
Newer Older
茶陵後's avatar
茶陵後 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
# Servlet API integration

## Servlet 2.5+ Integration

### HttpServletRequest.getRemoteUser()

The [HttpServletRequest.getRemoteUser()](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getRemoteUser()) will return the result of `SecurityContextHolder.getContext().getAuthentication().getName()` 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).

### HttpServletRequest.getUserPrincipal()

The [HttpServletRequest.getUserPrincipal()](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getUserPrincipal()) will return the result of `SecurityContextHolder.getContext().getAuthentication()`.
This means it is an `Authentication` which is typically an instance of `UsernamePasswordAuthenticationToken` 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 `UserDetailsService` that returns a custom `UserDetails` containing a first and last name for your user.
You could obtain this information with the following:

Java

```
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();
```

Kotlin

```
val auth: Authentication = httpServletRequest.getUserPrincipal()
// assume integrated custom UserDetails called MyCustomUserDetails
// by default, typically instance of UserDetails
val userDetails: MyCustomUserDetails = auth.principal as MyCustomUserDetails
val firstName: String = userDetails.firstName
val lastName: String = userDetails.lastName
```

|   |It should be noted that it is typically bad practice to perform so much logic throughout your application.<br/>Instead, one should centralize it to reduce any coupling of Spring Security and the Servlet API’s.|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### HttpServletRequest.isUserInRole(String)

The [HttpServletRequest.isUserInRole(String)](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#isUserInRole(java.lang.String)) will determine if `SecurityContextHolder.getContext().getAuthentication().getAuthorities()` contains a `GrantedAuthority` with the role passed into `isUserInRole(String)`.
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 following:

Java

```
boolean isAdmin = httpServletRequest.isUserInRole("ADMIN");
```

Kotlin

```
val isAdmin: Boolean = httpServletRequest.isUserInRole("ADMIN")
```

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.

## Servlet 3+ Integration
The following section describes the Servlet 3 methods that Spring Security integrates with.

### HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse)

The [HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse)](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#authenticate%28javax.servlet.http.HttpServletResponse%29) 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).

### HttpServletRequest.login(String,String)
The [HttpServletRequest.login(String,String)](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#login%28java.lang.String,%20java.lang.String%29) method can be used to authenticate the user with the current `AuthenticationManager`.
For example, the following would attempt to authenticate with the username "user" and password "password":

Java

```
try {
httpServletRequest.login("user","password");
} catch(ServletException ex) {
// fail to authenticate
}
```

Kotlin

```
try {
    httpServletRequest.login("user", "password")
} catch (ex: ServletException) {
    // fail to authenticate
}
```

|   |It is not necessary to catch the ServletException if you want Spring Security to process the failed authentication attempt.|
|---|---------------------------------------------------------------------------------------------------------------------------|

### HttpServletRequest.logout()
The [HttpServletRequest.logout()](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#logout%28%29) method can be used to log the current user out.

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.

### AsyncContext.start(Runnable)
The [AsyncContext.start(Runnable)](https://docs.oracle.com/javaee/6/api/javax/servlet/AsyncContext.html#start%28java.lang.Runnable%29) 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:

Java

```
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 ex) {
			throw new RuntimeException(ex);
		}
	}
});
```

Kotlin

```
val async: AsyncContext = httpServletRequest.startAsync()
async.start {
    val authentication: Authentication = SecurityContextHolder.getContext().authentication
    try {
        val asyncResponse = async.response as HttpServletResponse
        asyncResponse.status = HttpServletResponse.SC_OK
        asyncResponse.writer.write(String.valueOf(authentication))
        async.complete()
    } catch (ex: Exception) {
        throw RuntimeException(ex)
    }
}
```

### Async Servlet Support
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:

```
<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 https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">

</web-app>
```

Next you need to ensure that your springSecurityFilterChain is setup for processing asynchronous requests.

```
<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>
```

That’s it!
Now Spring Security will ensure that your SecurityContext is propagated on asynchronous requests too.

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 an Async environment.
For example, consider the following:

Java

```
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 ex) {
			ex.printStackTrace();
		}
	}
}.start();
```

Kotlin

```
httpServletRequest.startAsync()
object : Thread("AsyncThread") {
    override fun run() {
        try {
            // Do work
            TimeUnit.SECONDS.sleep(1)

            // Write to and commit the httpServletResponse
            httpServletResponse.outputStream.flush()
        } catch (ex: java.lang.Exception) {
            ex.printStackTrace()
        }
    }
}.start()
```

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 SecurityContext.
When Spring Security automatically saved the SecurityContext on committing the HttpServletResponse it would lose our logged in user.

Since version 3.2, Spring Security is smart enough to no longer automatically save the SecurityContext on committing the HttpServletResponse as soon as HttpServletRequest.startAsync() is invoked.

## Servlet 3.1+ Integration
The following section describes the Servlet 3.1 methods that Spring Security integrates with.

### HttpServletRequest#changeSessionId()
The [HttpServletRequest.changeSessionId()](https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html#changeSessionId()) is the default method for protecting against [Session Fixation](../authentication/session-management.html#ns-session-fixation) attacks in Servlet 3.1 and higher.

[Localization](localization.html)[Spring Data](data.html)