使用 Spring 安全测试来测试安全的 Spring MVC 控制器

Using Spring security test to test a secured Spring MVC controller

遵循 documentation 关于使用 Spring 安全测试 为连接在 [=40 后面的 spring MVC 应用程序编写测试=]Spring安全.

这是一个香草 spring-boot 应用程序,采用典型的 spring-security wiring。这是主要的 Application.java

@SpringBootApplication
public class Application {

    private static final Logger log = LoggerFactory.getLogger(Application.class);

    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}

这是 spring-安全的接线:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    @Qualifier("customUserDetailsService")
    UserDetailsService userDetailsService;

    @Autowired
    public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/","/sign_up").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .and()
            .csrf()
                .and()
            .exceptionHandling()
                .accessDeniedPage("/access_denied")
                .and()
            .logout()
                .permitAll();
    }
}

如您所见,除了“/”和“/sign_up”之外的所有请求都需要进行身份验证。我已经通过部署应用程序验证身份验证方案工作正常。


现在是有趣的部分:编写 spring mvc 测试。我提供的 link 提供了一些编写此类测试的好方法,其中 spring-security-test 框架允许插入模拟 users/security-contexts。我采取了

的方法
  1. 定义模拟 UserDetails 接口,并且
  2. 使用 SecurityContextFactory 创建一个 SecurityContext,
  3. 应在测试启动期间插入到应用程序上下文中。

1.的代码如下:

@WithSecurityContext(factory=WithMockUserDetailsSecurityContextFactory.class)
public @interface WithMockUserDetails {
    String firstName() default "apil";
    String lastName() default "tamang";
    String password() default "test";
    long id() default 999;
    String email() default "apil@test.com";


}

2.的代码如下:

final class WithMockUserDetailsSecurityContextFactory
    implements WithSecurityContextFactory<WithMockUserDetails>{


    @Override
    public SecurityContext createSecurityContext(WithMockUserDetails mockUserDetails) {


        /*
         * Use an anonymous implementation for 'UserDetails' to return a
         * mock authentication object, which is then set to the SecurityContext
         * for the test runs.
         */
        UserDetails principal=new UserDetails() {
            @Override
            public Collection<? extends GrantedAuthority> getAuthorities() {

                //another anonmyous interface implementation.
                GrantedAuthority auth=new GrantedAuthority() {
                    @Override
                    public String getAuthority() {
                        return "ROLE_USER";
                    }
                };
                List<GrantedAuthority> authorities=new ArrayList<>();
                authorities.add(auth);
                return authorities;
            }

            @Override
            public String getPassword() {
                return mockUserDetails.password();
            }

            @Override
            public String getUsername() {
                return mockUserDetails.email();
            }

            @Override
            public boolean isAccountNonExpired() {
                return true;
            }

            @Override
            public boolean isAccountNonLocked() {
                return true;
            }

            @Override
            public boolean isCredentialsNonExpired() {
                return true;
            }

            @Override
            public boolean isEnabled() {
                return true;
            }
        };
        Authentication authentication=new
                UsernamePasswordAuthenticationToken(principal,principal.getPassword(),principal.getAuthorities());
        SecurityContext context= SecurityContextHolder.createEmptyContext();
        context.setAuthentication(authentication);
        return context;
    }
}

最后,下面是测试class:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {Application.class})
@WebAppConfiguration
public class UserControllerTest {

    @Autowired
    private WebApplicationContext context;

    @Autowired
    private Filter springSecurityFilterChain;

    private MockMvc mvc;

    @Before
    public void setup(){
        mvc= MockMvcBuilders
                .webAppContextSetup(context)                    
                .apply(springSecurity())
                .build();
    }

    @Test
    public void testRootIsOk() throws Exception {
        mvc.perform(get("/"))
                .andExpect(status().isOk());
    }

    @Test
    public void expectRedirectToLogin() throws Exception {
        mvc.perform(get("/testConnect"))
                .andExpect(redirectedUrl("http://localhost/login"));

    }

    @Test
    @WithMockUserDetails
    public void testAuthenticationOkay() throws Exception {
        mvc.perform(get("/testConnect"))
                .andExpect(content().string("good request."));
    }

}

测试输出运行:

  1. 测试 1 通过。
  2. 测试 2 次通过。
  3. 测试 3 失败。预期输出但得到 <>。

测试 3 很可能失败,因为 'SecurityContext' 从未得到适当填充。根据文档,它应该有效。不知道我错过了什么。非常感谢任何帮助。

您需要添加

@Retention(RetentionPolicy.RUNTIME)

添加到您的自定义注释,以便在运行时保留注释信息。否则 WithSecurityContextTestExecutionListener 无法检测到您的注释。