spring 测试中的当前请求不是 [org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper] 类型
Current request is not of type [org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper] in spring test
我的控制器中有以下方法:
@RequestMapping(value = { "/owner/terminals/edit",
"/admin/terminals/edit" }, method = RequestMethod.POST)
public String editTerminal(@RequestParam(value = "terminalId") Long terminalId,
@ModelAttribute TerminalRawDTO terminalDto,
Principal principal, RedirectAttributes redirectAttrs, SecurityContextHolderAwareRequestWrapper securityContextHolderAwareRequestWrapper)
throws IOException {
...
我为此方法编写了以下测试:
Terminal terminal = Mockito.mock(Terminal.class);
Mockito.when(terminal.getTerminalId()).thenReturn(1L);
Mockito.when(terminalService.findTerminalById(anyLong())).thenReturn(terminal);
mockMvc.perform(post("/owner/terminals/edit").principal(principal).secure(true)
.param("name", "")
.param("terminalId", "1")
.param("description", "")
.param("startWorkTime", "")
.param("endWorkTime", "")
.param("mapLat", "0.0")
.param("mapLng", "0.0")
.param("terminalGroup", "1")
.param("cost", "0.0")
.param("operationSystem", "")
.param("address", "")
.param("moderationComment", "1"))
.andDo(print())
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/owner/terminals?panel=edit_error1"));
我看到错误:
testEditTerminal(com.terminal.controller.owner.OwnerTerminalsControllerTest): Request processing failed; nested exception is java.lang.IllegalStateException: Current request is not of type [org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper]: org.springframework.mock.web.MockHttpServletRequest@628c4ac0
如何解决?
P.S.
如果从参数控制器中删除 SecurityContextHolderAwareRequestWrapper securityContextHolderAwareRequestWrapper
- 一切正常
P.S.
我在测试中添加了以下代码 class:
@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration(locations = "classpath:META-INF/securityContext.xml")
public class OwnerTerminalsControllerTest {
@Autowired
SecurityContextHolderAwareRequestFilter securityContextHolderAwareRequestFilter;
....
}
securityContext.xml:
<beans:beans
xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<http auto-config="true" pattern="/admin/**" authentication-manager-ref="adminAuthenticationManager">
<access-denied-handler error-page="/403" />
<custom-filter ref="concurrencyFilter" after="SECURITY_CONTEXT_FILTER"/>
<form-login login-page="/loginAdmin" login-processing-url="/admin/j_spring_security_check_admin"
default-target-url="/admin"
authentication-failure-url="/loginAdminFailed"
authentication-success-handler-ref="authAdminSuccessHandler"/>
<intercept-url pattern="/admin/j_spring_security_check_admin" access="ROLE_ANONYMOUS"/>
<intercept-url pattern="/admin/accounts/**" access="ROLE_SUPERADMIN"/>
<intercept-url pattern="/admin/users/**" access="ROLE_SUPERADMIN"/>
<intercept-url pattern="/admin/terminals/**" access="ROLE_SUPERADMIN, ROLE_TERMINAL_MODERATOR, ROLE_MODERATOR"/>
<intercept-url pattern="/admin/money/**" access="ROLE_FINANSIER, ROLE_SUPERADMIN"/>
<intercept-url pattern="/admin/moderation/**" access="ROLE_SUPERADMIN,ROLE_MODERATOR"/>
<intercept-url pattern="/admin/moderation/pictures"
access="ROLE_SUPERADMIN,ROLE_MODERATOR, ROLE_IMAGE_MODERATOR"/>
<intercept-url pattern="/admin/statistic/**" access="ROLE_SUPERADMIN"/>
<intercept-url pattern="/admin/rules/**" access="ROLE_SUPERADMIN"/>
<intercept-url pattern="/admin/terminals/addImageToTerminal"
access="ROLE_SUPERADMIN, ROLE_TERMINAL_MODERATOR, ROLE_MODERATOR"/>
<intercept-url pattern="/admin/terminals/deleteTerminalImage"
access="ROLE_SUPERADMIN, ROLE_TERMINAL_MODERATOR, ROLE_MODERATOR"/>
<intercept-url pattern="/admin/systemGroupsModeration" access="ROLE_SUPERADMIN"/>
<intercept-url pattern="/admin/adminUsers" access="ROLE_SUPERADMIN"/>
<intercept-url pattern="/admin/contentModeration/**" access="ROLE_SUPERADMIN, ROLE_MODERATOR, ROLE_IMAGE_MODERATOR"/>
<intercept-url pattern="/admin/campaignModeration/**" access="ROLE_SUPERADMIN, ROLE_MODERATOR"/>
<intercept-url pattern="/admin/monitoring" access="ROLE_SUPERADMIN"/>
<logout logout-url="/logout" logout-success-url="/loginAdmin"/>
<port-mappings>
<port-mapping http="${http.port}" https="${https.port}"/>
</port-mappings>
<session-management session-authentication-strategy-ref="sas" invalid-session-url="/" />
</http>
<http auto-config="true" authentication-manager-ref="userAuthenticationManager">
<custom-filter ref="concurrencyFilter" after="SECURITY_CONTEXT_FILTER"/>
<form-login login-page="/"
default-target-url="/member/personalAccount"
authentication-failure-url="/loginfailed" authentication-success-handler-ref="authSuccessHandler"/>
<!-- <intercept-url pattern="/common/*" filters="none" /> -->
<intercept-url pattern="/member/createCompany/addParams" access="ROLE_ANONYMOUS, ROLE_USER"/>
<intercept-url pattern="/member/**" access="ROLE_USER"/>
<intercept-url pattern="/owner/*" access="ROLE_OWNER"/>
<intercept-url pattern="/member/getImage/*"
access="ROLE_ANONYMOUS, ROLE_OWNER,ROLE_USER, ROLE_SUPERADMIN, ROLE_TERMINAL_MODERATOR, ROLE_IMAGE_MODERATOR, ROLE_CAMPAIGN_MODERATOR, ROLE_FINANSIER, ROLE_MODERATOR"/>
<logout logout-url="/logout" logout-success-url="/"/>
<port-mappings>
<port-mapping http="${http.port}" https="${https.port}"/>
</port-mappings>
<session-management session-authentication-strategy-ref="sas" invalid-session-url="/" />
</http>
<beans:bean id="userSecurityService" class="com.terminal.service.impl.UserSecurityService"/>
<beans:bean id="authSuccessHandler" class="com.terminal.filter.RoleAuthSuccessHandler"/>
<beans:bean id="authAdminSuccessHandler" class="com.terminal.filter.admin.RoleAuthAdminHandler"/>
<beans:bean id="adminSecurityService" class="com.terminal.service.admin.impl.TerminalAdminSecurityServiceImpl"/>
<beans:bean id="webexpressionHandler"
class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler"/>
<authentication-manager id="adminAuthenticationManager">
<authentication-provider user-service-ref="adminSecurityService">
<password-encoder ref="encoder"/>
</authentication-provider>
</authentication-manager>
<authentication-manager id="userAuthenticationManager">
<authentication-provider user-service-ref="userSecurityService">
<password-encoder ref="encoder"/>
</authentication-provider>
</authentication-manager>
<authentication-manager id="internalUserAuthenticationManager">
<authentication-provider user-service-ref="userSecurityService">
<password-encoder ref="noopEncoder"/>
</authentication-provider>
</authentication-manager>
<beans:bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
<beans:constructor-arg index="0" value="10"/>
</beans:bean>
<beans:bean id="noopEncoder" class="org.springframework.security.crypto.password.NoOpPasswordEncoder"/>
<beans:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl"/>
<beans:bean id="sas" class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy">
<beans:constructor-arg>
<beans:list>
<beans:bean class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
<beans:constructor-arg ref="sessionRegistry"/>
<beans:property name="maximumSessions" value="1" />
<beans:property name="exceptionIfMaximumExceeded" value="true" />
</beans:bean>
<beans:bean class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy">
</beans:bean>
<beans:bean class="org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy">
<beans:constructor-arg ref="sessionRegistry"/>
</beans:bean>
</beans:list>
</beans:constructor-arg>
</beans:bean>
<beans:bean id="concurrencyFilter"
class="org.springframework.security.web.session.ConcurrentSessionFilter">
<beans:property name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="expiredUrl" value="/" />
</beans:bean>
</beans:beans>
你的问题是,为了能够在控制器中使用 SecurityContextHolderAwareRequestWrapper
,请求必须由 SecurityContextHolderAwareRequestFilter
处理,它将包装原始请求(这里是 MockHttpServletRequest
) 变成 SecurityContextHolderAwareRequestWrapper
。根据 Spring Security reference manual,此过滤器是 Spring 安全的内部过滤器,由 springSecurityFilterChain
和 运行 设置。
你的问题是 springSecurityFilterChain
还没有被调用。
我无法测试它,但根据 Spring 框架文档,您可以使用 MockMvc
:
注册过滤器实例
设置MockMvc时,可以注册一个或多个Filter实例:
mockMvc = standaloneSetup(new PersonController()).addFilters(
new CharacterEncodingFilter()).build();
注册的过滤器将通过 MockFilterChain 从 spring-test 调用,最后一个过滤器将委托给 DispatcherServlet。
您必须手动构建和配置 MockMvc
以模仿 classic web.xml
中的过滤器声明:
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
在你的用例中,我会使用类似的东西:
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup() {
DelegatingFilterProxy filterProxy = new DelegatingFilterProxy(
"springSecurityFilterChain", wac);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
addFilter(filterProxy, "/*").build();
}
如果 securityContext.xml 用于构建 wac
应用程序上下文,这应该可以工作。
但是注意。即使它有效,它也会增加漂亮的 Spring 测试框架的复杂性,即使对于简单的 URL 映射测试也是如此。因为您在控制器方法中绑定 Spring 安全性,所以当整个 Spring 机制倾向于支持 关注点分离 时。所以你应该问问自己是否真的有必要在控制器方法中使用 SecurityContextHolderAwareRequestWrapper
。最佳实践表明,安全性应存在于 intercept-url
元素中,形成简单的 url 规则,在服务级别的方法安全性中适用于更复杂的用例。如果您需要跟踪当前用户,使用过滤器或拦截器将相关信息放入会话属性的请求中,只需使用普通的ServletRequest
(WebRequest
或HttpServletRequest
) controller 如果你需要一个 Spring MSV 不会为你提取的元素:测试会更简单,如果你以后更改安全层,未来的发展也会更简单。
在您当前的用例中,您陷入了另一个测试陷阱:
- 您为请求声明主体
- spring 安全过滤器传递所有过滤器并将请求包装在
SecurityContextHolderAwareRequestWrapper
... 但是 SecurityContext 从未被填充,因此包装请求中的主体为空!
对于填充 SecurityContextHolder 的初始请求中的主体,您必须在安全过滤器链中设置一个 J2eePreAuthenticatedProcessingFilter
,在身份验证管理器中设置一个 PreAuthenticatedAuthenticationProvider
,但仅用于测试个人资料。
或者,您可以从头开始创建自定义过滤器,这样:
- 从请求中提取本金
- 如果为空,清除 SecurityContextHolder
- 其他
- 使用它来构建一个身份验证对象(我建议使用 TestingAuthenticationToken),如果它还没有的话
- 构建一个 SecurityContext 并使用该 Authentication 对象进行填充
- 使用该 SecurityContext 填充 SecurityContextHolder
- 将原始请求包装在 SecurityContextHolderAwareRequestWrapper
- 将包装的请求传递给过滤器链
并在配置 MockMvc 时使用它。
它并没有那么复杂,但它添加了另一个 class 专用于测试,应该自己测试。无论如何,包装请求中的主体将是身份验证对象,因为包装请求 returns 在 getUserPrincipal()
的安全上下文中。这就是为什么我之前说过在控制器方法中绑定 Spring 安全性是个坏主意。
如果您想尝试一下,自定义过滤器的代码可能如下所示:
public class TestingWrapperFilter implements Filter {
private Object credentials = "password";
private String rolePrefix = "ROLE";
@Override
public void init(FilterConfig fc) throws ServletException {
}
@Override
public void doFilter(ServletRequest sr, ServletResponse sr1, FilterChain fc) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) sr;
Principal principal = req.getUserPrincipal();
if (principal != null) {
Authentication auth;
auth = (principal instanceof Authentication) ? (Authentication) principal :
new TestingAuthenticationToken(principal, credentials);
SecurityContext sc = new SecurityContextImpl();
sc.setAuthentication(auth);
SecurityContextHolder.setContext(sc);
}
else {
SecurityContextHolder.clearContext();
}
sr = new SecurityContextHolderAwareRequestWrapper(req, rolePrefix);
fc.doFilter(sr, sr1);
}
@Override
public void destroy() {
}
public void setCredentials(Object credentials) {
this.credentials = credentials;
}
public void setRolePrefix(String rolePrefix) {
this.rolePrefix = rolePrefix;
}
}
"/owner/terminals/*" 或 "/owner/**" url 应在 spring-security.xml 中指定以避免该异常。我认为,如果您对“/admin/terminals/edit”执行测试,您将不会得到那种异常。请尝试一次..
<intercept-url pattern="/owner/**" access="ROLE_OWNER"/>
或
<intercept-url pattern="/owner/terminals/*" access="ROLE_OWNER"/>
我希望这能奏效...
我的控制器中有以下方法:
@RequestMapping(value = { "/owner/terminals/edit",
"/admin/terminals/edit" }, method = RequestMethod.POST)
public String editTerminal(@RequestParam(value = "terminalId") Long terminalId,
@ModelAttribute TerminalRawDTO terminalDto,
Principal principal, RedirectAttributes redirectAttrs, SecurityContextHolderAwareRequestWrapper securityContextHolderAwareRequestWrapper)
throws IOException {
...
我为此方法编写了以下测试:
Terminal terminal = Mockito.mock(Terminal.class);
Mockito.when(terminal.getTerminalId()).thenReturn(1L);
Mockito.when(terminalService.findTerminalById(anyLong())).thenReturn(terminal);
mockMvc.perform(post("/owner/terminals/edit").principal(principal).secure(true)
.param("name", "")
.param("terminalId", "1")
.param("description", "")
.param("startWorkTime", "")
.param("endWorkTime", "")
.param("mapLat", "0.0")
.param("mapLng", "0.0")
.param("terminalGroup", "1")
.param("cost", "0.0")
.param("operationSystem", "")
.param("address", "")
.param("moderationComment", "1"))
.andDo(print())
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/owner/terminals?panel=edit_error1"));
我看到错误:
testEditTerminal(com.terminal.controller.owner.OwnerTerminalsControllerTest): Request processing failed; nested exception is java.lang.IllegalStateException: Current request is not of type [org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper]: org.springframework.mock.web.MockHttpServletRequest@628c4ac0
如何解决?
P.S.
如果从参数控制器中删除 SecurityContextHolderAwareRequestWrapper securityContextHolderAwareRequestWrapper
- 一切正常
P.S.
我在测试中添加了以下代码 class:
@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration(locations = "classpath:META-INF/securityContext.xml")
public class OwnerTerminalsControllerTest {
@Autowired
SecurityContextHolderAwareRequestFilter securityContextHolderAwareRequestFilter;
....
}
securityContext.xml:
<beans:beans
xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<http auto-config="true" pattern="/admin/**" authentication-manager-ref="adminAuthenticationManager">
<access-denied-handler error-page="/403" />
<custom-filter ref="concurrencyFilter" after="SECURITY_CONTEXT_FILTER"/>
<form-login login-page="/loginAdmin" login-processing-url="/admin/j_spring_security_check_admin"
default-target-url="/admin"
authentication-failure-url="/loginAdminFailed"
authentication-success-handler-ref="authAdminSuccessHandler"/>
<intercept-url pattern="/admin/j_spring_security_check_admin" access="ROLE_ANONYMOUS"/>
<intercept-url pattern="/admin/accounts/**" access="ROLE_SUPERADMIN"/>
<intercept-url pattern="/admin/users/**" access="ROLE_SUPERADMIN"/>
<intercept-url pattern="/admin/terminals/**" access="ROLE_SUPERADMIN, ROLE_TERMINAL_MODERATOR, ROLE_MODERATOR"/>
<intercept-url pattern="/admin/money/**" access="ROLE_FINANSIER, ROLE_SUPERADMIN"/>
<intercept-url pattern="/admin/moderation/**" access="ROLE_SUPERADMIN,ROLE_MODERATOR"/>
<intercept-url pattern="/admin/moderation/pictures"
access="ROLE_SUPERADMIN,ROLE_MODERATOR, ROLE_IMAGE_MODERATOR"/>
<intercept-url pattern="/admin/statistic/**" access="ROLE_SUPERADMIN"/>
<intercept-url pattern="/admin/rules/**" access="ROLE_SUPERADMIN"/>
<intercept-url pattern="/admin/terminals/addImageToTerminal"
access="ROLE_SUPERADMIN, ROLE_TERMINAL_MODERATOR, ROLE_MODERATOR"/>
<intercept-url pattern="/admin/terminals/deleteTerminalImage"
access="ROLE_SUPERADMIN, ROLE_TERMINAL_MODERATOR, ROLE_MODERATOR"/>
<intercept-url pattern="/admin/systemGroupsModeration" access="ROLE_SUPERADMIN"/>
<intercept-url pattern="/admin/adminUsers" access="ROLE_SUPERADMIN"/>
<intercept-url pattern="/admin/contentModeration/**" access="ROLE_SUPERADMIN, ROLE_MODERATOR, ROLE_IMAGE_MODERATOR"/>
<intercept-url pattern="/admin/campaignModeration/**" access="ROLE_SUPERADMIN, ROLE_MODERATOR"/>
<intercept-url pattern="/admin/monitoring" access="ROLE_SUPERADMIN"/>
<logout logout-url="/logout" logout-success-url="/loginAdmin"/>
<port-mappings>
<port-mapping http="${http.port}" https="${https.port}"/>
</port-mappings>
<session-management session-authentication-strategy-ref="sas" invalid-session-url="/" />
</http>
<http auto-config="true" authentication-manager-ref="userAuthenticationManager">
<custom-filter ref="concurrencyFilter" after="SECURITY_CONTEXT_FILTER"/>
<form-login login-page="/"
default-target-url="/member/personalAccount"
authentication-failure-url="/loginfailed" authentication-success-handler-ref="authSuccessHandler"/>
<!-- <intercept-url pattern="/common/*" filters="none" /> -->
<intercept-url pattern="/member/createCompany/addParams" access="ROLE_ANONYMOUS, ROLE_USER"/>
<intercept-url pattern="/member/**" access="ROLE_USER"/>
<intercept-url pattern="/owner/*" access="ROLE_OWNER"/>
<intercept-url pattern="/member/getImage/*"
access="ROLE_ANONYMOUS, ROLE_OWNER,ROLE_USER, ROLE_SUPERADMIN, ROLE_TERMINAL_MODERATOR, ROLE_IMAGE_MODERATOR, ROLE_CAMPAIGN_MODERATOR, ROLE_FINANSIER, ROLE_MODERATOR"/>
<logout logout-url="/logout" logout-success-url="/"/>
<port-mappings>
<port-mapping http="${http.port}" https="${https.port}"/>
</port-mappings>
<session-management session-authentication-strategy-ref="sas" invalid-session-url="/" />
</http>
<beans:bean id="userSecurityService" class="com.terminal.service.impl.UserSecurityService"/>
<beans:bean id="authSuccessHandler" class="com.terminal.filter.RoleAuthSuccessHandler"/>
<beans:bean id="authAdminSuccessHandler" class="com.terminal.filter.admin.RoleAuthAdminHandler"/>
<beans:bean id="adminSecurityService" class="com.terminal.service.admin.impl.TerminalAdminSecurityServiceImpl"/>
<beans:bean id="webexpressionHandler"
class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler"/>
<authentication-manager id="adminAuthenticationManager">
<authentication-provider user-service-ref="adminSecurityService">
<password-encoder ref="encoder"/>
</authentication-provider>
</authentication-manager>
<authentication-manager id="userAuthenticationManager">
<authentication-provider user-service-ref="userSecurityService">
<password-encoder ref="encoder"/>
</authentication-provider>
</authentication-manager>
<authentication-manager id="internalUserAuthenticationManager">
<authentication-provider user-service-ref="userSecurityService">
<password-encoder ref="noopEncoder"/>
</authentication-provider>
</authentication-manager>
<beans:bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
<beans:constructor-arg index="0" value="10"/>
</beans:bean>
<beans:bean id="noopEncoder" class="org.springframework.security.crypto.password.NoOpPasswordEncoder"/>
<beans:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl"/>
<beans:bean id="sas" class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy">
<beans:constructor-arg>
<beans:list>
<beans:bean class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
<beans:constructor-arg ref="sessionRegistry"/>
<beans:property name="maximumSessions" value="1" />
<beans:property name="exceptionIfMaximumExceeded" value="true" />
</beans:bean>
<beans:bean class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy">
</beans:bean>
<beans:bean class="org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy">
<beans:constructor-arg ref="sessionRegistry"/>
</beans:bean>
</beans:list>
</beans:constructor-arg>
</beans:bean>
<beans:bean id="concurrencyFilter"
class="org.springframework.security.web.session.ConcurrentSessionFilter">
<beans:property name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="expiredUrl" value="/" />
</beans:bean>
</beans:beans>
你的问题是,为了能够在控制器中使用 SecurityContextHolderAwareRequestWrapper
,请求必须由 SecurityContextHolderAwareRequestFilter
处理,它将包装原始请求(这里是 MockHttpServletRequest
) 变成 SecurityContextHolderAwareRequestWrapper
。根据 Spring Security reference manual,此过滤器是 Spring 安全的内部过滤器,由 springSecurityFilterChain
和 运行 设置。
你的问题是 springSecurityFilterChain
还没有被调用。
我无法测试它,但根据 Spring 框架文档,您可以使用 MockMvc
:
设置MockMvc时,可以注册一个或多个Filter实例:
mockMvc = standaloneSetup(new PersonController()).addFilters(
new CharacterEncodingFilter()).build();
注册的过滤器将通过 MockFilterChain 从 spring-test 调用,最后一个过滤器将委托给 DispatcherServlet。
您必须手动构建和配置 MockMvc
以模仿 classic web.xml
中的过滤器声明:
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
在你的用例中,我会使用类似的东西:
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup() {
DelegatingFilterProxy filterProxy = new DelegatingFilterProxy(
"springSecurityFilterChain", wac);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
addFilter(filterProxy, "/*").build();
}
如果 securityContext.xml 用于构建 wac
应用程序上下文,这应该可以工作。
但是注意。即使它有效,它也会增加漂亮的 Spring 测试框架的复杂性,即使对于简单的 URL 映射测试也是如此。因为您在控制器方法中绑定 Spring 安全性,所以当整个 Spring 机制倾向于支持 关注点分离 时。所以你应该问问自己是否真的有必要在控制器方法中使用 SecurityContextHolderAwareRequestWrapper
。最佳实践表明,安全性应存在于 intercept-url
元素中,形成简单的 url 规则,在服务级别的方法安全性中适用于更复杂的用例。如果您需要跟踪当前用户,使用过滤器或拦截器将相关信息放入会话属性的请求中,只需使用普通的ServletRequest
(WebRequest
或HttpServletRequest
) controller 如果你需要一个 Spring MSV 不会为你提取的元素:测试会更简单,如果你以后更改安全层,未来的发展也会更简单。
在您当前的用例中,您陷入了另一个测试陷阱:
- 您为请求声明主体
- spring 安全过滤器传递所有过滤器并将请求包装在
SecurityContextHolderAwareRequestWrapper
... 但是 SecurityContext 从未被填充,因此包装请求中的主体为空!
对于填充 SecurityContextHolder 的初始请求中的主体,您必须在安全过滤器链中设置一个 J2eePreAuthenticatedProcessingFilter
,在身份验证管理器中设置一个 PreAuthenticatedAuthenticationProvider
,但仅用于测试个人资料。
或者,您可以从头开始创建自定义过滤器,这样:
- 从请求中提取本金
- 如果为空,清除 SecurityContextHolder
- 其他
- 使用它来构建一个身份验证对象(我建议使用 TestingAuthenticationToken),如果它还没有的话
- 构建一个 SecurityContext 并使用该 Authentication 对象进行填充
- 使用该 SecurityContext 填充 SecurityContextHolder
- 将原始请求包装在 SecurityContextHolderAwareRequestWrapper
- 将包装的请求传递给过滤器链
并在配置 MockMvc 时使用它。
它并没有那么复杂,但它添加了另一个 class 专用于测试,应该自己测试。无论如何,包装请求中的主体将是身份验证对象,因为包装请求 returns 在 getUserPrincipal()
的安全上下文中。这就是为什么我之前说过在控制器方法中绑定 Spring 安全性是个坏主意。
如果您想尝试一下,自定义过滤器的代码可能如下所示:
public class TestingWrapperFilter implements Filter {
private Object credentials = "password";
private String rolePrefix = "ROLE";
@Override
public void init(FilterConfig fc) throws ServletException {
}
@Override
public void doFilter(ServletRequest sr, ServletResponse sr1, FilterChain fc) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) sr;
Principal principal = req.getUserPrincipal();
if (principal != null) {
Authentication auth;
auth = (principal instanceof Authentication) ? (Authentication) principal :
new TestingAuthenticationToken(principal, credentials);
SecurityContext sc = new SecurityContextImpl();
sc.setAuthentication(auth);
SecurityContextHolder.setContext(sc);
}
else {
SecurityContextHolder.clearContext();
}
sr = new SecurityContextHolderAwareRequestWrapper(req, rolePrefix);
fc.doFilter(sr, sr1);
}
@Override
public void destroy() {
}
public void setCredentials(Object credentials) {
this.credentials = credentials;
}
public void setRolePrefix(String rolePrefix) {
this.rolePrefix = rolePrefix;
}
}
"/owner/terminals/*" 或 "/owner/**" url 应在 spring-security.xml 中指定以避免该异常。我认为,如果您对“/admin/terminals/edit”执行测试,您将不会得到那种异常。请尝试一次..
<intercept-url pattern="/owner/**" access="ROLE_OWNER"/>
或
<intercept-url pattern="/owner/terminals/*" access="ROLE_OWNER"/>
我希望这能奏效...