単体テストでログインフォームを送信すると、次のエラーが発生します。
org.springframework.web.servlet.PageNotFound noHandlerFound
WARNING: No mapping found for HTTP request with URI [/app/login/authenticate] in DispatcherServlet with name ''
Spring Security 4.0.0.M1 で Spring 4.0.5.RELEASE を使用しています。
これは、テストクラスを構成した方法です。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {
UnitTestContext.class,
SecurityContext.class,
ServletContext.class
})
@WebAppConfiguration
public class LoginControllerTest {
@Autowired
private WebApplicationContext webAppContext;
@Autowired
private FilterChainProxy springSecurityFilterChain;
private MockMvcHtmlUnitDriver webDriver;
@Before
public void setUp() {
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup(webAppContext)
.addFilter(springSecurityFilterChain)
.build();
webDriver = new MockMvcHtmlUnitDriver(mockMvc);
}
@Test
public void test() {
LoginPage loginPage = LoginPage.get(webDriver).login(LoginPage.class);
}
}
ログインページクラス:
public class LoginPage extends AbstractPage {
public static final String URL = "http://localhost:8080/app/login";
@FindBy(id = "username")
private WebElement usernameElement;
@FindBy(id = "password")
private WebElement passwordElement;
@FindBy(id = "remember-me")
private WebElement rememberMeElement;
@FindBy(id = "login-btn")
private WebElement loginBtnElement;
@FindBy(id = "login-form")
private WebElement loginForm;
public LoginPage(WebDriver webDriver) {
super(webDriver);
}
public LoginPage typeUsername(String username) {
usernameElement.sendKeys(username);
return this;
}
public String getUsername() {
return usernameElement.getAttribute("value");
}
public LoginPage typePassword(String password) {
passwordElement.sendKeys(password);
return this;
}
public String getPassword() {
return passwordElement.getAttribute("value");
}
public LoginPage selectRememberMe() {
rememberMeElement.click();
return this;
}
public boolean isRememberMe() {
return rememberMeElement.isSelected();
}
public <T> T login(Class<T> resultPage) {
loginBtnElement.click();
return PageFactory.initElements(webDriver, resultPage);
}
public static LoginPage get(WebDriver webDriver) {
webDriver.get(URL);
return PageFactory.initElements(webDriver, LoginPage.class);
}
}
そして、ここでマッピングSecurityContext.class
を宣言しました:/login/authenticate
@Configuration
@EnableWebSecurity
public class SecurityContext extends WebSecurityConfigurerAdapter {
@Autowired
private UserMapper userMapper;
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/static/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers();
http
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/login/authenticate")
.failureHandler(new SimpleUrlAuthenticationFailureHandler("/loginfailed"));
}
@Override
public UserDetailsService userDetailsService() {
return new UserDetailsServiceImpl(userMapper);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService())
.passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10);
}
}
-
@Configuration
public class UnitTestContext {
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setDefaultEncoding("UTF-8");
messageSource.setBasenames(
"i18n/messages",
"i18n/validation-messages");
messageSource.setFallbackToSystemLocale(false);
return messageSource;
}
@Bean
public UserService userService() {
return Mockito.mock(UserService.class);
}
@Bean
public UserMapper userMapper() {
return Mockito.mock(UserMapper.class);
}
}
-
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {
"com.app.common.controller",
"com.app.home.controller",
"com.app.user.controller"
})
public class ServletContext extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("/static/")
.setCachePeriod((int) TimeUnit.DAYS.toSeconds(365));
}
@Bean
public ThymeleafViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setContentType("text/html; charset=UTF-8");
return viewResolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
templateEngine.setAdditionalDialects(new HashSet<>(Arrays.asList(
new SpringSecurityDialect())));
return templateEngine;
}
@Bean
public ServletContextTemplateResolver templateResolver() {
ServletContextTemplateResolver templateResolver =
new ServletContextTemplateResolver();
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("HTML5");
templateResolver.setCharacterEncoding("UTF-8");
return templateResolver;
}
}
ログインページ:
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org" th:include="layouts/main-layout :: main-layout">
<head>
<title th:text="#{login.page.title}"></title>
</head>
<body>
<div class="col-sm-6 col-sm-offset-3" th:fragment="content">
<form class="default-form" method="post" action="login/authenticate" role="form"
th:object="${loginForm}">
<input type="hidden"
th:name="${_csrf.parameterName}"
th:value="${_csrf.token}" />
<div class="default-form-header">
<h3 th:text="#{login.header.text}"></h3>
</div>
<fieldset>
<div class="form-group">
<label for="username" th:text="#{login.username.label}"></label>
<div class="input-group">
<span class="input-group-addon">
<i class="fa fa-user fa-fw"></i>
</span>
<input type="text"
id="username"
class="form-control"
th:field="*{username}"
th:maxlength="${usernameMaxLength}"
th:placeholder="#{login.username.label}" />
</div>
</div>
<div class="form-group">
<label for="password" th:text="#{login.password.label}"></label>
<div class="input-group">
<span class="input-group-addon">
<i class="fa fa-key fa-fw"></i>
</span>
<input type="password"
id="password"
class="form-control"
th:field="*{password}"
th:maxlength="${passwordMaxLength}"
th:placeholder="#{login.password.label}" />
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox"
id="remember-me" />
<span th:text="#{login.remember.checkbox}"></span>
</label>
</div>
</div>
</fieldset>
<div class="default-form-footer">
<button type="submit"
id="login-btn"
class="btn btn-primary"
th:text="#{login.submit.button}">
</button>
</div>
</form>
</div>
</body>
</html>
Tomcat で実行すると、すべてがうまく機能します。フィルターが足りないのですか?
デバッグ中に、次の URL のみurlMap
が含まれていることがわかりました: (/login/authenticate がありません)AbstractHandlerMethodMapping
{
/=[{[/],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}],
/loginfailed=[{[/loginfailed],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}],
/login=[{[/login],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}],
/signup=[{[/signup],methods=[POST],params=[],headers=[],consumes=[],produces=[],custom=[]},
{[/signup],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}]
}
これは print() から来るものです
MockHttpServletRequest:
HTTP Method = GET
Request URI = /app/login
Parameters = {}
Headers = {Content-Type=[*/*;charset=ISO-8859-1], Accept=[image/gif, image/jpeg, image/pjpeg, image/pjpeg, */*], Accept-Encoding=[gzip, deflate], Accept-Language=[en-us]}
Handler:
Type = com.app.user.controller.LoginController
Method = public java.lang.String com.app.user.controller.LoginController.showLoginPage(org.springframework.ui.ModelMap)
Async:
Was async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = login
View = null
Attribute = usernameMaxLength
value = 30
Attribute = passwordMaxLength
value = 30
Attribute = loginForm
value = com.app.user.dto.LoginForm@fa8227
errors = []
FlashMap:
MockHttpServletResponse:
Status = 200
Error message = null
Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY], Content-Type=[text/html; charset=UTF-8]}
Content type = text/html; charset=UTF-8
Forwarded URL = null
Redirected URL = null
Cookies = []
kesäkuuta 01, 2014 9:10:51 AP. org.springframework.web.servlet.PageNotFound noHandlerFound
WARNING: No mapping found for HTTP request with URI [/app/login/authenticate] in DispatcherServlet with name ''
MockHttpServletRequest:
HTTP Method = POST
Request URI = /app/login/authenticate
Parameters = {_csrf=[12e09c07-32f7-4dee-8ad0-35bfa1827354], username=[], password=[]}
Headers = {Content-Type=[*/*;charset=UTF-8], Accept=[*/*], Referer=[http://localhost:8080/app/login], Accept-Encoding=[gzip, deflate], Accept-Language=[en-us]}
Handler:
Type = null
Async:
Was async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
MockHttpServletResponse:
Status = 404
Error message = null
Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []