[JAVA] Spring Security usage memo test

Basic and mechanical story Authentication / Authorization Story Remember-Me story CSRF story Session management story The story of the response header Method security story CORS story The story of Run-As The story of ACL Talk about cooperation with MVC and Boot

Extra edition What Spring Security can and cannot do

Spring Security test

Spring Security provides a mechanism to support testing with JUnit.

For example, the user used for testing can be fixed and the authority can be specified.

Hello World

Implementation

build.gradle


dependencies {
    compile 'org.springframework.security:spring-security-web:4.2.1.RELEASE'
    compile 'org.springframework.security:spring-security-config:4.2.1.RELEASE'
    testCompile 'junit:junit:4.12'★ Addition
    testCompile 'org.springframework.security:spring-security-test:4.2.1.RELEASE'★ Addition
}

--Added JUnit and Spring Test modules to dependencies

MyTestService.java


package sample.spring.security.test;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;

public class MyTestService {
    
    @PreAuthorize("authenticated")
    public String getMessage() {
        String name = SecurityContextHolder.getContext().getAuthentication().getName();
        return "Hello " + name;
    }
}

--Class to be tested --Check if authenticated with @PreAuthorize () --Get the name of the current ʻAuthentication and return it in the form "Hello Name" `

namespace

src/test/resources/test-applicationContext.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:sec="http://www.springframework.org/schema/security"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
         http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
         http://www.springframework.org/schema/security
         http://www.springframework.org/schema/security/spring-security.xsd">

    <sec:global-method-security pre-post-annotations="enabled" />
    
    <bean class="sample.spring.security.test.MyTestService" />
    
</beans>

--Spring config file for testing --Actually, I think that you should test using the same configuration file as the production, but since the purpose of this is to verify the operation, we have prepared another file --Turn on method security and register the class under test as a bean

MyTestServiceTest.java


package sample.spring.security.test;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/test-applicationContext.xml")
public class MyTestServiceTest {
    
    @Autowired
    private MyTestService service;

    @Test(expected = AuthenticationException.class)
    public void test_getMessage_no_authentication() throws Exception {
        // exercise
        this.service.getMessage();
    }

    @Test
    @WithMockUser(username = "hoge")
    public void test_getMessage() throws Exception {
        // exercise
        String message = this.service.getMessage();
        
        // verify
        Assert.assertEquals("Hello hoge", message);
    }
}

--Test class --Reading the test configuration file with @ContextConfiguration --Testing that ʻAuthenticationException is thrown when getMessage () is executed without specifying @WithMockUser --If you specify@WithMockUser, you are testing that the return message is constructed with the string specified by @WithMockUser`.

Java Configuration

MyTestSpringSecurityConfig.java


package sample.spring.security.test;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class MyTestSpringSecurityConfig {
    
    @Bean
    public MyTestService myTestService() {
        return new MyTestService();
    }
}

--Spring configuration class for testing --The content is the same as the one in xml

MyTestServiceTest.java


package sample.spring.security.test;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=MyTestSpringSecurityConfig.class)
public class MyTestServiceTest {
    
    @Autowired
    private MyTestService service;

    @Test(expected = AuthenticationException.class)
    public void test_getMessage_no_authentication() throws Exception {
        // exercise
        this.service.getMessage();
    }

    @Test
    @WithMockUser(username = "hoge")
    public void test_getMessage() throws Exception {
        // exercise
        String message = this.service.getMessage();

        // verify
        Assert.assertEquals("Hello hoge", message);
    }
}

--This is also the same as xml except that the configuration class is loaded with @ContextConfiguration.

Operation check

If you run each, the test will succeed.

Description

Spring Test settings

MyTestServiceTest.java


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/test-applicationContext.xml")

--These two annotations are for Spring Test, not for Spring Security. --With the settings here, you are building a ʻApplicationContextfor testing. --In@ContextConfiguration, specify the Spring settings to use for testing. --For namespace, pass the location on the xml classpath, for Java Configuration, pass the Class` object of the configuration class

Specifying a mock user

MyTestServiceTest.java


    @Test
    @WithMockUser(username = "hoge")
    public void test_getMessage() throws Exception {

--By annotating with @WithMockUser, you can specify the user information (ʻAuthentication) that will be used while the test is running. --- Since username = "hoge" , ʻAuthentication with username hogewill be used. --Spring Security integrates with Spring Test to set and clearSecurityContextHolder` information before and after the test.

Annotation

Any information can be set in the SecurityContext under test by annotation.

@WithMockUser

MyTestServiceTest.java


package sample.spring.security.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/test-applicationContext.xml")
public class MyTestServiceTest {
    
    @Test
    @WithMockUser
    public void test() throws Exception {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String message = "class = " + auth.getClass() + "\n" +
                         "name = " + auth.getName() + "\n" +
                         "credentials = " + auth.getCredentials() + "\n" +
                         "authorities = " + auth.getAuthorities() + "\n" +
                         "principal = " + auth.getPrincipal() + "\n" +
                         "details = " + auth.getDetails();

        System.out.println(message);
    }
}

Execution result

class = class org.springframework.security.authentication.UsernamePasswordAuthenticationToken
name = user
credentials = password
authorities = [ROLE_USER]
principal = org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER
details = null

--Using @WithMockUser saves mock user information in SecurityContext --If nothing is specified, it has the following information by default. --Username is ʻuser --Password ispassword --Permission isROLE_USER --ʻUsernamePasswordAuthenticationToken is used to implement ʻAuthentication --The ʻUser object is used as the principal --ʻUser` is a mock user, so the user with that name doesn't really need to exist --It is also possible to annotate the class (in which case all methods will use the mock user)

Set user name

MyTestServiceTest.java


    @Test
    @WithMockUser("test-user")
    public void test() throws Exception {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String message = "class = " + auth.getClass() + "\n" +
                         "name = " + auth.getName() + "\n" +
                         "credentials = " + auth.getCredentials() + "\n" +
                         "authorities = " + auth.getAuthorities() + "\n" +
                         "principal = " + auth.getPrincipal() + "\n" +
                         "details = " + auth.getDetails();

        System.out.println(message);
    }

Execution result

class = class org.springframework.security.authentication.UsernamePasswordAuthenticationToken
name = test-user
credentials = password
authorities = [ROLE_USER]
principal = org.springframework.security.core.userdetails.User@b6e8d426: Username: test-user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER
details = null

--The character string set in value is used as the user name --Can be specified with ʻusername instead of value --When setting with roles described later, it is easier to understand if you use ʻusername rather than value.

Set role

MyTestServiceTest.java


    @Test
    @WithMockUser(
        username="test-user",
        roles={"FOO", "BAR"}
    )
    public void test() throws Exception {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String message = "class = " + auth.getClass() + "\n" +
                         "name = " + auth.getName() + "\n" +
                         "credentials = " + auth.getCredentials() + "\n" +
                         "authorities = " + auth.getAuthorities() + "\n" +
                         "principal = " + auth.getPrincipal() + "\n" +
                         "details = " + auth.getDetails();

        System.out.println(message);
    }

Execution result

class = class org.springframework.security.authentication.UsernamePasswordAuthenticationToken
name = test-user
credentials = test-pass
authorities = [ROLE_BAR, ROLE_FOO]
principal = org.springframework.security.core.userdetails.User@b6e8d426: Username: test-user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_BAR,ROLE_FOO
details = null

--By specifying roles, the authority to be automatically given the ROLE_ prefix is set. --An error occurs when set with ʻauthorities`

Set permissions

MyTestServiceTest.java


    @Test
    @WithMockUser(
        username="test-user",
        authorities={"FOO", "BAR"}
    )
    public void test() throws Exception {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String message = "class = " + auth.getClass() + "\n" +
                         "name = " + auth.getName() + "\n" +
                         "credentials = " + auth.getCredentials() + "\n" +
                         "authorities = " + auth.getAuthorities() + "\n" +
                         "principal = " + auth.getPrincipal() + "\n" +
                         "details = " + auth.getDetails();

        System.out.println(message);
    }

Execution result

class = class org.springframework.security.authentication.UsernamePasswordAuthenticationToken
name = test-user
credentials = password
authorities = [BAR, FOO]
principal = org.springframework.security.core.userdetails.User@b6e8d426: Username: test-user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: BAR,FOO
details = null

