Spring Security(+ MVC)  JavaConfig

#概要

Spring SecurityとSpring MVCをxmlを使わずに設定してみた際のメモ。

完全なソースは[GutHub](https://github.com/jun-1/spring-javaconfig-sample/)

#設定内容

* 認証エラーの場合はログインページを表示する
* 但し、Ajaxリクエストの場合はHTTPステータス401を返す
* Thymeleafで`sec:authorize="hasRole('ROLE_ADMIN')"`のように、ロールによって表示を切り替えられるようにする

#必要な依存関係

使用する主なモジュールとバージョン

* SpringSecurity : 4.0.0.M1
* SpringMVC : 4.0.5.RELEASE
* Thymeleaf : 2.1.3.RELEASE

詳細は[pom.xm](https://github.com/jun-1/spring-javaconfig-sample/pom.xml)を参照

#SpringSecurityの設定

今回はAjaxリクエストの場合はログインページにリダイレクトするのではなくて、HTTPステータス401を返したいので、リクエストヘッダに`X-Requested-With: XMLHttpRequest`があった場合のAuthenticationEntryPointを追加しています。

```java:WebSecurityConfig
@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.inMemoryAuthentication()
			.withUser("admin").password("admin").roles("ADMIN").and()
			.withUser("user").password("user").roles("USER");
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.authorizeRequests()
				.antMatchers(
					"/",
					"/css/**",
					"/login",
					"/logout").permitAll()
				.anyRequest().authenticated()
				.and()
			.formLogin()
				.loginPage("/login")
				.and()
			.logout()
				.logoutRequestMatcher(logoutRequestMatcher())
				.invalidateHttpSession(true)
				.and()
			.csrf()
				.and()
			.exceptionHandling()
				.defaultAuthenticationEntryPointFor(
					ajaxAuthenticationEntryPoint(),
					ajaxRequestMatcher()
				);
	}

	@Bean
	public AuthenticationEntryPoint ajaxAuthenticationEntryPoint() {
	return new AuthenticationEntryPoint() {
		@Override
		public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
			response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
		}
	};
	}

	@Bean
	public RequestMatcher ajaxRequestMatcher() {
	return new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest");
	}

	@Bean
	public RequestMatcher logoutRequestMatcher() {
	return new AntPathRequestMatcher("/logout");
	}

}
```

#SpringMVCの設定

リソースハンドラとビューを返すだけのコントローラーを追加しています。
それから`@Import(ThymeleafConfig.class)`でThymeleafの設定をインポート。

```java:WebMvcConfig
@Configuration
@EnableWebMvc
@Import(ThymeleafConfig.class)
@ComponentScan(
		basePackageClasses = {AjaxTestController.class}
)
public class WebMvcConfig extends WebMvcConfigurerAdapter {

	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		registry.addResourceHandler("/css/**").addResourceLocations("/WEB-INF/views/css/");
	}

	@Override
	public void addViewControllers(ViewControllerRegistry registry) {
		registry.addViewController("/").setViewName("index");
		registry.addViewController("/login").setViewName("login");
		registry.addViewController("/member").setViewName("member");
	}
}
```

#Tymeleafの設定

ThymeleafとSpringSecurityを連携するためにSpringSecurityDialectを追加。
開発中はキャッシュされない方がいいので`resolver.setCacheable(false);`
それから文字コードの設定は2箇所あるので注意。

```java:ThymeleafConfig 
@Configuration
public class ThymeleafConfig {

	private final String CHARACTER_ENCODING = "UTF-8";

	@Bean
	public ServletContextTemplateResolver templateResolver() {
		ServletContextTemplateResolver resolver
				= new ServletContextTemplateResolver();
		resolver.setPrefix("/WEB-INF/views/");
		resolver.setSuffix(".html");
		resolver.setTemplateMode("HTML5");
		resolver.setCharacterEncoding(CHARACTER_ENCODING);
		resolver.setCacheable(false);
		return resolver;
	}

	@Bean
	public SpringTemplateEngine templateEngine() {
		SpringTemplateEngine engine = new SpringTemplateEngine();
		engine.setTemplateResolver(templateResolver());
		engine.addDialect(springSecurityDialect());
		return engine;
	}

	@Bean
	public ThymeleafViewResolver thymeleafViewResolver() {
		ThymeleafViewResolver resolver = new ThymeleafViewResolver();
		resolver.setTemplateEngine(templateEngine());
		resolver.setCharacterEncoding(CHARACTER_ENCODING);
		return resolver;
	}

	@Bean
	public IDialect springSecurityDialect() {
		SpringSecurityDialect dialect = new SpringSecurityDialect();
		return dialect;
	}
}
```