6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Spring SecurityのOAuth2ログインしてる体でコントローラーのテストをする

Last updated at Posted at 2018-03-04

やりたかったこと

外部のIdpを使ってログインを実装したアプリケーションで、コントローラーのテストをするときにログイン済みの体でControllerのテストをしたかった。

かなり雰囲気でやったらできた感じなのでそもそもSpringがこういうの提供してるよ的なのあったら教えてください。

テスト対象はこういうやつ

build.gradle(抜粋)

buildscript {
	ext {
		defaultEncoding  = 'UTF-8'
		compatibility    = 1.8
		springBootVersion = '2.0.0.RELEASE'
	}
	repositories {
		mavenCentral()
		maven { url "https://repo.spring.io/snapshot" }
		maven { url "https://repo.spring.io/milestone" }
	}
	dependencies {
		classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
	}
}

repositories {
	mavenCentral()
	maven { url "https://repo.spring.io/snapshot" }
	maven { url "https://repo.spring.io/milestone" }
}

dependencyManagement {
	imports {
		mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES
	}
}

dependencies {
	compile('org.springframework.boot:spring-boot-starter-actuator')
	compile('org.springframework.boot:spring-boot-starter-mail')
	compile('org.springframework.boot:spring-boot-starter-security')
	compile('org.springframework.boot:spring-boot-starter-web')

	compile('org.springframework.security:spring-security-oauth2-client')
	compile('org.springframework.security:spring-security-oauth2-jose')

	runtime('org.springframework.boot:spring-boot-devtools')
	testCompile('org.springframework.boot:spring-boot-starter-test')
	testCompile('org.springframework.security:spring-security-test')
	testCompile group: 'com.h2database', name: 'h2', version: '1.4.196'

}

application.yml

####################################################################################################
# spring
####################################################################################################
spring:
  security.oauth2.client.registration:
      google:
        client-id: xxxxx
        client-secret: xxxxx

コントローラー

import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;


/**
 * Controller for login.
 */
@Controller
public class LoginController {

    /**
     * 認証後に表示するページ。
     *
     * @param authentication authentication info
     * @return user info
     */
    @GetMapping("/")
    public String index(final OAuth2AuthenticationToken authentication) {
        return "index";
    }

    /**
     * ログイン画面を表示する.
     *
     * @return login page
     */
    @GetMapping("/login")
    public String viewLogin() {
        return "login";
    }
}

設定

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * Configuration about security.
 *
 * @since
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(final HttpSecurity http) throws Exception { // NOPMD
        // Define NOPMD because HttpSecurity$authorizeRequests throws Exception.
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .logout().permitAll()
                .and()
                .oauth2Login()
                .loginPage("/login").permitAll();
    }
}

テストはこうした

Mockユーザー

このアノテーションがついてると認証済みの状態でアクセスするよ的な。

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

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

/**
 * Annotation for test with OAuth2 Authentication.
 */
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithOAuth2SecurityContextFactory.class)
public @interface WithMockOAuth2User {

    /**
     * Username.
     *
     * @return username
     */
    String username() default "testuser";

}

SecurityContextFactory

とにかくOAuth2AuthenticationTokenを作ってSecurityContextに叩き込んでるやつ。XXXなユーザでテストしたいぞ的な気持ちはここをごにょごにょすれば良さそう。

import java.time.Instant;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.test.context.support.WithSecurityContextFactory;

/**
 * OAuth2 token provider(put to {@SecurityContext} for integration test.
 */
public class WithOAuth2SecurityContextFactory implements WithSecurityContextFactory<WithMockOAuth2User> {

    /**
     * Create security context with OAuth2 token.
     *
     * @param user user
     * @return security context
     */
    @Override
    public SecurityContext createSecurityContext(final WithMockOAuth2User user) {
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        Map<String, Object> claims = new HashMap<>();
        claims.put("sub", "testsub");

        Set<GrantedAuthority> authorities = new HashSet<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));

        OAuth2User oidcUser = new DefaultOidcUser(authorities, new OidcIdToken("sampletoken", Instant.MIN, Instant.MAX, claims));
        OAuth2AuthenticationToken token = new OAuth2AuthenticationToken(oidcUser, authorities, "test-client");
        context.setAuthentication(token);
        return context;
    }
}

テストクラス

@WithMockOAuth2Userがついてるとログイン後の世界でURLを叩ける。


import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Test;
import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;



/**
 * Test for {@link LoginController}.
 */
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class LoginControllerTest {

    /**
     * MockMvc.
     */
    @Autowired
    private MockMvc mockMvc;

    /**
     * Test for index page before login (redirect to login page).
     *
     * @throws Exception exception
     */
    @Test
    public void indexBeforeLogin() throws Exception { // NOPMD
        mockMvc.perform(get("/")).andExpect(status().is3xxRedirection());
    }

    /**
     * Test for index page after login.
     *
     * @throws Exception exception
     */
    @Test
    @WithMockOAuth2User()
    public void indexAfterLogin() throws Exception { // NOPMD
        mockMvc.perform(get("/")).andExpect(status().isOk());
    }


    /**
     * Test for login page.
     *
     * @throws Exception exception
     */
    @Test
    public void viewLogin() throws Exception { // NOPMD
        mockMvc.perform(get("/login")).andExpect(status().isOk());
    }
}

6
5
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?