--Permissions are set by specifying ʻauthorities --An error occurs when set withroles`

Set password

MyTestServiceTest.java


    @Test
    @WithMockUser(password="test-pass")
    public void test() throws Exception {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String message = "class = " + auth.getClass() + "\n" +
                         "name = " + auth.getName() + "\n" +
                         "credentials = " + auth.getCredentials() + "\n" +
                         "authorities = " + auth.getAuthorities() + "\n" +
                         "principal = " + auth.getPrincipal() + "\n" +
                         "details = " + auth.getDetails();

        System.out.println(message);
    }

Execution result

class = class org.springframework.security.authentication.UsernamePasswordAuthenticationToken
name = user
credentials = test-pass
authorities = [ROLE_USER]
principal = org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER
details = null

--You can set a password with password

@WithAnonymousUser

MyTestServiceTest.java


package sample.spring.security.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.test.context.support.WithAnonymousUser;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@WithMockUser
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/test-applicationContext.xml")
public class MyTestServiceTest {

    @Test
    @WithAnonymousUser
    public void testAnonymous() throws Exception {
        this.printAuthentication("testAnonymous");
    }
    
    @Test
    public void testDefault() throws Exception {
        this.printAuthentication("testDefault");
    }

    private void printAuthentication(String testMethodName) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String message =
                "[" + testMethodName + "]\n" +
                "class = " + auth.getClass() + "\n" +
                "name = " + auth.getName() + "\n" +
                "credentials = " + auth.getCredentials() + "\n" +
                "authorities = " + auth.getAuthorities() + "\n" +
                "principal = " + auth.getPrincipal() + "\n" +
                "details = " + auth.getDetails();

        System.out.println(message);
    }
}

Execution result

[testAnonymous]
class = class org.springframework.security.authentication.AnonymousAuthenticationToken
name = anonymous
credentials = 
authorities = [ROLE_ANONYMOUS]
principal = anonymous
details = null

[testDefault]
class = class org.springframework.security.authentication.UsernamePasswordAuthenticationToken
name = user
credentials = password
authorities = [ROLE_USER]
principal = org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER
details = null

--Anonymous user is used when annotating with @WithAnonymousUser --Even if the class is annotated with @WithMockUser, the setting can be overridden by annotating the method with @WithAnonymousUser.

@WithUserDetails

MyUserDetailsService.java


package sample.spring.security.service;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.Collection;

public class MyUserDetailsService implements UserDetailsService {
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return new MyUser(username, "test-password", AuthorityUtils.createAuthorityList("FOO", "BAR"));
    }
    
    private static class MyUser extends User {
        private MyUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
            super(username, password, authorities);
        }
    }
}

test-applicationContext.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean class="sample.spring.security.service.MyUserDetailsService" />
</beans>

MyTestServiceTest.java


package sample.spring.security.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.test.context.support.WithUserDetails;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/test-applicationContext.xml")
public class MyTestServiceTest {

    @Test
    @WithUserDetails
    public void test() throws Exception {
        this.printAuthentication("test");
    }

    private void printAuthentication(String testMethodName) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String message =
                "[" + testMethodName + "]\n" +
                "class = " + auth.getClass() + "\n" +
                "name = " + auth.getName() + "\n" +
                "credentials = " + auth.getCredentials() + "\n" +
                "authorities = " + auth.getAuthorities() + "\n" +
                "principal = " + auth.getPrincipal() + "\n" +
                "details = " + auth.getDetails();

        System.out.println(message);
    }
}

Execution result

[test]
class = class org.springframework.security.authentication.UsernamePasswordAuthenticationToken
name = user
credentials = test-password
authorities = [BAR, FOO]
principal = sample.spring.security.service.MyUserDetailsService$MyUser@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: BAR,FOO
details = null

--If you annotate with @WithUserDetails, the authentication information will be constructed with the user information obtained from ʻUserDetailsService registered as a bean. --Use when using your own ʻUserDetails --@WithUserDetails`` value allows you to specify the name of the user to search for (default is " user ") --ʻUserDetailsServiceBeanName allows you to specify the ʻUserDetailsService bean to use

@WithSecurityContext

MyTestUser.java


package sample.spring.security.test;

import org.springframework.security.test.context.support.WithSecurityContext;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@WithSecurityContext(factory=MyTestUserFactory.class)
public @interface MyTestUser {
    String name();
    String pass();
    String authority();
}

MyTestUserFactory.java


package sample.spring.security.test;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.test.context.support.WithSecurityContextFactory;

public class MyTestUserFactory implements WithSecurityContextFactory<MyTestUser> {
    
    @Override
    public SecurityContext createSecurityContext(MyTestUser annotation) {
        SecurityContext context = SecurityContextHolder.createEmptyContext();

        String name = annotation.name();
        String pass = annotation.pass();
        String authority = annotation.authority();

        UserDetails user = new User(name, pass, AuthorityUtils.createAuthorityList(authority));
        Authentication authentication = new UsernamePasswordAuthenticationToken(
                                            user, user.getPassword(), user.getAuthorities());
        
        context.setAuthentication(authentication);

        return context;
    }
}

MyTestServiceTest.java


package sample.spring.security.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/test-applicationContext.xml")
public class MyTestServiceTest {

    @Test
    @MyTestUser(name="foo", pass="FOO", authority="TEST_FOO")
    public void test() throws Exception {
        this.printAuthentication("test");
    }

    private void printAuthentication(String testMethodName) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String message =
                "[" + testMethodName + "]\n" +
                "class = " + auth.getClass() + "\n" +
                "name = " + auth.getName() + "\n" +
                "credentials = " + auth.getCredentials() + "\n" +
                "authorities = " + auth.getAuthorities() + "\n" +
                "principal = " + auth.getPrincipal() + "\n" +
                "details = " + auth.getDetails();

        System.out.println(message);
    }
}

Execution result

[test]
class = class org.springframework.security.authentication.UsernamePasswordAuthenticationToken
name = foo
credentials = FOO
authorities = [TEST_FOO]
principal = org.springframework.security.core.userdetails.User@18cc6: Username: foo; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: TEST_FOO
details = null

--You can create your own SecurityContext with any implementation

MyTestUser.java


@WithSecurityContext(factory=MyTestUserFactory.class)
public @interface MyTestUser {

--Create an arbitrary annotation and annotate it with @WithSecurityContext --In factory, specify the Class object of the class that implements the WithSecurityContextFactory interface. --The factory class specified here implements the process to create SecurityContext.

MyTestUserFactory.java


package sample.spring.security.test;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.test.context.support.WithSecurityContextFactory;

public class MyTestUserFactory implements WithSecurityContextFactory<MyTestUser> {
    
    @Override
    public SecurityContext createSecurityContext(MyTestUser annotation) {
        SecurityContext context = SecurityContextHolder.createEmptyContext();

        ...

        return context;
    }
}

--A factory class created by implementing the WithSecurityContextFactory <T> interface --In the type variable T, specify the type of annotation handled by the factory class. --Implement the createSecurityContext () method to create and return your own SecurityContext --This class does not need to be registered in the Spring container. --WithSecurityContextTestExecutionListener will generate a factory from the annotation when the test is run

MyTestServiceTest.java


    @MyTestUser(name="foo", pass="FOO", authority="TEST_FOO")
    public void test() throws Exception {

--After that, if you annotate the test method with your own annotation, the SecurityContext created in the specified factory will be used as the authentication information.

Inject container-managed beans into factory classes

MyTestUserFactory.java


public class MyTestUserFactory implements WithSecurityContextFactory<MyTestUser> {
    private UserDetailsService userDetailsService;

    @Autowired
    public MyTestUserFactory(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

--Factory classes can inject other beans in the same way as regular beans --The above implementation uses constructor injection (@Autowired can be omitted)

Meta annotation

HogeUser.java


package sample.spring.security.test;

import org.springframework.security.test.context.support.WithMockUser;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@WithMockUser(username = "hoge", authorities = {"FOO", "BAR"})
public @interface HogeUser {
}

MyTestServiceTest.java


package sample.spring.security.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/test-applicationContext.xml")
public class MyTestServiceTest {

    @Test
    @HogeUser
    public void test() throws Exception {
        this.printAuthentication("test");
    }

    private void printAuthentication(String testMethodName) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String message =
                "[" + testMethodName + "]\n" +
                "class = " + auth.getClass() + "\n" +
                "name = " + auth.getName() + "\n" +
                "credentials = " + auth.getCredentials() + "\n" +
                "authorities = " + auth.getAuthorities() + "\n" +
                "principal = " + auth.getPrincipal() + "\n" +
                "details = " + auth.getDetails();

        System.out.println(message);
    }
}

Execution result

[test]
class = class org.springframework.security.authentication.UsernamePasswordAuthenticationToken
name = hoge
credentials = password
authorities = [BAR, FOO]
principal = org.springframework.security.core.userdetails.User@30f425: Username: hoge; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: BAR,FOO
details = null

--You can annotate your own annotations with the @WithMockUser annotation. --If you use this self-made annotation for a test method etc., the setting of @WithMockUser set in the self-made annotation will be inherited. --This mechanism is called ** meta-annotation ** --When using mock users with the same settings in various tests, it is easier to use meta annotations because the settings are in one place rather than annotating each method with @WithMockUser. --Of course, you can use it with @WithUserDetails as well as @WithMockUser.

Integration with Spring MVC

Hello World Implementation

build.gradle


dependencies {
    compile 'org.springframework.security:spring-security-web:4.2.1.RELEASE'
    compile 'org.springframework.security:spring-security-config:4.2.1.RELEASE'
    compile 'org.springframework:spring-webmvc:4.2.1.RELEASE'★ Addition
    testCompile 'junit:junit:4.12'
    testCompile 'org.springframework.security:spring-security-test:4.2.1.RELEASE'
}

--Adding a dependency of spring-webmvc because we will add an implementation of Spring MVC

MyMvcController.java


package sample.spring.security.control;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/mvc")
public class MyMvcController {
    
    @GetMapping
    public String hello() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        System.out.println("auth.name = " + auth.getName());
        
        return "test";
    }
}

--Controller class mapped to GET request to / mvc --ʻOutputting the name of Authentication`

