servlet-integrations-servlet-api.md 10.0 KB
Newer Older
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
# Servlet API集成

## Servlet 2.5+集成

### HttpServletRequest.getRemoteUser()

[HttpServletRequest.getRemoteUser()](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getRemoteUser())将返回`SecurityContextHolder.getContext().getAuthentication().getName()`的结果,这通常是当前的用户名。如果你希望在应用程序中显示当前用户名,这将非常有用。此外,检查此值是否为null可以用来指示用户是否已通过身份验证或是匿名的。了解用户是否经过了身份验证,对于确定某些UI元素是否应该显示是有用的(例如,只有在用户经过身份验证的情况下才应该显示注销链接)。

### HttpServletRequest.getUserPrincipal()

[HttpServletRequest.getUserPrincipal()](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getUserPrincipal())将返回`SecurityContextHolder.getContext().getAuthentication()`的结果。这意味着它是`Authentication`,当使用基于用户名和密码的身份验证时,它通常是`UsernamePasswordAuthenticationToken`的一个实例。如果你需要有关你的用户的其他信息,这可能是有用的。例如,你可能已经创建了一个自定义`UserDetailsService`,它返回一个自定义的`UserDetails`,其中包含用户的姓和名。你可以通过以下方式获得此信息:

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

|   |应该注意的是,在整个应用程序中执行这么多逻辑通常是糟糕的做法。<br/>相反,应该将其集中以减少 Spring 安全性和 Servlet API的任何耦合。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### HttpServletRequest.isUserinRole(字符串)

[HttpServletRequest.isUserinRole(字符串)](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#isUserInRole(java.lang.String))将确定`SecurityContextHolder.getContext().getAuthentication().getAuthorities()`是否包含带有传递到`isUserInRole(String)`的角色的`GrantedAuthority`。通常,用户不应该将“role\_”前缀传入此方法,因为它是自动添加的。例如,如果你想确定当前用户是否拥有“role\_admin”权限,可以使用以下方法:

Java

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

Kotlin

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

这对于确定是否应该显示某些UI组件可能很有用。例如,你可能仅在当前用户是管理员的情况下才显示管理链接。

## Servlet 3+集成
下面的部分描述了 Spring 安全性集成的 Servlet 3种方法。

### HttpServletRequest.Authenticate(HttpServletRequest,HttpServletResponse)

可以使用[HttpServletRequest.Authenticate(HttpServletRequest,HttpServletResponse)](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#authenticate%28javax.servlet.http.HttpServletResponse%29)方法来确保对用户进行身份验证。如果未对它们进行身份验证,则将使用配置的身份验证中心点来请求用户进行身份验证(即重定向到登录页面)。

### HttpServletRequest.login(字符串,字符串)
可以使用[HttpServletRequest.login(字符串,字符串)](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#login%28java.lang.String,%20java.lang.String%29)方法对当前`AuthenticationManager`的用户进行身份验证。例如,下面将尝试使用用户名“user”和密码“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
}
```

|   |如果你希望 Spring 安全性来处理失败的身份验证尝试,那么不需要捕获ServletException。|
|---|---------------------------------------------------------------------------------------------------------------------------|

### HttpServletRequest.logout()
可以使用[HttpServletRequest.logout()](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#logout%28%29)方法将当前用户注销。

通常,这意味着SecurityContextholder将被清除,HttpSession将无效,任何“记住我”的身份验证都将被清除,等等。然而,所配置的LogouthAndler实现将根据你的 Spring 安全配置而有所不同。需要注意的是,在调用了HttpServletRequest.logout()之后,你仍然负责编写响应。通常情况下,这需要重定向到欢迎页面。

### AsyncContext.start(可运行)
确保你的凭据将被传播到新线程的[AsyncContext.start(可运行)](https://docs.oracle.com/javaee/6/api/javax/servlet/AsyncContext.html#start%28java.lang.Runnable%29)方法。利用 Spring Security的并发支持, Spring Security覆盖了AsyncContext.Start,以确保在处理Runnable时使用当前的SecurityContext。例如,下面将输出当前用户的身份验证:

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

### 异步 Servlet 支持
如果你正在使用基于 Java 的配置,那么你已经准备好了。如果你正在使用XML配置,那么有一些更新是必要的。第一步是确保你已经更新了web.xml,以至少使用3.0模式,如下所示:

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

接下来,你需要确保你的SpringSecurityFilterchain是为处理异步请求而设置的。

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

就是这样!现在 Spring 安全性将确保你的SecurityContext也在异步请求上传播。

那么,它是如何工作的呢?如果你真的不感兴趣,请跳过这一节的其余部分,否则请继续阅读。这其中的大部分是内置在 Servlet 规范中的,但是 Spring 安全性做了一些调整,以确保异步请求能够正常工作。在 Spring Security3.2之前,一旦提交了HttpServletResponse,来自SecurityContextholder的SecurityContext就会自动保存。这可能会在异步环境中引起问题。例如,考虑以下几点:

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

问题是安全性不知道这个线程,因此SecurityContext不会传播到它。这意味着当我们提交HttpServletResponse时,不存在SecurityContext。 Spring 当Security在提交HttpServletResponse时自动保存SecurityContext时,它将丢失已登录的用户。

自版本3.2以来, Spring 安全性已经足够聪明,不再在调用HttpServletRequest.startasync()时提交HttpServletResponse时自动保存SecurityContext。

## Servlet 3.1+集成
下面的部分描述了 Spring 安全性集成的 Servlet 3.1方法。

### HttpServletRequest#changesessionID()
在 Servlet 3.1及更高版本中,[HttpServletRequest.changesessionID()](https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html#changeSessionId())是防止[Session Fixation](../authentication/session-management.html#ns-session-fixation)攻击的默认方法。

[本地化](localization.html)[Spring Data](data.html)