Spring Security(三十五):Part III. Testing
This section describes the testing support provided by Spring Security.
To use the Spring Security test support, you must include spring-security-test-4.2.10.RELEASE.jar as a dependency of your project.
11. Testing Method Security
This section demonstrates how to use Spring Security’s Test support to test method based security. We first introduce a MessageService that requires the user to be authenticated in order to access it.
public class HelloMessageService implements MessageService {
@PreAuthorize("authenticated")
public String getMessage() {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
return "Hello " + authentication;
}
}
The result of getMessage is a String saying "Hello" to the current Spring Security Authentication. An example of the output is displayed below.
Hello org.springframework.security.authentication.UsernamePasswordAuthenticationToken@ca25360: Principal: org.springframework.security.core.userdetails.User@36ebcb: 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
11.1 Security Test Setup
Before we can use Spring Security Test support, we must perform some setup. An example can be seen below:
@RunWith(SpringJUnit4ClassRunner.class) 1
@ContextConfiguration 2
public class WithMockUserTests {
This is a basic example of how to setup Spring Security Test. The highlights are:
1、@RunWith instructs the spring-test module that it should create an ApplicationContext. This is no different than using the existing Spring Test support. For additional information, refer to the Spring Reference2、@ContextConfiguration instructs the spring-test the configuration to use to create the ApplicationContext. Since no configuration is specified, the default configuration locations will be tried. This is no different than using the existing Spring Test support. For additional information, refer to the Spring ReferenceWithSecurityContextTestExecutionListener which will ensure our tests are ran with the correct user. It does this by populating the SecurityContextHolder prior to running our tests. After the test is done, it will clear out the SecurityContextHolder. If you only need Spring Security related support, you can replace @ContextConfiguration with @SecurityTestExecutionListeners.@PreAuthorize annotation to our HelloMessageService and so it requires an authenticated user to invoke it. If we ran the following test, we would expect the following test will pass:@Test(expected = AuthenticationCredentialsNotFoundException.class)
public void getMessageUnauthenticated() {
messageService.getMessage();
}
11.2 @WithMockUser
The question is "How could we most easily run the test as a specific user?" The answer is to use @WithMockUser. The following test will be run as a user with the username "user", the password "password", and the roles "ROLE_USER".
@Test
@WithMockUser
public void getMessageWithMockUser() {
String message = messageService.getMessage();
...
}
Specifically the following is true:
- The user with the username "user" does not have to exist since we are mocking the user
- 用户名为“user”的用户不必存在,因为我们正在模拟用户
- The
Authenticationthat is populated in theSecurityContextis of typeUsernamePasswordAuthenticationToken - SecurityContext中填充的身份验证的类型为UsernamePasswordAuthenticationToken
- The principal on the
Authenticationis Spring Security’sUserobject - 身份验证的主体是Spring Security的User对象
- The
Userwill have the username of "user", the password "password", and a singleGrantedAuthoritynamed "ROLE_USER" is used. - 用户将拥有“user”的用户名,密码“password”,并使用名为“ROLE_USER”的单个GrantedAuthority。
Our example is nice because we are able to leverage a lot of defaults. What if we wanted to run the test with a different username? The following test would run with the username "customUser". Again, the user does not need to actually exist.
@Test
@WithMockUser("customUsername")
public void getMessageWithMockUserCustomUsername() {
String message = messageService.getMessage();
...
}
We can also easily customize the roles. For example, this test will be invoked with the username "admin" and the roles "ROLE_USER" and "ROLE_ADMIN".
@Test
@WithMockUser(username="admin",roles={"USER","ADMIN"})
public void getMessageWithMockUserCustomUser() {
String message = messageService.getMessage();
...
}
If we do not want the value to automatically be prefixed with ROLE_ we can leverage the authorities attribute. For example, this test will be invoked with the username "admin" and the authorities "USER" and "ADMIN".
@Test
@WithMockUser(username = "admin", authorities = { "ADMIN", "USER" })
public void getMessageWithMockUserCustomAuthorities() {
String message = messageService.getMessage();
...
}
Of course it can be a bit tedious placing the annotation on every test method. Instead, we can place the annotation at the class level and every test will use the specified user. For example, the following would run every test with a user with the username "admin", the password "password", and the roles "ROLE_USER" and "ROLE_ADMIN".
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WithMockUser(username="admin",roles={"USER","ADMIN"})
public class WithMockUserTests {
11.3 @WithAnonymousUser
Using @WithAnonymousUser allows running as an anonymous user. This is especially convenient when you wish to run most of your tests with a specific user, but want to run a few tests as an anonymous user. For example, the following will run withMockUser1 and withMockUser2 using @WithMockUser and anonymous as an anonymous user.
@RunWith(SpringJUnit4ClassRunner.class)
@WithMockUser
public class WithUserClassLevelAuthenticationTests { @Test
public void withMockUser1() {
} @Test
public void withMockUser2() {
} @Test
@WithAnonymousUser
public void anonymous() throws Exception {
// override default to run as anonymous user
}
}
11.4 @WithUserDetails
While @WithMockUser is a very convenient way to get started, it may not work in all instances. For example, it is common for applications to expect that the Authentication principal be of a specific type. This is done so that the application can refer to the principal as the custom type and reduce coupling on Spring Security.
UserDetailsService that returns an object that implements both UserDetails and the custom type. For situations like this, it is useful to create the test user using the custom UserDetailsService. That is exactly what @WithUserDetails does.UserDetailsService exposed as a bean, the following test will be invoked with an Authentication of type UsernamePasswordAuthenticationToken and a principal that is returned from the UserDetailsService with the username of "user".@Test
@WithUserDetails
public void getMessageWithUserDetails() {
String message = messageService.getMessage();
...
}
We can also customize the username used to lookup the user from our UserDetailsService. For example, this test would be executed with a principal that is returned from the UserDetailsService with the username of "customUsername".
@Test
@WithUserDetails("customUsername")
public void getMessageWithUserDetailsCustomUsername() {
String message = messageService.getMessage();
...
}
We can also provide an explicit bean name to look up the UserDetailsService. For example, this test would look up the username of "customUsername" using the UserDetailsService with the bean name "myUserDetailsService".
@Test
@WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService")
public void getMessageWithUserDetailsServiceBeanName() {
String message = messageService.getMessage();
...
}
Like @WithMockUser we can also place our annotation at the class level so that every test uses the same user. However unlike @WithMockUser, @WithUserDetailsrequires the user to exist.
11.5 @WithSecurityContext
We have seen that @WithMockUser is an excellent choice if we are not using a custom Authentication principal. Next we discovered that @WithUserDetails would allow us to use a custom UserDetailsService to create our Authentication principal but required the user to exist. We will now see an option that allows the most flexibility.
@WithSecurityContext to create any SecurityContext we want. For example, we might create an annotation named @WithMockCustomUser as shown below:@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public @interface WithMockCustomUser { String username() default "rob"; String name() default "Rob Winch";
}
You can see that @WithMockCustomUser is annotated with the @WithSecurityContext annotation. This is what signals to Spring Security Test support that we intend to create a SecurityContext for the test. The @WithSecurityContext annotation requires we specify a SecurityContextFactory that will create a new SecurityContext given our @WithMockCustomUser annotation. You can find our WithMockCustomUserSecurityContextFactory implementation below:
public class WithMockCustomUserSecurityContextFactory
implements WithSecurityContextFactory<WithMockCustomUser> {
@Override
public SecurityContext createSecurityContext(WithMockCustomUser customUser) {
SecurityContext context = SecurityContextHolder.createEmptyContext(); CustomUserDetails principal =
new CustomUserDetails(customUser.name(), customUser.username());
Authentication auth =
new UsernamePasswordAuthenticationToken(principal, "password", principal.getAuthorities());
context.setAuthentication(auth);
return context;
}
}
We can now annotate a test class or a test method with our new annotation and Spring Security’s WithSecurityContextTestExecutionListener will ensure that our SecurityContext is populated appropriately.
WithSecurityContextFactory implementations, it is nice to know that they can be annotated with standard Spring annotations. For example, the WithUserDetailsSecurityContextFactory uses the @Autowired annotation to acquire the UserDetailsService:final class WithUserDetailsSecurityContextFactory
implements WithSecurityContextFactory<WithUserDetails> { private UserDetailsService userDetailsService; @Autowired
public WithUserDetailsSecurityContextFactory(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
} public SecurityContext createSecurityContext(WithUserDetails withUser) {
String username = withUser.value();
Assert.hasLength(username, "value() must be non-empty String");
UserDetails principal = userDetailsService.loadUserByUsername(username);
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities());
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
return context;
}
}
11.6 Test Meta Annotations
If you reuse the same user within your tests often, it is not ideal to have to repeatedly specify the attributes. For example, if there are many tests related to an administrative user with the username "admin" and the roles ROLE_USER and ROLE_ADMIN you would have to write:
@WithMockUser(username="admin",roles={"USER","ADMIN"})
Rather than repeating this everywhere, we can use a meta annotation. For example, we could create a meta annotation named WithMockAdmin:
@Retention(RetentionPolicy.RUNTIME)
@WithMockUser(value="rob",roles="ADMIN")
public @interface WithMockAdmin { }
Now we can use @WithMockAdmin in the same way as the more verbose @WithMockUser.
Meta annotations work with any of the testing annotations described above. For example, this means we could create a meta annotation for @WithUserDetails("admin") as well.
Spring Security(三十五):Part III. Testing的更多相关文章
- Spring Security(十五):5.6 Authentication
Thus far we have only taken a look at the most basic authentication configuration. Let’s take a look ...
- 精选Spring Boot三十五道必知必会知识点
Spring Boot 是微服务中最好的 Java 框架. 我们建议你能够成为一名 Spring Boot 的专家.本文精选了三十五个常见的Spring Boot知识点,祝你一臂之力! 问题一 Spr ...
- NeHe OpenGL教程 第三十五课:播放AVI
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- spring boot / cloud (十五) 分布式调度中心进阶
spring boot / cloud (十五) 分布式调度中心进阶 在<spring boot / cloud (十) 使用quartz搭建调度中心>这篇文章中介绍了如何在spring ...
- JAVA之旅(三十五)——完结篇,终于把JAVA写完了,真感概呐!
JAVA之旅(三十五)--完结篇,终于把JAVA写完了,真感概呐! 这篇博文只是用来水经验的,写这个系列是因为我自己的java本身也不是特别好,所以重温了一下,但是手比较痒于是就写出了这三十多篇博客了 ...
- Java进阶(三十五)java int与integer的区别
Java进阶(三十五)java int与Integer的区别 前言 int与Integer的区别从大的方面来说就是基本数据类型与其包装类的区别: int 是基本类型,直接存数值,而Integer是对象 ...
- Gradle 1.12用户指南翻译——第三十五章. Sonar 插件
本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...
- SQL注入之Sqli-labs系列第三十四关(基于宽字符逃逸POST注入)和三十五关
开始挑战第三十四关和第三十五关(Bypass add addslashes) 0x1查看源码 本关是post型的注入漏洞,同样的也是将post过来的内容进行了 ' \ 的处理. if(isset($_ ...
- “全栈2019”Java多线程第三十五章:如何获取线程被等待的时间?
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- Python进阶(三十五)-Fiddler命令行和HTTP断点调试
Python进阶(三十五)-Fiddler命令行和HTTP断点调试 一. Fiddler内置命令 上一节(使用Fiddler进行抓包分析)中,介绍到,在web session(与我们通常所说的se ...
随机推荐
- 16-Flink-Redis-Sink
戳更多文章: 1-Flink入门 2-本地环境搭建&构建第一个Flink应用 3-DataSet API 4-DataSteam API 5-集群部署 6-分布式缓存 7-重启策略 8-Fli ...
- [九]基础数据类型之Boolean详解
相对于其他的基础性 类型Boolean是很简单的 Boolean 基本数据类型boolean 的包装类 Boolean 类型的对象包含一个 boolean 类型的字段 属性简介 属性也比较 ...
- 讲一下Asp.net core MVC2.1 里面的 ApiControllerAttribute
先贴文章链接 正文 ASP.NET Core MVC 2.1 特意为构建 HTTP API 提供了一些小特性,今天主角就是 ApiControllerAttribute. (注:文章是18年2月份的, ...
- python学习笔记(七)、异常
在实际开发中,往往不会出现一个系统编写下来,没有出现任何一个bug.为处理这种会出现错误的地方,如除数为零的除法,python提供了非常强大的机制————异常处理机制. 1 异常 python使用异常 ...
- Java开发笔记(五十八)简单接口及其实现
前面介绍了抽象方法及抽象类的用法,看似解决了不确定行为的方法定义,既然叫唤动作允许声明为抽象方法,那么飞翔.游泳也能声明为抽象方法,并且鸡类涵盖的物种不够多,最好把这些行为动作扩展到鸟类这个群体,于是 ...
- 当placeholder的字体大小跟input大小不一致,placeholder垂直居中
当placeholder的字体大小跟input大小不一致,实现placeholder垂直居中 设计稿的placeholder的样式是这样的 输入值的时候大小是这样的 最后想要实现的效果是这样的 当我这 ...
- 解决: 移动端经mouseover显示出的弹层中链接点击问题
通常我们会遇到这样的需求,导航菜单在鼠标划过的时候显示自定义弹层,在弹层中有一些链接需要点击后跳转或者其他一些事件.比如: $(".menu li").on("mouse ...
- 【Dojo 1.x】笔记目录
学习笔记和教程是不同的,笔记是随心记,学到什么就写什么,我尽量按逻辑顺序写笔记. Dojo是什么? Dojo是这么一个JavaScript框架,区别于jQuery等小型类库,这个类库更合适于构建Web ...
- Android Studio撤销与SVN的关联
为何会记录这一个问题,主要是在做项目的过程中出现了一个奇怪的现象,就是直接在文件目录下使用svn上传文件的话,可以看到该目录是与SVN相关联的,可是到了用Android Studio上传代码的时候却发 ...
- Python枚举类
Enum可以把一组相关常量定义在一个class中,且class不可变,而且成员可以直接比较. 定义枚举类: from enum import Enum, unique @unique class We ...