test-applicationContext.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:sec="http://www.springframework.org/schema/security"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
         http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
         http://www.springframework.org/schema/security
         http://www.springframework.org/schema/security/spring-security.xsd">

    <bean class="sample.spring.security.control.MyMvcController" />

    <sec:http>
        <sec:intercept-url pattern="/login" access="permitAll" />
        <sec:intercept-url pattern="/**" access="isAuthenticated()" />
        <sec:form-login />
        <sec:logout />
    </sec:http>

    <sec:authentication-manager />
</beans>

--Simple declaration for Spring Security and Bean declaration for MyMvcController

MyMvcControllerTest.java


package sample.spring.security.test;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/test-applicationContext.xml")
@WebAppConfiguration
public class MyMvcControllerTest {
    @Autowired
    private WebApplicationContext context;
    
    private MockMvc mvc;

    @Before
    public void setUp() throws Exception {
        this.mvc = MockMvcBuilders
                    .webAppContextSetup(this.context)
                    .apply(SecurityMockMvcConfigurers.springSecurity())
                    .build();
    }

    @Test
    public void unauthorized() throws Exception {
        MvcResult mvcResult = this.mvc.perform(get("/mvc")).andReturn();
        this.printResponse("unauthorized", mvcResult);
    }

    @Test
    public void authorized() throws Exception {
        MvcResult mvcResult = this.mvc.perform(get("/mvc").with(user("foo"))).andReturn();
        this.printResponse("authorized", mvcResult);
    }
    
    private void printResponse(String method, MvcResult result) throws Exception {
        MockHttpServletResponse response = result.getResponse();
        int status = response.getStatus();
        String locationHeader = response.getHeader("Location");

        System.out.println("[" + method + "]\n" +
                           "status : " + status + "\n" +
                           "Location : " + locationHeader);
    }
}

--There are two test methods, ʻunauthorized and ʻauthorized. --Each access to / mvc and prints the resulting HTTP status and Location header

Execution result

auth.name = foo
[authorized]
status : 200
Location : null

[unauthorized]
status : 302
Location : http://localhost/login

--ʻAuthorized can be executed by the controller and the user name is output. --ʻUnauthorized returns a status of 302 and is prompted to redirect to the login screen

Description

MyMvcControllerTest.java


import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/test-applicationContext.xml")
@WebAppConfiguration
public class MyMvcControllerTest {
    @Autowired
    private WebApplicationContext context;
    
    private MockMvc mvc;

    @Before
    public void setUp() throws Exception {
        this.mvc = MockMvcBuilders
                    .webAppContextSetup(this.context)
                    .apply(SecurityMockMvcConfigurers.springSecurity())
                    .build();
    }

――The implementation of this test preparation is as it was written in the document because I have not studied Spring MVC, so I do not understand much meaning. --The settings for Spring Security are SecurityMockMvcConfigurers.springSecurity () in the setUp () method. --With this setting, it seems that Spring Security Filter is incorporated in the MVC mock and Spring Security processing works.

MyMvcControllerTest.java



import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;.

...

    @Test
    public void unauthorized() throws Exception {
        MvcResult mvcResult = this.mvc.perform(get("/mvc")).andReturn();
        this.printResponse("unauthorized", mvcResult);
    }

