(window.webpackJsonp=window.webpackJsonp||[]).push([[653],{1084:function(e,t,s){"use strict";s.r(t);var a=s(56),n=Object(a.a)({},(function(){var e=this,t=e.$createElement,s=e._self._c||t;return s("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[s("h1",{attrs:{id:"测试方法安全性"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#测试方法安全性"}},[e._v("#")]),e._v(" 测试方法安全性")]),e._v(" "),s("p",[e._v("本节演示如何使用 Spring Security的测试支持来测试基于方法的安全性。我们首先介绍一个"),s("code",[e._v("MessageService")]),e._v(",它需要对用户进行身份验证才能访问它。")]),e._v(" "),s("p",[e._v("Java")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('public class HelloMessageService implements MessageService {\n\n\t@PreAuthorize("authenticated")\n\tpublic String getMessage() {\n\t\tAuthentication authentication = SecurityContextHolder.getContext()\n\t\t\t.getAuthentication();\n\t\treturn "Hello " + authentication;\n\t}\n}\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('class HelloMessageService : MessageService {\n @PreAuthorize("authenticated")\n fun getMessage(): String {\n val authentication: Authentication = SecurityContextHolder.getContext().authentication\n return "Hello $authentication"\n }\n}\n')])])]),s("p",[s("code",[e._v("getMessage")]),e._v("的结果是对当前 Spring 安全性"),s("code",[e._v("Authentication")]),e._v("说“你好”的字符串。下面将显示一个输出示例。")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("Hello org.springframew[email protected]ca25360: Principal: [email protected]: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER\n")])])]),s("h2",{attrs:{id:"安全测试设置"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#安全测试设置"}},[e._v("#")]),e._v(" 安全测试设置")]),e._v(" "),s("p",[e._v("在使用 Spring 安全测试支持之前,我们必须执行一些设置。下面是一个例子:")]),e._v(" "),s("p",[e._v("Java")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("@RunWith(SpringJUnit4ClassRunner.class) (1)\n@ContextConfiguration (2)\npublic class WithMockUserTests {\n")])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("@RunWith(SpringJUnit4ClassRunner::class)\n@ContextConfiguration\nclass WithMockUserTests {\n")])])]),s("p",[e._v("这是如何设置 Spring 安全性测试的一个基本示例。重点是:")]),e._v(" "),s("table",[s("thead",[s("tr",[s("th",[s("strong",[e._v("1")])]),e._v(" "),s("th",[s("code",[e._v("@RunWith")]),e._v("指示 Spring-test模块应该创建"),s("code",[e._v("ApplicationContext")]),e._v("。这与使用现有的 Spring 测试支持没有什么不同。有关更多信息,请参阅"),s("a",{attrs:{href:"https://docs.spring.io/spring-framework/docs/4.0.x/spring-framework-reference/htmlsingle/#integration-testing-annotations-standard",target:"_blank",rel:"noopener noreferrer"}},[e._v("Spring Reference"),s("OutboundLink")],1)])])]),e._v(" "),s("tbody",[s("tr",[s("td",[s("strong",[e._v("2")])]),e._v(" "),s("td",[s("code",[e._v("@ContextConfiguration")]),e._v("指示 Spring-test用于创建"),s("code",[e._v("ApplicationContext")]),e._v("的配置。由于未指定配置,因此将尝试默认的配置位置。这与使用现有的 Spring 测试支持没有什么不同。有关更多信息,请参阅"),s("a",{attrs:{href:"https://docs.spring.io/spring-framework/docs/4.0.x/spring-framework-reference/htmlsingle/#testcontext-ctx-management",target:"_blank",rel:"noopener noreferrer"}},[e._v("Spring Reference"),s("OutboundLink")],1)])])])]),e._v(" "),s("table",[s("thead",[s("tr",[s("th"),e._v(" "),s("th",[e._v("Spring 使用"),s("code",[e._v("WithSecurityContextTestExecutionListener")]),e._v("将安全挂钩到 Spring 测试支持中,这将确保我们的测试与正确的用户一起运行。"),s("br"),e._v("它通过在运行我们的测试之前填充"),s("code",[e._v("SecurityContextHolder")]),e._v("来实现这一点。"),s("br"),e._v("如果你使用的是反应式方法安全,你还需要填充"),s("code",[e._v("ReactorContextTestExecutionListener")]),e._v("的"),s("code",[e._v("ReactiveSecurityContextHolder")]),e._v("。"),s("br"),e._v("测试完成后,它将清除"),s("code",[e._v("SecurityContextHolder")]),e._v("。"),s("br"),e._v("如果你只需要 Spring 安全相关的支持,则可以将"),s("code",[e._v("@ContextConfiguration")]),e._v("替换为"),s("code",[e._v("@SecurityTestExecutionListeners")]),e._v("。")])])]),e._v(" "),s("tbody")]),e._v(" "),s("p",[e._v("请记住,我们在我们的"),s("code",[e._v("@PreAuthorize")]),e._v("中添加了"),s("code",[e._v("HelloMessageService")]),e._v("注释,因此它需要经过身份验证的用户来调用它。如果我们运行以下测试,我们预计以下测试将通过:")]),e._v(" "),s("p",[e._v("Java")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("@Test(expected = AuthenticationCredentialsNotFoundException.class)\npublic void getMessageUnauthenticated() {\n\tmessageService.getMessage();\n}\n")])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("@Test(expected = AuthenticationCredentialsNotFoundException::class)\nfun getMessageUnauthenticated() {\n messageService.getMessage()\n}\n")])])]),s("h2",{attrs:{id:"withmockuser"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#withmockuser"}},[e._v("#")]),e._v(" @WithMockuser")]),e._v(" "),s("p",[e._v("问题是“作为一个特定的用户,我们如何才能最容易地运行测试?”答案是使用"),s("code",[e._v("@WithMockUser")]),e._v("。下面的测试将以用户名“user”、密码“password”和角色“role_user”的用户身份运行。")]),e._v(" "),s("p",[e._v("Java")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("@Test\n@WithMockUser\npublic void getMessageWithMockUser() {\nString message = messageService.getMessage();\n...\n}\n")])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("@Test\n@WithMockUser\nfun getMessageWithMockUser() {\n val message: String = messageService.getMessage()\n // ...\n}\n")])])]),s("p",[e._v("具体来说,以下情况是正确的:")]),e._v(" "),s("ul",[s("li",[s("p",[e._v("用户名为“user”的用户不一定存在,因为我们是在嘲笑用户")])]),e._v(" "),s("li",[s("p",[e._v("在"),s("code",[e._v("SecurityContext")]),e._v("中填充的"),s("code",[e._v("Authentication")]),e._v("类型为"),s("code",[e._v("UsernamePasswordAuthenticationToken")])])]),e._v(" "),s("li",[s("p",[s("code",[e._v("Authentication")]),e._v("上的主体是 Spring 证券的"),s("code",[e._v("User")]),e._v("对象")])]),e._v(" "),s("li",[s("p",[s("code",[e._v("User")]),e._v("将具有“user”的用户名,密码“password”,并使用一个名为“role_user”的"),s("code",[e._v("GrantedAuthority")]),e._v("。")])])]),e._v(" "),s("p",[e._v("我们的例子很好,因为我们能够利用大量的违约。如果我们想用不同的用户名运行测试,该怎么办?下面的测试将使用用户名“CustomUser”运行。同样,用户不需要实际存在。")]),e._v(" "),s("p",[e._v("Java")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('@Test\n@WithMockUser("customUsername")\npublic void getMessageWithMockUserCustomUsername() {\n\tString message = messageService.getMessage();\n...\n}\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('@Test\n@WithMockUser("customUsername")\nfun getMessageWithMockUserCustomUsername() {\n val message: String = messageService.getMessage()\n // ...\n}\n')])])]),s("p",[e._v("我们还可以轻松地定制角色。例如,将使用用户名“admin”以及角色“role_user”和“role_admin”来调用此测试。")]),e._v(" "),s("p",[e._v("Java")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('@Test\n@WithMockUser(username="admin",roles={"USER","ADMIN"})\npublic void getMessageWithMockUserCustomUser() {\n\tString message = messageService.getMessage();\n\t...\n}\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('@Test\n@WithMockUser(username="admin",roles=["USER","ADMIN"])\nfun getMessageWithMockUserCustomUser() {\n val message: String = messageService.getMessage()\n // ...\n}\n')])])]),s("p",[e._v("如果我们不希望该值自动以role_为前缀,那么我们可以利用Authorities属性。例如,将使用用户名“admin”以及权威机构“user”和“admin”来调用此测试。")]),e._v(" "),s("p",[e._v("Java")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('@Test\n@WithMockUser(username = "admin", authorities = { "ADMIN", "USER" })\npublic void getMessageWithMockUserCustomAuthorities() {\n\tString message = messageService.getMessage();\n\t...\n}\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('@Test\n@WithMockUser(username = "admin", authorities = ["ADMIN", "USER"])\nfun getMessageWithMockUserCustomUsername() {\n val message: String = messageService.getMessage()\n // ...\n}\n')])])]),s("p",[e._v("当然,在每个测试方法上放置注释可能会有点繁琐。相反,我们可以将注释放在类级别,并且每个测试都将使用指定的用户。例如,下面将对用户名为“admin”、密码为“password”、角色为“role_user”和“role_admin”的用户进行每次测试。")]),e._v(" "),s("p",[e._v("Java")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('@RunWith(SpringJUnit4ClassRunner.class)\n@ContextConfiguration\n@WithMockUser(username="admin",roles={"USER","ADMIN"})\npublic class WithMockUserTests {\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('@RunWith(SpringJUnit4ClassRunner::class)\n@ContextConfiguration\n@WithMockUser(username="admin",roles=["USER","ADMIN"])\nclass WithMockUserTests {\n')])])]),s("p",[e._v("如果你正在使用JUnit5的"),s("code",[e._v("@Nested")]),e._v("测试支持,那么你还可以将注释放置在封闭类上,以应用于所有嵌套的类。例如,下面将使用用户名为“admin”、密码为“password”、角色为“role_user”和“role_admin”的用户来运行每个测试。")]),e._v(" "),s("p",[e._v("Java")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('@ExtendWith(SpringExtension.class)\n@ContextConfiguration\n@WithMockUser(username="admin",roles={"USER","ADMIN"})\npublic class WithMockUserTests {\n\n\t@Nested\n\tpublic class TestSuite1 {\n\t\t// ... all test methods use admin user\n\t}\n\n\t@Nested\n\tpublic class TestSuite2 {\n\t\t// ... all test methods use admin user\n\t}\n}\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('@ExtendWith(SpringExtension::class)\n@ContextConfiguration\n@WithMockUser(username = "admin", roles = ["USER", "ADMIN"])\nclass WithMockUserTests {\n @Nested\n inner class TestSuite1 { // ... all test methods use admin user\n }\n\n @Nested\n inner class TestSuite2 { // ... all test methods use admin user\n }\n}\n')])])]),s("p",[e._v("默认情况下,"),s("code",[e._v("SecurityContext")]),e._v("是在"),s("code",[e._v("TestExecutionListener.beforeTestMethod")]),e._v("事件期间设置的。这相当于在JUnit"),s("code",[e._v("@Before")]),e._v("之前发生的情况。你可以将此更改为在"),s("code",[e._v("TestExecutionListener.beforeTestExecution")]),e._v("事件期间发生,该事件发生在JUnit的"),s("code",[e._v("@Before")]),e._v("之后,但在调用测试方法之前。")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("@WithMockUser(setupBefore = TestExecutionEvent.TEST_EXECUTION)\n")])])]),s("h2",{attrs:{id:"withanonymoususer"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#withanonymoususer"}},[e._v("#")]),e._v(" @withAnonymoususer")]),e._v(" "),s("p",[e._v("使用"),s("code",[e._v("@WithAnonymousUser")]),e._v("允许作为匿名用户运行。当你希望与特定用户一起运行大多数测试,但希望以匿名用户的身份运行一些测试时,这一点特别方便。例如,下面将使用"),s("a",{attrs:{href:"#test-method-withmockuser"}},[e._v("@WithMockuser")]),e._v("和匿名者作为匿名用户运行Mockuser1和Mockuser2。")]),e._v(" "),s("p",[e._v("Java")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("@RunWith(SpringJUnit4ClassRunner.class)\n@WithMockUser\npublic class WithUserClassLevelAuthenticationTests {\n\n\t@Test\n\tpublic void withMockUser1() {\n\t}\n\n\t@Test\n\tpublic void withMockUser2() {\n\t}\n\n\t@Test\n\t@WithAnonymousUser\n\tpublic void anonymous() throws Exception {\n\t\t// override default to run as anonymous user\n\t}\n}\n")])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("@RunWith(SpringJUnit4ClassRunner::class)\n@WithMockUser\nclass WithUserClassLevelAuthenticationTests {\n @Test\n fun withMockUser1() {\n }\n\n @Test\n fun withMockUser2() {\n }\n\n @Test\n @WithAnonymousUser\n fun anonymous() {\n // override default to run as anonymous user\n }\n}\n")])])]),s("p",[e._v("默认情况下,"),s("code",[e._v("SecurityContext")]),e._v("是在"),s("code",[e._v("TestExecutionListener.beforeTestMethod")]),e._v("事件期间设置的。这相当于发生在JUnit的"),s("code",[e._v("@Before")]),e._v("之前。你可以将此更改为在"),s("code",[e._v("TestExecutionListener.beforeTestExecution")]),e._v("事件期间发生,该事件发生在JUnit的"),s("code",[e._v("@Before")]),e._v("之后,但在调用测试方法之前。")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("@WithAnonymousUser(setupBefore = TestExecutionEvent.TEST_EXECUTION)\n")])])]),s("h2",{attrs:{id:"withuserdetails"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#withuserdetails"}},[e._v("#")]),e._v(" @withuserdetails")]),e._v(" "),s("p",[e._v("虽然"),s("code",[e._v("@WithMockUser")]),e._v("是一种非常方便的启动方式,但它可能并不是在所有实例中都有效。例如,对于应用程序来说,期望"),s("code",[e._v("Authentication")]),e._v("主体具有特定类型是很常见的。这样做是为了使应用程序可以将主体称为自定义类型,并减少 Spring 安全性上的耦合。")]),e._v(" "),s("p",[e._v("自定义主体通常是由一个自定义"),s("code",[e._v("UserDetailsService")]),e._v("返回的,该自定义主体返回一个对象,该对象实现"),s("code",[e._v("UserDetails")]),e._v("和自定义类型。对于这样的情况,使用自定义"),s("code",[e._v("UserDetailsService")]),e._v("创建测试用户是有用的。这正是"),s("code",[e._v("@WithUserDetails")]),e._v("所做的。")]),e._v(" "),s("p",[e._v("假设我们将"),s("code",[e._v("UserDetailsService")]),e._v("作为 Bean 公开,那么下面的测试将使用"),s("code",[e._v("Authentication")]),e._v("类型的"),s("code",[e._v("UsernamePasswordAuthenticationToken")]),e._v("和从"),s("code",[e._v("UserDetailsService")]),e._v("返回的、用户名为“user”的主体来调用。")]),e._v(" "),s("p",[e._v("Java")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("@Test\n@WithUserDetails\npublic void getMessageWithUserDetails() {\n\tString message = messageService.getMessage();\n\t...\n}\n")])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("@Test\n@WithUserDetails\nfun getMessageWithUserDetails() {\n val message: String = messageService.getMessage()\n // ...\n}\n")])])]),s("p",[e._v("我们还可以自定义用于从"),s("code",[e._v("UserDetailsService")]),e._v("中查找用户的用户名。例如,该测试将使用从"),s("code",[e._v("UserDetailsService")]),e._v("返回的主体运行,该主体的用户名为“CustomUsername”。")]),e._v(" "),s("p",[e._v("Java")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('@Test\n@WithUserDetails("customUsername")\npublic void getMessageWithUserDetailsCustomUsername() {\n\tString message = messageService.getMessage();\n\t...\n}\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('@Test\n@WithUserDetails("customUsername")\nfun getMessageWithUserDetailsCustomUsername() {\n val message: String = messageService.getMessage()\n // ...\n}\n')])])]),s("p",[e._v("我们还可以提供一个显式的 Bean 名称来查找"),s("code",[e._v("UserDetailsService")]),e._v("。例如,该测试将使用"),s("code",[e._v("UserDetailsService")]),e._v("和 Bean 名称“MyUserDetailsService”来查找“CustomUsername”的用户名。")]),e._v(" "),s("p",[e._v("Java")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('@Test\n@WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService")\npublic void getMessageWithUserDetailsServiceBeanName() {\n\tString message = messageService.getMessage();\n\t...\n}\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('@Test\n@WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService")\nfun getMessageWithUserDetailsServiceBeanName() {\n val message: String = messageService.getMessage()\n // ...\n}\n')])])]),s("p",[e._v("像"),s("code",[e._v("@WithMockUser")]),e._v("一样,我们也可以将注释放在类级别,这样每个测试都使用相同的用户。然而,与"),s("code",[e._v("@WithMockUser")]),e._v("不同,"),s("code",[e._v("@WithUserDetails")]),e._v("要求用户存在。")]),e._v(" "),s("p",[e._v("默认情况下,"),s("code",[e._v("SecurityContext")]),e._v("是在"),s("code",[e._v("TestExecutionListener.beforeTestMethod")]),e._v("事件期间设置的。这相当于发生在JUnit的"),s("code",[e._v("@Before")]),e._v("之前。你可以将此更改为在"),s("code",[e._v("TestExecutionListener.beforeTestExecution")]),e._v("事件期间发生,该事件发生在JUnit的"),s("code",[e._v("@Before")]),e._v("之后,但在调用测试方法之前。")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("@WithUserDetails(setupBefore = TestExecutionEvent.TEST_EXECUTION)\n")])])]),s("h2",{attrs:{id:"withsecuritycontext"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#withsecuritycontext"}},[e._v("#")]),e._v(" @withSecurityContext")]),e._v(" "),s("p",[e._v("我们已经看到,如果我们不使用自定义的"),s("code",[e._v("Authentication")]),e._v("原则,"),s("code",[e._v("@WithMockUser")]),e._v("是一个很好的选择。接下来,我们发现"),s("code",[e._v("@WithUserDetails")]),e._v("将允许我们使用自定义"),s("code",[e._v("UserDetailsService")]),e._v("来创建我们的"),s("code",[e._v("Authentication")]),e._v("主体,但需要用户存在。我们现在将看到一个允许最大灵活性的选项。")]),e._v(" "),s("p",[e._v("我们可以创建自己的注释,它使用"),s("code",[e._v("@WithSecurityContext")]),e._v("来创建我们想要的任何"),s("code",[e._v("SecurityContext")]),e._v("。例如,我们可以创建一个名为"),s("code",[e._v("@WithMockCustomUser")]),e._v("的注释,如下所示:")]),e._v(" "),s("p",[e._v("Java")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('@Retention(RetentionPolicy.RUNTIME)\n@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)\npublic @interface WithMockCustomUser {\n\n\tString username() default "rob";\n\n\tString name() default "Rob Winch";\n}\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('@Retention(AnnotationRetention.RUNTIME)\n@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory::class)\nannotation class WithMockCustomUser(val username: String = "rob", val name: String = "Rob Winch")\n')])])]),s("p",[e._v("你可以看到"),s("code",[e._v("@WithMockCustomUser")]),e._v("是用"),s("code",[e._v("@WithSecurityContext")]),e._v("注释的。这是向 Spring 安全性测试支持发出的信号,表示我们打算为测试创建"),s("code",[e._v("SecurityContext")]),e._v("。"),s("code",[e._v("@WithSecurityContext")]),e._v("注释要求我们指定一个"),s("code",[e._v("SecurityContextFactory")]),e._v(",给定我们的"),s("code",[e._v("@WithMockCustomUser")]),e._v("注释,它将创建一个新的"),s("code",[e._v("SecurityContext")]),e._v("注释。你可以在下面找到我们的"),s("code",[e._v("WithMockCustomUserSecurityContextFactory")]),e._v("实现:")]),e._v(" "),s("p",[e._v("Java")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('public class WithMockCustomUserSecurityContextFactory\n\timplements WithSecurityContextFactory {\n\t@Override\n\tpublic SecurityContext createSecurityContext(WithMockCustomUser customUser) {\n\t\tSecurityContext context = SecurityContextHolder.createEmptyContext();\n\n\t\tCustomUserDetails principal =\n\t\t\tnew CustomUserDetails(customUser.name(), customUser.username());\n\t\tAuthentication auth =\n\t\t\tnew UsernamePasswordAuthenticationToken(principal, "password", principal.getAuthorities());\n\t\tcontext.setAuthentication(auth);\n\t\treturn context;\n\t}\n}\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('class WithMockCustomUserSecurityContextFactory : WithSecurityContextFactory {\n override fun createSecurityContext(customUser: WithMockCustomUser): SecurityContext {\n val context = SecurityContextHolder.createEmptyContext()\n val principal = CustomUserDetails(customUser.name, customUser.username)\n val auth: Authentication =\n UsernamePasswordAuthenticationToken(principal, "password", principal.authorities)\n context.authentication = auth\n return context\n }\n}\n')])])]),s("p",[e._v("我们现在可以使用新的注释对测试类或测试方法进行注释,并且 Spring Security的"),s("code",[e._v("WithSecurityContextTestExecutionListener")]),e._v("将确保我们的"),s("code",[e._v("SecurityContext")]),e._v("被适当地填充。")]),e._v(" "),s("p",[e._v("在创建你自己的"),s("code",[e._v("WithSecurityContextFactory")]),e._v("实现时,很高兴知道它们可以使用标准 Spring 注释进行注释。例如,"),s("code",[e._v("WithUserDetailsSecurityContextFactory")]),e._v("使用"),s("code",[e._v("@Autowired")]),e._v("注释来获取"),s("code",[e._v("UserDetailsService")]),e._v(":")]),e._v(" "),s("p",[e._v("Java")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('final class WithUserDetailsSecurityContextFactory\n\timplements WithSecurityContextFactory {\n\n\tprivate UserDetailsService userDetailsService;\n\n\t@Autowired\n\tpublic WithUserDetailsSecurityContextFactory(UserDetailsService userDetailsService) {\n\t\tthis.userDetailsService = userDetailsService;\n\t}\n\n\tpublic SecurityContext createSecurityContext(WithUserDetails withUser) {\n\t\tString username = withUser.value();\n\t\tAssert.hasLength(username, "value() must be non-empty String");\n\t\tUserDetails principal = userDetailsService.loadUserByUsername(username);\n\t\tAuthentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities());\n\t\tSecurityContext context = SecurityContextHolder.createEmptyContext();\n\t\tcontext.setAuthentication(authentication);\n\t\treturn context;\n\t}\n}\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('class WithUserDetailsSecurityContextFactory @Autowired constructor(private val userDetailsService: UserDetailsService) :\n WithSecurityContextFactory {\n override fun createSecurityContext(withUser: WithUserDetails): SecurityContext {\n val username: String = withUser.value\n Assert.hasLength(username, "value() must be non-empty String")\n val principal = userDetailsService.loadUserByUsername(username)\n val authentication: Authentication =\n UsernamePasswordAuthenticationToken(principal, principal.password, principal.authorities)\n val context = SecurityContextHolder.createEmptyContext()\n context.authentication = authentication\n return context\n }\n}\n')])])]),s("p",[e._v("默认情况下,"),s("code",[e._v("SecurityContext")]),e._v("是在"),s("code",[e._v("TestExecutionListener.beforeTestMethod")]),e._v("事件期间设置的。这相当于发生在JUnit的"),s("code",[e._v("@Before")]),e._v("之前。你可以将此更改为在"),s("code",[e._v("TestExecutionListener.beforeTestExecution")]),e._v("事件期间发生,该事件发生在JUnit的"),s("code",[e._v("@Before")]),e._v("之后,但在调用测试方法之前。")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("@WithSecurityContext(setupBefore = TestExecutionEvent.TEST_EXECUTION)\n")])])]),s("h2",{attrs:{id:"测试元注释"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#测试元注释"}},[e._v("#")]),e._v(" 测试元注释")]),e._v(" "),s("p",[e._v("如果你经常在测试中重用同一个用户,那么必须反复指定属性是不理想的。例如,如果有许多与用户名为“admin”的管理用户以及角色"),s("code",[e._v("ROLE_USER")]),e._v("和"),s("code",[e._v("ROLE_ADMIN")]),e._v("相关的测试,则必须编写:")]),e._v(" "),s("p",[e._v("Java")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('@WithMockUser(username="admin",roles={"USER","ADMIN"})\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('@WithMockUser(username="admin",roles=["USER","ADMIN"])\n')])])]),s("p",[e._v("我们可以使用元注释,而不是到处重复这一点。例如,我们可以创建一个名为"),s("code",[e._v("WithMockAdmin")]),e._v("的元注释:")]),e._v(" "),s("p",[e._v("Java")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('@Retention(RetentionPolicy.RUNTIME)\n@WithMockUser(value="rob",roles="ADMIN")\npublic @interface WithMockAdmin { }\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('@Retention(AnnotationRetention.RUNTIME)\n@WithMockUser(value = "rob", roles = ["ADMIN"])\nannotation class WithMockAdmin\n')])])]),s("p",[e._v("现在我们可以使用"),s("code",[e._v("@WithMockAdmin")]),e._v(",就像使用更详细的"),s("code",[e._v("@WithMockUser")]),e._v("一样。")]),e._v(" "),s("p",[e._v("元注释可以与上面描述的任何测试注释一起工作。例如,这意味着我们也可以为"),s("code",[e._v('@WithUserDetails("admin")')]),e._v("创建一个元注释。")]),e._v(" "),s("p",[s("RouterLink",{attrs:{to:"/spring-security/index.html"}},[e._v("Testing")]),s("RouterLink",{attrs:{to:"/spring-security/mockmvc/index.html"}},[e._v("MockMVC支持")])],1)])}),[],!1,null,null,null);t.default=n.exports}}]);