やりたかったこと
外部の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());
}
}