    @Test
    public void authorized() throws Exception {
        MvcResult mvcResult = this.mvc.perform(get("/mvc").with(user("foo"))).andReturn();
        this.printResponse("authorized", mvcResult);
    }

--get () is the static method of MockMvcRequestBuilders --ʻUser () is the staticmethod ofSecurityMockMvcRequestPostProcessors --Specify the name of the login user at runtime --Spring MVC Test has a mechanism to rewrite the request and uses a class that implements theRequestPostProcessorinterface. --Spring Security provides a number of classes that implement thisRequestPostProcessorto make it easier to configure for Spring Security. --A class calledSecurityMockMvcRequestPostProcessors, which is a collection of static` factory methods, is provided as a window for using those classes.

CSRF If CSRF protection is enabled, the token must be included in the request. If you call the controller as it is without doing anything, an error will occur because there is no token.

Therefore, there is an API for testing to pass the CSRF token check.

Implementation

test-applicationContext.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:sec="http://www.springframework.org/schema/security"
       ...>

    <bean class="sample.spring.security.control.MyMvcController" />

    <sec:http>
        <sec:intercept-url pattern="/login" access="permitAll" />
        <sec:intercept-url pattern="/**" access="isAuthenticated()" />
        <sec:form-login />
        <sec:logout />
        <sec:csrf />★ Addition
    </sec:http>

    <sec:authentication-manager />
</beans>

--Add <csrf> to enable CSRF protection

MyMvcController.java


package sample.spring.security.control;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/mvc")
public class MyMvcController {
    
    @PostMapping
    public String hello() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        System.out.println("auth.name = " + auth.getName());
        
        return "test";
    }
}

--Changed method annotation to @PostMapping --Because CSRF measures are not executed by the GET method

MyMvcControllerTest.java


package sample.spring.security.test;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/test-applicationContext.xml")
@WebAppConfiguration
public class MyMvcControllerTest {
    @Autowired
    private WebApplicationContext context;
    
    private MockMvc mvc;

    @Before
    public void setUp() throws Exception {
        this.mvc = MockMvcBuilders
                    .webAppContextSetup(this.context)
                    .apply(SecurityMockMvcConfigurers.springSecurity())
                    .build();
    }

    @Test
    public void noToken() throws Exception {
        MvcResult mvcResult = this.mvc.perform(
            post("/mvc")
            .with(user("foo"))
        ).andReturn();
        
        this.printResponse("noToken", mvcResult);
    }

    @Test
    public void useToken() throws Exception {
        MvcResult mvcResult = this.mvc.perform(
            post("/mvc")
            .with(user("bar"))
            .with(csrf())
        ).andReturn();
        
        this.printResponse("useToken", mvcResult);
    }
    
    private void printResponse(String method, MvcResult result) throws Exception {
        MockHttpServletResponse response = result.getResponse();
        int status = response.getStatus();
        String errorMessage = response.getErrorMessage();

        System.out.println("[" + method + "]\n" +
                           "status : " + status + "\n" +
                           "errorMessage : " + errorMessage);
    }
}

Execution result

[noToken]
status : 403
errorMessage : Could not verify the provided CSRF token because your session was not found.

auth.name = bar
[useToken]
status : 200
errorMessage : null

--noToken () is caught in the token check of CSRF and an error occurs. --ʻUseToken () `successfully executed the controller method

Description

MyMvcControllerTest.java


    @Test
    public void noToken() throws Exception {
        MvcResult mvcResult = this.mvc.perform(
            post("/mvc")
            .with(user("foo"))
        ).andReturn();
        
        this.printResponse("noToken", mvcResult);
    }

    @Test
    public void useToken() throws Exception {
        MvcResult mvcResult = this.mvc.perform(
            post("/mvc")
            .with(user("bar"))
            .with(csrf())
        ).andReturn();
        
        this.printResponse("useToken", mvcResult);
    }

--Use the csrf () method to include the CSRF token in the test run-time request. --This method is also defined in SecurityMockMvcRequestPostProcessors --If you want to put the token in the header, use csrf (). AsHeader ()

Set an illegal token

Implementation

MyMvcControllerTest.java


    @Test
    public void useInvalidToken() throws Exception {
        MvcResult mvcResult = this.mvc.perform(
            post("/mvc")
            .with(user("bar"))
            .with(csrf().useInvalidToken())
        ).andReturn();
        
        this.printResponse("useInvalidToken", mvcResult);
    }

Execution result

[useInvalidToken]
status : 403
errorMessage : Invalid CSRF Token 'invalidd19aed27-65e2-4cf6-9456-157e1f29c984' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.

Description

-- csrf (). useInvalidToken () will set an invalid token

User specification

There are two ways to specify the user:

--Method using RequestPostProcessor --Method using annotations

Method using RequestPostProcessor

A method using the extension point of the request provided by Spring MVC Test. Compared to the specification by annotation, it seems that the feature is that it can be specified dynamically.

Implementation

MyMvcController.java


package sample.spring.security.control;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.stream.Collectors;

@Controller
@RequestMapping("/mvc")
public class MyMvcController {
    
    @GetMapping
    public String hello() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();

        String name = auth.getName();
        Object credentials = auth.getCredentials();
        Object principal = auth.getPrincipal();
        String authorities = auth.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining(", "));

        System.out.println(
            "name = " + name + "\n" + 
            "credentials = " + credentials + "\n" + 
            "authorities = " + authorities + "\n" +
            "principal = " + principal
        );
        
        return "test";
    }
}

MyMvcControllerTest.java


package sample.spring.security.test;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import java.util.List;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/test-applicationContext.xml")
@WebAppConfiguration
public class MyMvcControllerTest {
    private static final String USERNAME = "foo";
    private static final String PASSWORD = "test-pass";
    private static final List<GrantedAuthority> AUTHORITIES = AuthorityUtils.createAuthorityList("FOO", "BAR");
    
    @Autowired
    private WebApplicationContext context;
    
    private MockMvc mvc;

    @Before
    public void setUp() throws Exception {
        this.mvc = MockMvcBuilders
                    .webAppContextSetup(this.context)
                    .apply(SecurityMockMvcConfigurers.springSecurity())
                    .build();
    }

    @Test
    public void withUser() throws Exception {
        System.out.println("[withUser]");
        this.mvc.perform(
            get("/mvc").with(user(USERNAME))
        );
    }

    @Test
    public void customized() throws Exception {
        System.out.println("[customized]");
        this.mvc.perform(
            get("/mvc").with(
                user(USERNAME)
                .password(PASSWORD)
                .authorities(AUTHORITIES)
            )
        );
    }

    @Test
    public void userDetails() throws Exception {
        System.out.println("[userDetails]");
        UserDetails user = this.createUserDetails();

        this.mvc.perform(
            get("/mvc").with(user(user))
        );
    }

    @Test
    public void withAnonymous() throws Exception {
        System.out.println("[withAnonymous]");

        this.mvc.perform(
            get("/mvc").with(anonymous())
        );
    }

