Spring Security は 5.4 以降、設定の書き方に大幅な変更が入っています。
詳しくは @suke_masa さんの Spring Security 5.7でセキュリティ設定の書き方が大幅に変わる件 - Qiita を参照してください。
基礎・仕組み的な話
認証・認可の話
Remember-Me の話
CSRF の話
セッション管理の話
レスポンスヘッダーの話
メソッドセキュリティの話
CORS の話
Run-As の話
ACL の話
テストの話
番外編
Spring Security にできること・できないこと
Spring MVC や Spring Boot で Spring Security を使う方法とか、なんかその辺の話。
Spring MVC との連携
リクエストパスの Matcher
まずは普通に統合させてみる
apply plugin: 'war'
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
compileJava.options.encoding = 'UTF-8'
repositories {
mavenCentral()
}
dependencies {
compile 'org.springframework.security:spring-security-web:4.2.3.RELEASE'
compile 'org.springframework.security:spring-security-config:4.2.3.RELEASE'
compile 'org.springframework:spring-webmvc:4.3.10.RELEASE'
}
- 依存関係に
spring-webmvc
を追加
package sample.spring.security.mvc;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyMvcController {
@GetMapping("/foo")
public String foo() {
return "FOO!!";
}
}
-
/foo
に GET リクエストがきたら"FOO!!"
と返すコントローラクラス
namespace
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/mvc.xml
/WEB-INF/security.xml
</param-value>
</context-param>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
- Spring Security の
DelegatingFilterProxy
と Spring MVC のDispatcherServlet
をそれぞれ定義 - Spring MVC と Security の設定ファイルとして
/WEB-INF/mvc.xml
と/WEB-INF/security.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:http>
<sec:intercept-url pattern="/foo" access="isAuthenticated()" />
<sec:form-login />
</sec:http>
<sec:authentication-manager />
</beans>
- Spring Security の設定
-
/foo
へのアクセスは認証が必要ということにしている
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven />
<bean class="sample.spring.security.mvc.MyMvcController" />
</beans>
- Spring MVC 用の設定
Java Configuration
package sample.spring.config;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class MySecurityInitializer extends AbstractSecurityWebApplicationInitializer {
}
- Spring Security の
Filter
を適用するためのクラス - Spring Security 単体のときは、ここで設定クラスを親クラスのコンストラクタに渡していたが、 MVC と統合した場合は MVC 用の
Initializer
の方に移動している
package sample.spring.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class MyServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {MySecurityConfig.class, MyMvcConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] {};
}
@Override
protected String[] getServletMappings() {
return new String[] {"/"};
}
}
- Spring MVC 用の初期化クラス
- MVC の設定(
MyMvcConfig
)も Security の設定(MySecurityConfig
)も、両方ともアプリケーションのルートとして設定している(理由はよくわかってないが、こうしないと後述するmvcMatchers
が働かなかった)
package sample.spring.config;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/foo").authenticated()
.and()
.formLogin();
}
}
- Spring Security の設定クラス
-
/foo
へのリクエストは認証が必要
package sample.spring.config;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import sample.spring.security.mvc.MyMvcController;
@EnableWebMvc
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Bean
public MyMvcController myMvcController() {
return new MyMvcController();
}
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new RequestMappingHandlerMapping();
}
}
- Spring MVC 用の設定
- コントローラを登録している
動作確認
まずは /foo
にリクエストを飛ばし、次に /foo.html
にリクエストを飛ばす。
$ curl http://localhost:8080/namespace/foo -i
HTTP/1.1 302 Found
Server: Apache-Coyote/1.1
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-XSS-Protection: 1; mode=block
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Set-Cookie: JSESSIONID=0AD6574E5F43053B42E5C9926535AC0E; Path=/namespace/; HttpOnly
Location: http://localhost:8080/namespace/login
Content-Length: 0
Date: Mon, 31 Jul 2017 13:05:03 GMT
$ curl http://localhost:8080/namespace/foo.html -i
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-XSS-Protection: 1; mode=block
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Content-Disposition: inline;filename=f.txt
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 5
Date: Mon, 31 Jul 2017 13:05:06 GMT
FOO!!
/foo
の場合はログイン画面にリダイレクトを促されたが、 /foo.html
の場合は普通にコントローラの実装が呼ばれてしまった。
なぜこうなるか
- Spring MVC は、
/foo
というパスをコントローラにマッピングしたときに、設定によっては/foo.html
など拡張子を指定したパスもマッチングの対象になる- この挙動自体は、レスポンスの形式の切り替えを拡張子によって指定するタイプの API を簡単に実装できるようにすることが目的となっている
-
/foo.html
も/foo.json
も同じコントローラのメソッドにマッピングされるようにしている
- しかし、 Spring Security は Ant 形式でパスを指定しているため、
/foo
以外のパスへのリクエストはすり抜けてしまう
Spring MVC 用の RequestMatcher
- この問題に対応するため、 Spring Security には Spring MVC のパスのマッチング処理と同じロジックでパスのマッチングを行う
Matcher
が用意されている
namespace
<?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:http request-matcher="mvc">
<sec:intercept-url pattern="/foo" access="isAuthenticated()" />
<sec:form-login />
</sec:http>
<sec:authentication-manager />
</beans>
-
<http>
タグのrequest-matcher
にmvc
を指定する
Java Configuration
package sample.spring.config;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers("/foo").authenticated()
.and()
.formLogin();
}
}
-
antMatchers()
の代わりにmvcMatchers()
を使用する
$ curl http://localhost:8080/namespace/foo -i
HTTP/1.1 302 Found
Server: Apache-Coyote/1.1
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-XSS-Protection: 1; mode=block
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Set-Cookie: JSESSIONID=0322DCB394ED74B38CCA600DDEF8CBBF; Path=/namespace/; HttpOnly
Location: http://localhost:8080/namespace/login
Content-Length: 0
Date: Mon, 31 Jul 2017 13:07:48 GMT
$ curl http://localhost:8080/namespace/foo.html -i
HTTP/1.1 302 Found
Server: Apache-Coyote/1.1
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-XSS-Protection: 1; mode=block
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Set-Cookie: JSESSIONID=BE049E9C138392A8751351762B200D63; Path=/namespace/; HttpOnly
Location: http://localhost:8080/namespace/login
Content-Length: 0
Date: Mon, 31 Jul 2017 13:07:49 GMT
今度は .html
をつけてもログイン画面へ飛ばされた。
コントローラの引数で現在のプリンシパルを受け取る
実装
package sample.spring.security.mvc;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyMvcController {
@GetMapping("/user")
public String foo(@AuthenticationPrincipal User user) {
System.out.println("username=" + user.getUsername() + ", authorities=" + user.getAuthorities());
return "User!!";
}
}
namespace
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
<bean class="sample.spring.security.mvc.MyMvcController" />
</beans>
<?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:http request-matcher="mvc">
<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="GENERAL_USER, ADMINISTRATOR" />
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
</beans>
Java Configuration
MyMvcConfig.java
は変更なし
package sample.spring.config;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("foo")
.password("foo")
.authorities("GENERAL_USER", "ADMINISTRATOR");
}
}
動作確認
ブラウザでアクセスして foo
ユーザでログイン後、 /user
にアクセスする。
username=foo, authorities=[ADMINISTRATOR, GENERAL_USER]
説明
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.User;
...
@GetMapping("/user")
public String foo(@AuthenticationPrincipal User user) {
System.out.println("username=" + user.getUsername() + ", authorities=" + user.getAuthorities());
return "User!!";
}
- コントローラの引数で
Authentication.getPrincipal()
が返すオブジェクトを受け取ることができる - 引数を
@AuthenticationPrincipal
でアノテートする - 引数の解決は、 Spring Security が提供する
AuthenticationPrincipalArgumentResolver
によって行われる
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
- namespace を使っている場合は、
<argument-resolvers>
でAuthenticationPrincipalArgumentResolver
を指定してあげる必要がある - Java Configuration を使っている場合は、
@EnableWebSecurity
を使うことでこの設定が自動的に行われるので、追加での設定は不要
CSRF トークンを受け取る
実装
package sample.spring.security.mvc;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyMvcController {
@GetMapping("/csrf")
public String foo(CsrfToken token) {
System.out.println("token=" + token.getToken() + ", headerName=" + token.getHeaderName() + ", parameterName=" + token.getParameterName());
return "CSRF!!";
}
}
namespace
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="org.springframework.security.web.method.annotation.CsrfTokenArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
<bean class="sample.spring.security.mvc.MyMvcController" />
</beans>
<?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:http request-matcher="mvc">
<sec:intercept-url pattern="/**" access="permitAll" />
<sec:form-login />
<sec:csrf />
</sec:http>
<sec:authentication-manager />
</beans>
Java Configuration
MyMvcConfig
は変更なし
package sample.spring.config;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers("/**").permitAll()
.and()
.formLogin()
.and()
.csrf();
}
}
実行結果
/csrf
にアクセスする
token=bcac7b2e-f2c0-424c-a563-4b957ff7133e, headerName=X-CSRF-TOKEN, parameterName=_csrf
説明
import org.springframework.security.web.csrf.CsrfToken;
...
@GetMapping("/csrf")
public String foo(CsrfToken token) {
System.out.println("token=" + token.getToken() + ", headerName=" + token.getHeaderName() + ", parameterName=" + token.getParameterName());
return "CSRF!!";
}
- CSRF のトークンをコントローラのメソッド引数で受け取ることができる
- トークンは
CsrfToken
インスタンスとして取得できる
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="org.springframework.security.web.method.annotation.CsrfTokenArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
- namespace の場合、
CsrfToken
を引数で受け取るようにするためには、CsrfTokenArgumentResolver
を<argument-resolvers>
で指定する必要がある - Java Configuration の場合は
@EnableWebSecurity
を使えば自動的に登録されるので、追加の設定は不要
Spring Boot との連携
Hello World
実装
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.5.6.RELEASE'
}
}
apply plugin: 'java'
apply plugin: 'spring-boot'
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
compileJava.options.encoding = 'UTF-8'
repositories {
mavenCentral()
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'org.springframework.boot:spring-boot-starter-security'
}
package sample.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello Spring Security with Spring Boot</title>
</head>
<body>
<h1>Hello Spring Security!!</h1>
</body>
</html>
動作確認
$ gradle bootRun
The plugin id 'spring-boot' is deprecated. Please use 'org.springframework.boot' instead.
:compileJava
:processResources
:classes
:findMainClass
:bootRun
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.6.RELEASE)
2017-08-02 22:55:17.710 INFO 13608 --- [ main] sample.boot.Main : Starting Main on .....
with PID 13608 (...\spring-boot-security\build\classes\main started by .... in ...\spring-boot-security)
(中略)
2017-08-02 22:55:19.756 INFO 13608 --- [ main] b.a.s.AuthenticationManagerConfiguration :
Using default security password: 40890087-600d-417d-962d-a856e139b9c4
2017-08-02 22:55:19.808 INFO 13608 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: OrRequestMatcher [requestMatchers=[Ant [pattern='/css/**'], Ant [pattern='/js/**'], Ant [pattern='/images/**'], Ant [pattern='/webjars/**'], Ant [pattern='/**/favicon.ico'], Ant [pattern='/error']]], []
(後略)
http://localhost:8080/hello.html
にアクセスする。
ダイアログが表示されてユーザー名とパスワードの入力を求められるので、次のように入力する。
入力項目 | 値 |
---|---|
ユーザー名 | user |
パスワード | 起動時にコンソールに出力されたパスワード |
パスワードは、アプリケーションを起動したときにコンソールに出力されている。
Using default security password: 40890087-600d-417d-962d-a856e139b9c4
ログインに成功して、 hello.html
の内容が表示される。
説明
dependencies {
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'org.springframework.boot:spring-boot-starter-security'
}
-
spring-boot-starter-security
を追加すると Spring Security の依存関係が追加される - 何もしないと、デフォルトで Basic 認証が有効になり、
user
という名前のユーザーがメモリ上に用意される(パスワードは起動しなおすと変わる)- パスワードは
security.user.password
プロパティで明示することも可能
- パスワードは
- また、
/js/**
,/css/**
,/images/**
,/webjars/**
,**/favicon.js
へのアクセスは認証なしで可能なように設定されている
設定を明示する
実装
package sample.boot.config;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("foo").password("foo").authorities("TEST_USER");
}
}
動作確認
http://localhost:8080/hello.html
にアクセス
デフォルトのログイン画面が表示されるので、 foo
ユーザでログインする
説明
@EnableWebSecurity
でアノテートした設定クラスを追加し、 Spring Security の設定を明示することによって、デフォルトの挙動を任意に書き換えることができる。