Spring Security 實(shí)戰(zhàn)干貨:Spring Security中的單元測(cè)試
今天組里的新人迷茫的問我:哥,Spring Security弄的我單元測(cè)試跑不起來,總是401,你看看咋解決。沒問題,有寫單元測(cè)試的覺悟,寫的代碼質(zhì)量肯定有保證,對(duì)代碼質(zhì)量重視的態(tài)度,這種忙一定要幫!
Spring Security 測(cè)試環(huán)境
要想在單元測(cè)試中使用Spring Security,你需要在Spring Boot項(xiàng)目中集成:
- <dependency>
 - <groupId>org.springframework.security</groupId>
 - <artifactId>spring-security-test</artifactId>
 - <scope>test</scope>
 - </dependency>
 
這樣測(cè)試的上下文配置就能和Spring Security結(jié)合起來了,接下來教你幾招。
Spring Security 測(cè)試
所有的測(cè)試都是在Spring Boot Test下進(jìn)行的,也就是@SpringBootTest注解的支持下。
@WithMockUser
@WithMockUser注解可以幫我們?cè)赟pring Security安全上下文中模擬一個(gè)默認(rèn)名稱為user,默認(rèn)密碼為password,默認(rèn)角色為USER的用戶。當(dāng)你的測(cè)試方法使用了該注解后,你就能通過:
- Authentication authentication = SecurityContextHolder.getContext()
 - .getAuthentication();
 
獲取該模擬用戶的信息,也就“假裝”當(dāng)前登錄了用戶user。當(dāng)然你也可以根據(jù)需要來自定義用戶名、密碼、角色:
- @SneakyThrows
 - @Test
 - @WithMockUser(username = "felord",password = "felord.cn",roles = {"ADMIN"})
 - void updatePassword() {
 - mockMvc.perform(post("/user/update/password")
 - .contentType(MediaType.APPLICATION_JSON)
 - .content("{\n" +
 - " \"newPassword\": \"12345\",\n" +
 - " \"oldPassword\": \"12345\"\n" +
 - "}"))
 - .andExpect(ResultMatcher.matchAll(status().isOk()))
 - .andDo(print());
 - }
 
當(dāng)然你可以將@WithMockUser標(biāo)記到整個(gè)測(cè)試類上,這樣每個(gè)測(cè)試都將使用指定該用戶。
@WithAnonymousUser
@WithAnonymousUser是用來模擬一種特殊的用戶,也被叫做匿名用戶。如果有測(cè)試匿名用戶的需要,可以直接使用該注解。其實(shí)等同于@WithMockUser(roles = {"ANONYMOUS"}),也等同于@WithMockUser(authorities = {"ROLE_ANONYMOUS"}),細(xì)心的你應(yīng)該能看出來差別。
@WithUserDetails
雖然@WithMockUser是一種非常方便的方式,但可能并非在所有情況下都湊效。有時(shí)候你魔改了一些東西使得安全上下文的驗(yàn)證機(jī)制發(fā)生了改變,比如你定制了UserDetails,這一類注解就不好用了。但是通過UserDetailsService 加載的用戶往往還是可靠的。于是@WithUserDetails就派上了用場(chǎng)。
- @SneakyThrows
 - @Test
 - @WithUserDetails("felord")
 - void updatePassword() {
 - mockMvc.perform(post("/user/update/password")
 - .contentType(MediaType.APPLICATION_JSON)
 - .content("{\n" +
 - " \"newPassword\": \"12345\",\n" +
 - " \"oldPassword\": \"12345\"\n" +
 - "}"))
 - .andExpect(ResultMatcher.matchAll(status().isOk()))
 - .andDo(print());
 - }
 
當(dāng)我們執(zhí)行單元測(cè)試時(shí),將通過UserDetailsService 的loadUserByUsername方法查找用戶名為felord的用戶并加載到安全上下文中。
自定義注解
其實(shí)我們還可以模擬@WithMockUser
- @Target({ ElementType.METHOD, ElementType.TYPE })
 - @Retention(RetentionPolicy.RUNTIME)
 - @Inherited
 - @Documented
 - @WithSecurityContext(factory = WithMockUserSecurityContextFactory.class)
 - public @interface WithMockUser {
 - String value() default "user";
 - String username() default "";
 - String[] roles() default { "USER" };
 - String[] authorities() default {};
 - String password() default "password";
 - @AliasFor(annotation = WithSecurityContext.class)
 - TestExecutionEvent setupBefore() default TestExecutionEvent.TEST_METHOD;
 - }
 
關(guān)鍵就在于@WithSecurityContext注解,我們只需要實(shí)現(xiàn)factory就行了,也就是:
- public interface WithSecurityContextFactory<A extends Annotation> {
 - SecurityContext createSecurityContext(A annotation);
 - }
 
這里如法炮制就行,沒什么難度就不演示了。
總結(jié)
今天介紹了當(dāng)你的應(yīng)用中集成了Spring Security時(shí)如何單元測(cè)試,我們可以使用提供的模擬用戶的注解,也可以模擬加載用戶,甚至你可以根據(jù)自己的需要來定制化。其實(shí)如果你使用了JWT的話還有種野路子,你可以在Spring MVC Mock測(cè)試中加入對(duì)應(yīng)的請(qǐng)求頭或者參數(shù),也能順利進(jìn)行。















 
 
 










 
 
 
 