    @Test
    public void withAuthentication() throws Exception {
        System.out.println("[withAuthentication]");
        Authentication auth = this.createAuthentication();

        this.mvc.perform(
            get("/mvc").with(authentication(auth))
        );
    }

    @Test
    public void withSecurityContext() throws Exception {
        System.out.println("[withAuthentication]");
        Authentication auth = this.createAuthentication();
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        context.setAuthentication(auth);

        this.mvc.perform(
            get("/mvc").with(securityContext(context))
        );
    }

    private UserDetails createUserDetails() {
        return new User(USERNAME, PASSWORD, AUTHORITIES);
    }
    
    private Authentication createAuthentication() {
        UserDetails user = this.createUserDetails();
        return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
    }
}

Execution result

[withUser]
name = foo
credentials = password
authorities = ROLE_USER
principal = org.springframework.security.core.userdetails.User@18cc6: Username: foo; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER

[customized]
name = foo
credentials = test-pass
authorities = BAR, FOO
principal = org.springframework.security.core.userdetails.User@18cc6: Username: foo; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: BAR,FOO

[userDetails]
name = foo
credentials = test-pass
authorities = BAR, FOO
principal = org.springframework.security.core.userdetails.User@18cc6: Username: foo; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: BAR,FOO

[withAnonymous]
name = anonymous
credentials = 
authorities = ROLE_ANONYMOUS
principal = anonymous

[withAuthentication]
name = foo
credentials = test-pass
authorities = BAR, FOO
principal = org.springframework.security.core.userdetails.User@18cc6: Username: foo; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: BAR,FOO

[withSecurityContext]
name = foo
credentials = test-pass
authorities = BAR, FOO
principal = org.springframework.security.core.userdetails.User@18cc6: Username: foo; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: BAR,FOO

Description

--If only user (String) is specified, only the user name will be the specified value. --Password is password --Authority is onlyROLE_USER --You can make detailed settings withpassword (String) and ʻauthorities (Collection <? Extends GrantedAuthority>) after ʻuser (String) --You can also specify roles withroles (String ...) --If ʻuser (UserDetails), you can specify ʻUserDetailsdirectly. --You can specify an anonymous user with ʻanonymous () --You can directly specify ʻAuthentication with ʻauthentication (Authentication) --You can specify SecurityContext directly withsecurityContext (SecurityContext)

Set default user information

Implementation

MyMvcControllerTest.java


package sample.spring.security.test;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/test-applicationContext.xml")
@WebAppConfiguration
public class MyMvcControllerTest {
    @Autowired
    private WebApplicationContext context;
    
    private MockMvc mvc;

    @Before
    public void setUp() throws Exception {
        this.mvc = MockMvcBuilders
                    .webAppContextSetup(this.context)
                    .apply(springSecurity())
                    .defaultRequest(get("/")
                        .with(
                            user("foo")
                            .password("test-pass")
                            .authorities(AuthorityUtils.createAuthorityList("FOO", "BAR"))
                        )
                    )
                    .build();
    }

    @Test
    public void test() throws Exception {
        this.mvc.perform(get("/mvc"));
    }
}

Execution result

name = foo
credentials = test-pass
authorities = BAR, FOO
principal = org.springframework.security.core.userdetails.User@18cc6: Username: foo; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: BAR,FOO

Description

MyMvcControllerTest.java


    @Before
    public void setUp() throws Exception {
        this.mvc = MockMvcBuilders
                    .webAppContextSetup(this.context)
                    .apply(springSecurity())
                    .defaultRequest(get("/")
                        .with(
                            user("foo")
                            .password("test-pass")
                            .authorities(AuthorityUtils.createAuthorityList("FOO", "BAR"))
                        )
                    )
                    .build();
    }

--If you specify the user information with defaultRequest () when creating MockMvc, you can use it as the default setting.

Summarize frequently used user definitions

Implementation

MyMockUserPostProcessors.java


package sample.spring.security.test;

import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.test.web.servlet.request.RequestPostProcessor;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;

public class MyMockUserPostProcessors {
    
    public static RequestPostProcessor hoge() {
        return user("hoge")
                .password("test-pass")
                .authorities(AuthorityUtils.createAuthorityList("FOO", "BAR"));
    }
}

MyMvcControllerTest.java


package sample.spring.security.test;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static sample.spring.security.test.MyMockUserPostProcessors.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/test-applicationContext.xml")
@WebAppConfiguration
public class MyMvcControllerTest {
    @Autowired
    private WebApplicationContext context;
    
    private MockMvc mvc;

    @Before
    public void setUp() throws Exception {
        this.mvc = MockMvcBuilders
                    .webAppContextSetup(this.context)
                    .apply(springSecurity())
                    .build();
    }
    
    @Test
    public void test() throws Exception {
        this.mvc.perform(get("/mvc").with(hoge()));
    }
}

Execution result

name = hoge
credentials = test-pass
authorities = BAR, FOO
principal = org.springframework.security.core.userdetails.User@30f425: Username: hoge; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: BAR,FOO

Description

MyMockUserPostProcessors.java


    public static RequestPostProcessor hoge() {
        return user("hoge")
                .password("test-pass")
                .authorities(AuthorityUtils.createAuthorityList("FOO", "BAR"));
    }

--If you can call the definition of RequestPostProcessor of frequently used settings with the static method, it will be easier to reuse.

Method using annotation

The method using annotations such as @WithMockUser written at the beginning.

Compared to the method using RequestPostProcessor, ** when dynamic change is not required ** annotations to methods and classes are sufficient, and I feel that it has a more declarative feature. After that, it is possible to share the method of specifying mock users with tests other than MVC.

Implementation

MyMvcControllerTest.java


