Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SEC-2015: Create spring-security-test module #2241

Closed
spring-issuemaster opened this issue Jul 20, 2012 · 9 comments
Closed

SEC-2015: Create spring-security-test module #2241

spring-issuemaster opened this issue Jul 20, 2012 · 9 comments
Assignees
Milestone

Comments

@spring-issuemaster
Copy link

@spring-issuemaster spring-issuemaster commented Jul 20, 2012

Gaetan Pitteloud (Migrated from SEC-2015) said:

Updated description

Spring Security should provide mechanisms to make testing easier. Specifically:

  • Provide an easy way to run a test with a specific user for method based security
  • Provide an easy way to run a test with a specific user with MockMvc
  • Provide easy ways to add security related information (i.e. CsrfToken) to MockMvc requests
  • Provide easy way to assert security related information (i.e verify that a specific user is authenticated) after MockMvc requests

h2. Original request

There should be a way to write tests for classes that uses security features, by defining a security context (consisting of a user name and roles) around the test methods.

This is something similar to the @transactional feature that's available for tests. The "security context" that is set up for each test is synchronized to both :

  • request.getUserPrincipal(), request.isUserInRole(String) and request.getRemoteUser(), for a test that uses the WAC loading and mock request injection features defined in SPR-5243. Useful when testing web controllers that rely on these web security methods.
  • Authentication.getAuthorities() and Authentication.getPricipal() for a spring-security test. Useful when writing integration tests for a system using spring-security.

The idea is to provide a @Runas annotation with String username and String[] roles attributes. A TestExecutionListener parses this annotation, creates a TestingAuthenticationToken according to the annotation values, and stores it a SecurityContext that is statically accessible with a ThreadLocal strategy in SecurityContextHolder.

For a web test, wrap the spring MockHttpServletRequest that is created by the web test features in a SecurityContextHolderAwareRequestWrapper.

@spring-issuemaster
Copy link
Author

@spring-issuemaster spring-issuemaster commented Jul 27, 2012

Rob Winch said:

The proposal for the @Runas annotation is a bit too specific for your usecase and is unlikely to be provided by the framework. We could however provide some sort of integration that would allow the MockHttpServletRequest to automatically integrate with the value of the SecurityContextHolder. The basic steps for a user would be to use the custom listener and then ensure a SecurityContetHolder was populated with the desired SecurityContext. The provided listener would then ensure that the Authentication on the Security context was used to populate the MockHttpServletRequest

@spring-issuemaster
Copy link
Author

@spring-issuemaster spring-issuemaster commented Mar 14, 2013

Rob Winch said:

I realized I did not explain why I felt that @Runas was a bit too specific, so I thought I would provide some additional explanation.

The problem with using the annotations to specify the roles is that it falls short when following Spring Security best practices. Typically we recommend developers create their own object (i.e. MyUserImpl) that extends Spring Security's User object and implements their own internal concept of a user (i.e. MyUser). When they refer to their concept of a user they should do so through the MyUser interface. This ensures that Spring Security's understanding of the user (UserDetails) and the applications understanding of a user (MyUser) are kept separate.

Not only is it best practice to do this, but extending the default User object is often necessary for all but the most trivial applications. For example, the Spring Security User object does not have a name or even and ID on it. In order to get this data associated with a user, the Spring Security's default User object must be extended. If any of the data is required in the tests, the annotations concept would not be able to be setup easily since we do not know what the MyUser interface looks like.

