# 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 ``` | |应该注意的是,在整个应用程序中执行这么多逻辑通常是糟糕的做法。
相反,应该将其集中以减少 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模式,如下所示: ``` ``` 接下来,你需要确保你的SpringSecurityFilterchain是为处理异步请求而设置的。 ``` springSecurityFilterChain org.springframework.web.filter.DelegatingFilterProxy true springSecurityFilterChain /* REQUEST ASYNC ``` 就是这样!现在 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)