package sample.spring.security.test;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/test-applicationContext.xml")
@WebAppConfiguration
public class MyMvcControllerTest {
    @Autowired
    private WebApplicationContext context;
    
    private MockMvc mvc;

    @Before
    public void setUp() throws Exception {
        this.mvc = MockMvcBuilders
                    .webAppContextSetup(this.context)
                    .apply(springSecurity())
                    .build();
    }

    @Test
    @WithMockUser(username="foo", password="test-pass", authorities={"FOO", "BAR"})
    public void test() throws Exception {
        this.mvc.perform(get("/mvc"));
    }
}

Execution result

name = foo
credentials = test-pass
authorities = BAR, FOO
principal = org.springframework.security.core.userdetails.User@18cc6: Username: foo; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: BAR,FOO

Description

--Since it is the same as how to use it when it is not MVC, there is nothing special to mention.

Form login

Implementation

test-applicationContext.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:sec="http://www.springframework.org/schema/security"
       ...>

    <bean class="sample.spring.security.control.MyMvcController" />

    <sec:http>
        <sec:intercept-url pattern="/login" access="permitAll" />
        <sec:intercept-url pattern="/**" access="isAuthenticated()" />
        <sec:form-login />
        <sec:logout />
    </sec:http>

    <sec:authentication-manager>
        <sec:authentication-provider>
            <sec:user-service>
                <sec:user name="user" password="password" authorities="FOO" />
            </sec:user-service>
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>

MyMvcControllerTest.java


package sample.spring.security.test;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/test-applicationContext.xml")
@WebAppConfiguration
public class MyMvcControllerTest {
    @Autowired
    private WebApplicationContext context;
    
    private MockMvc mvc;

    @Before
    public void setUp() throws Exception {
        this.mvc = MockMvcBuilders
                    .webAppContextSetup(this.context)
                    .apply(springSecurity())
                    .build();
    }

    @Test
    public void test() throws Exception {
        MvcResult result = this.mvc.perform(formLogin()).andReturn();
        this.printResponse("test", result);
    }

    private void printResponse(String method, MvcResult result) throws Exception {
        MockHttpServletResponse response = result.getResponse();
        int status = response.getStatus();
        String location = response.getHeader("Location");

        System.out.println("[" + method + "]\n" +
                "status : " + status + "\n" +
                "location : " + location;
    }
}

Execution result

[test]
status : 302
location : /

--Successfully logged in and skipped to the root / which is the default transition destination after login

Description

test-applicationContext.xml


    <sec:user name="user" password="password1" authorities="FOO" />

--The user must actually exist, as the normal logic is executed in the Form login.

MyMvcControllerTest.java


    @Test
    public void test() throws Exception {
        MvcResult result = this.mvc.perform(formLogin()).andReturn();
        this.printResponse("test", result);
    }

--Form login is executed by setting SecurityMockMvcRequestBuilders.formLogin () to perform () --By default, the following request is executed --Request path: / login --Method: POST --Username: ʻuser --Password:password --Username parameter name: ʻusername --Password parameter name: password

Specify various settings

Implementation

test-applicationContext.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:sec="http://www.springframework.org/schema/security"
       ...>

    <bean class="sample.spring.security.control.MyMvcController" />

    <sec:http>
        <sec:intercept-url pattern="/do-login" access="permitAll" />
        <sec:intercept-url pattern="/**" access="isAuthenticated()" />
        <sec:form-login login-processing-url="/do-login"
                        username-parameter="login-id"
                        password-parameter="pass" />
        <sec:logout />
    </sec:http>

    <sec:authentication-manager>
        <sec:authentication-provider>
            <sec:user-service>
                <sec:user name="user" password="password" authorities="FOO" />
            </sec:user-service>
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>

--Set login URL and user name / password parameter names to different values from the default

MyMvcControllerTest.java


package sample.spring.security.test;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/test-applicationContext.xml")
@WebAppConfiguration
public class MyMvcControllerTest {
    @Autowired
    private WebApplicationContext context;
    
    private MockMvc mvc;

    @Before
    public void setUp() throws Exception {
        this.mvc = MockMvcBuilders
                    .webAppContextSetup(this.context)
                    .apply(springSecurity())
                    .build();
    }

    @Test
    public void test() throws Exception {
        MvcResult result = this.mvc.perform(
            formLogin("/do-login")
            .userParameter("login-id")
            .passwordParam("pass")
            .user("user")
            .password("password")
        ).andReturn();

        this.printResponse("test", result);
    }

    private void printResponse(String method, MvcResult result) throws Exception {
        ...
    }
}

Execution result

[test]
status : 302
location : /

Description

MyMvcControllerTest.java


    @Test
    public void test() throws Exception {
        MvcResult result = this.mvc.perform(
            formLogin("/do-login")
            .userParameter("login-id")
            .passwordParam("pass")
            .user("user")
            .password("password")
        ).andReturn();

        this.printResponse("test", result);
    }

--If the login process path is different from the default (/ login), you can specify the path with the argument offormLogin (). --Can also be specified with loginProcessingUrl () --If the parameter name of the user name is different from the default (ʻusername), you can specify it with ʻuserParameter (). --If the password parameter name is different from the default (password), you can specify it withpasswordParam ().

Log out

Implementation

MyMvcControllerTest.java


package sample.spring.security.test;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/test-applicationContext.xml")
@WebAppConfiguration
public class MyMvcControllerTest {
    @Autowired
    private WebApplicationContext context;
    
    private MockMvc mvc;

    @Before
    public void setUp() throws Exception {
        this.mvc = MockMvcBuilders
                    .webAppContextSetup(this.context)
                    .apply(springSecurity())
                    .build();
    }

    @Test
    public void test() throws Exception {
        MvcResult result = this.mvc.perform(logout()).andReturn();
        this.printResponse("test", result);
    }