PS: Attributes like the name attribute are unlikely to be added because we don't want to unnecessarily add requirements to Spring Security's APIs (Spring Security doesn't need a name so why require a developer provide the name)

@spring-issuemaster
Copy link
Author

@spring-issuemaster spring-issuemaster commented Sep 19, 2013

Olli Varis said:

I'm using approach in unit tests mentioned in link above, I was just wondering is it possible to use different @Profile for testing so that test profile would use in-memory userdetailsservice and other profile jdbc or what ever. I guess the question is how to implement configure class which extends WebSecurityConfigurerAdapter and the registerAuthentication-method. Both profiles would still use the same configure(HttpSecurity http), only the registerAuthentication() part would differ.

@spring-issuemaster
Copy link
Author

@spring-issuemaster spring-issuemaster commented Sep 19, 2013

Rob Winch said:

ovaris If you wish you can externalize the Authentication Configuration with something like this:

@Configuration
public class AuthenticationConfiguration {

    @Autowire
    public void registerAuthentication(AuthenticationManagerBuilder auth) {
        // ... method name does not matter ...
        // ... configure your authentication here ...
    }
}

Then you will no longer use WebSecurityConfigurerAdapter's registerAuthentication and WebSecurityConfigurerAdapter will automatically pick up the AuthenticationManagerBuilder you configured using @Autowired. You can then add @Profile to your AuthenticationConfiguration and create a testing in memory one as well.

@spring-issuemaster
Copy link
Author

@spring-issuemaster spring-issuemaster commented Sep 24, 2013

Olli Varis said:

Hi, okay, thanks, I'm getting error in my test now :

java.lang.NullPointerException
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername(WebSecurityConfigurerAdapter.java:354)
    at xxx.controller.SecurityRequestPostProcessors$UserDetailsRequestPostProcessor.authentication(SecurityRequestPostProcessors.java:203)
    at xxx.controller.SecurityRequestPostProcessors$UserDetailsRequestPostProcessor.postProcessRequest(SecurityRequestPostProcessors.java:195)

which happens when I try to load user in my test case (userDeatilsService is copied from your example SpringSecurityTests.java) :

@Test
    public void accessGranted() throws Exception {
        mockMvc.perform(get("/user").with(userDeatilsService("user")))
                .andExpect(status().isOk());
    }

Here's my SecurityConfig class:

/**
 * Spring Security Configuration.
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    public UserDetailsService userDetailsServiceBean() throws Exception {
        return super.userDetailsServiceBean();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web
                .ignoring()
                .antMatchers("/resources/**", "/favicon.ico");
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean()
            throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/admin**").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/signin")
                .permitAll()
                .and()
                .logout()
                .logoutUrl("/signout");

    }

    /**
     * Embedded Security configuration (not secure).
     */
    @Configuration
    @Profile("embedded")
    static class Embedded {

        @Bean
        public PasswordEncoder passwordEncoder() {
            return NoOpPasswordEncoder.getInstance();
        }

        @Bean
        public TextEncryptor textEncryptor() {
            return Encryptors.noOpText();
        }


        @Autowired
        public void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
            auth
                    .inMemoryAuthentication()
                    .withUser("user").password("password").roles("USER").and()
                    .withUser("admin").password("password").roles("USER", "ADMIN");
        }
    }

    /**
     * Standard security configuration (secure).
     */
    @Configuration
    @Profile("standard")
    static class Standard {

        @Inject
        private Environment environment;

        @Bean
        public PasswordEncoder passwordEncoder() {
            return new StandardPasswordEncoder(getEncryptPassword());
        }

        @Bean
        public TextEncryptor textEncryptor() {
            return Encryptors.queryableText(getEncryptPassword(), environment.getProperty("security.encryptSalt"));
        }

        private String getEncryptPassword() {
            return environment.getProperty("security.encryptPassword");
        }

        @Autowired
        public void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
            auth
                    .jdbcAuthentication()
                    .usersByUsernameQuery("select u.username, u.password, u.active from user_resource")
                    .authoritiesByUsernameQuery("select usr.username, role.user_role_type from user_resource as usr, " +
                            "user_resource_user_role as userRole, user_role as role where usr.username=?");
        }

    }

}

Configuration above works fine when I remove autowired registerAuthentication and just override registerAuthentication in base configuration class but then I would loose the ability to use profile to determine correct userdetailsservice implementation.

@spring-issuemaster
Copy link
Author

@spring-issuemaster spring-issuemaster commented Apr 22, 2014

Rob Winch said:

There is a new module named spring-security-test available in the snapshot repository now. While there isn't concrete documentation, you can check out the showcase package within the tests. It demonstrates how you can use annotations to run as a particular user for both web requests and method tests. It also demonstrates some of the security related matchers, request post processors (i.e. csrf), and security request builders.

@spring-issuemaster
Copy link
Author

@spring-issuemaster spring-issuemaster commented May 14, 2014

Grigory Kislin said:

Hello.
I'd like to add security to my spring mvc test.
But code in sample

    this.mockMvc.perform(get("/").with(userDeatilsService("user")))
    this.mockMvc.perform(get("/").with(user("user").roles("DENIED")))

requered org.springframework.test.web.server.samples.context.SecurityRequestPostProcessors with is missed in spring-test-4.0.3.RELEASE.jar
(as well as in spring-test-mvc-1.0.0.M2.jar)

So are there any better solution iso copy this class to own project?
Thank you in advance,
Grigory.

@spring-issuemaster
Copy link
Author

@spring-issuemaster spring-issuemaster commented May 14, 2014

Rob Winch said:

GKislin Please see my response on SEC-2592

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants
You can’t perform that action at this time.