使用Spring Security实现与Spring MVC和Spring Boot的集成的方法备忘录
基本结构和机制的讨论
身份验证和授权的讨论
记住我功能的讨论
跨站请求伪造的讨论
会话管理的讨论
响应头的讨论
方法安全性的讨论
跨域资源共享的讨论
Run-As功能的讨论
访问控制列表的讨论
测试的讨论
番外編
Spring Security 可以做的事情和不可以做的事情
使用Spring MVC或Spring Boot使用Spring Security的方法以及其他相关内容。
与Spring MVC的整合
请求路径的匹配器
首先,试着以一种普通的方式进行整合。
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!!” と返すコントローラクラス
命名空间
<?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 配置
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 が用意されている
命名空间
<?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>
タグの request-matcher に mvc を指定する
Java配置
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!!";
}
}
命名空间
<?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配置
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 を使っている場合は、 で 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!!";
}
}
命名空间
<?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 配置 (Java peizhi)
我MvcConfig没有任何修改。
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();
}
}
执行结果
访问/crsf
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 を で指定する必要がある
- Java Configuration の場合は @EnableWebSecurity を使えば自動的に登録されるので、追加の設定は不要
与Spring Boot的整合
你好,世界
落实
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配置,可以自定义默认行为。
给出以下中文句子的同义表达:
参考资料(can be used as a noun phrase)
-
- 37. Spring MVC Integration | Spring Security Reference
- 28. Security | Spring Boot Reference Guide