    private void printResponse(String method, MvcResult result) throws Exception {
        ...
    }
}

Execution result

[test]
status : 302
location : /login?logout

Description

--Passing SecurityMockMvcRequestBuilders.logout () to perform () will execute the logout request --If the logout URL is different from the default (/ logout), you can specify it as an argument likelogout ("/ other-logout-url")

Verification of credentials after login

Implementation

test-applicationContext.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:sec="http://www.springframework.org/schema/security"
       ...>

    <bean class="sample.spring.security.control.MyMvcController" />

    <sec:http>
        <sec:intercept-url pattern="/login" access="permitAll" />
        <sec:intercept-url pattern="/**" access="isAuthenticated()" />
        <sec:form-login />
        <sec:logout />
    </sec:http>

    <sec:authentication-manager>
        <sec:authentication-provider>
            <sec:user-service>
                <sec:user name="foo" password="foo" authorities="FOO, BAR" />
                <sec:user name="fizz" password="fizz" authorities="ROLE_FIZZ, ROLE_BUZZ" />
            </sec:user-service>
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>

--Set FOO and BAR privileges for the foo user --Set the roles of ROLE_FIZZ and ROLE_BUZZ for the fizz user

MyMvcControllerTest.java


package sample.spring.security.test;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import java.util.List;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.*;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/test-applicationContext.xml")
@WebAppConfiguration
public class MyMvcControllerTest {
    @Autowired
    private WebApplicationContext context;
    
    private MockMvc mvc;

    @Before
    public void setUp() throws Exception {
        this.mvc = MockMvcBuilders
                    .webAppContextSetup(this.context)
                    .apply(springSecurity())
                    .build();
    }

    @Test
    public void test_unauthenticated() throws Exception {
        this.mvc.perform(formLogin().user("foo").password("invalid"))
                .andExpect(unauthenticated());
    }

    @Test
    public void test_authenticated() throws Exception {
        this.mvc.perform(formLogin().user("foo").password("foo"))
                .andExpect(authenticated());
    }

    @Test
    public void test_withUsername() throws Exception {
        this.mvc.perform(formLogin().user("foo").password("foo"))
                .andExpect(authenticated().withUsername("foo"));
    }

    @Test
    public void test_withAuthorities() throws Exception {
        List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("FOO", "BAR");
        
        this.mvc.perform(formLogin().user("foo").password("foo"))
                .andExpect(authenticated().withAuthorities(authorities));
    }

    @Test
    public void test_combine() throws Exception {
        List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("FOO", "BAR");
        
        this.mvc.perform(formLogin().user("foo").password("foo"))
                .andExpect(authenticated().withUsername("foo").withAuthorities(authorities));
    }

    @Test
    public void test_withRoles() throws Exception {
        this.mvc.perform(formLogin().user("fizz").password("fizz"))
                .andExpect(authenticated().withRoles("FIZZ", "BUZZ"));
    }
}

Execution result

All tests succeed

Description

MyMvcControllerTest.java


    @Test
    public void test_unauthenticated() throws Exception {
        this.mvc.perform(formLogin().user("foo").password("invalid"))
                .andExpect(unauthenticated());
    }

--You can verify the authentication result by using ʻandExpect ()afterperform (). --SecurityMockMvcResultMatchers provides static` methods and use them.

Method name Verification content
unauthenticated() Verify that you are not authenticated
authenticated() Verify that you are authenticated
to thiswithUsername()Detailed verification is possible by continuing such as
withUsername(String) Validate username
withAuthorities(Collection<? extends GrantedAuthority>) Validate the granted authority
withRoles(String...) Validate the assigned role

MyMvcControllerTest.java


    @Test
    public void test_combine() throws Exception {
        List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("FOO", "BAR");
        
        this.mvc.perform(formLogin().user("foo").password("foo"))
                .andExpect(authenticated().withUsername("foo").withAuthorities(authorities));
    }

--Multiple withUsername () and withAuthorities () can be combined

reference

Recommended Posts

Spring Security usage memo test
Spring Security usage memo CSRF
Spring Security usage memo Run-As
Spring Security Usage memo Method security
Spring Security usage memo Remember-Me
Spring Security usage memo CORS
Spring Security usage memo session management
Spring Security Usage Memo Domain Object Security (ACL)
Spring Security usage memo: Cooperation with Spring MVC and Boot
Spring retrospective memo
JavaParser usage memo
Spring Boot @WebMvcTest test enables Spring Security default security
WatchService usage memo
PlantUML usage memo
JUnit5 usage memo
About Spring Security authentication
Spring Security causes 403 forbidden
Spring boot memo writing (2)
[Personal memo] About Spring framework
JJUG CCC 2018 Spring participation memo
[Spring] Controller exception output test
Spring Framework self-study memo series_1
Login function with Spring Security
[Spring Security] Spring Security on GAE (SE)
Dependency Management Plugin Usage memo
Try using Spring Boot Security
Spring single item validation test
Spring boot controller method memo
Implemented authentication function with Spring Security ②
How to unit test Spring AOP
JCA (Java Cryptography Architecture) Usage Memo
Implemented authentication function with Spring Security ③
Spring Boot Tutorial Using Spring Security Authentication
Write test code in Spring Boot
spring framework Simple study memo (2): AOP
Ruby Study Memo (Test Driven Development)
Implemented authentication function with Spring Security ①
Learn Spring Security authentication processing architecture
Memo after the first Spring project-MVC-
Use DBUnit for Spring Boot test
Spring thorough introduction version upgrade memo
Authentication / authorization with Spring Security & Thymeleaf
Memo after the first Spring project-Database-
Test Spring framework controller with Junit
Thymeleaf usage notes in Spring Boot
Spring5 MVC Web application development with Visual Studio Code Spring Security usage 1/3 [Preparation]