Basic and mechanical story Authentication / Authorization Story Remember-Me story CSRF story Session management story The story of the response header Method security story CORS story The story of Run-As The story of ACL Test story
Extra edition What Spring Security can and cannot do
How to use Spring Security with Spring MVC and Spring Boot, and so on.
build.gradle
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'
}
--Added spring-webmvc
to dependencies
MyMvcController.java
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!!";
}
}
--Controller class that returns " FOO !! "
when a GET request comes to / foo
namespace
web.xml
<?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>
--Define Spring Security DelegatingFilterProxy
and Spring MVC DispatcherServlet
respectively
--Specify /WEB-INF/mvc.xml
and /WEB-INF/security.xml
as Spring MVC and Security configuration files
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 settings
--Authentication is required to access / foo
mvc.xml
<?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>
--Settings for Spring MVC
Java Configuration
MySecurityInitializer.java
package sample.spring.config;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class MySecurityInitializer extends AbstractSecurityWebApplicationInitializer {
}
--Class for applying Spring Security Filter
--In the case of Spring Security alone, the setting class was passed to the constructor of the parent class here, but when integrated with MVC, it is moved to ʻInitializer` for MVC.
MyServletInitializer.java
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[] {"/"};
}
}
--Initialization class for Spring MVC
--Both the MVC setting (MyMvcConfig
) and the Security setting (MySecurityConfig
) are set as the root of the application (I don't know why, but otherwise mvcMatchers
, which will be described later, will not work. T)
MySecurityConfig.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()
.antMatchers("/foo").authenticated()
.and()
.formLogin();
}
}
--Spring Security configuration class
--Requests to / foo
require authentication
MyMvcConfig.java
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();
}
}
--Settings for Spring MVC --Registering the controller
First, the request is sent to / foo
, and then the request is sent to /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!!
In the case of / foo
, I was prompted to redirect to the login screen, but in the case of /foo.html
, the controller implementation was called normally.
--Spring MVC will match paths with extensions such as /foo.html
depending on the settings when mapping the path / foo
to the controller.
--This behavior itself is intended to make it easy to implement a type of API that specifies switching between response formats by extension.
--Both /foo.html
and /foo.json
are mapped to the same controller method
--However, since Spring Security specifies the path in Ant format, requests for paths other than / foo
will be bypassed.
--To address this issue, Spring Security provides Matcher
, which matches paths with the same logic as Spring MVC's path matching process.
namespace
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 request-matcher="mvc">
<sec:intercept-url pattern="/foo" access="isAuthenticated()" />
<sec:form-login />
</sec:http>
<sec:authentication-manager />
</beans>
--Specify mvc
in request-matcher
of <http>
tag
Java Configuration
MySecurityConfig.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();
}
}
--Use mvcMatchers ()
instead of ʻantMatchers () `
Operation check
$ 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
This time, even if I added .html
, I was skipped to the login screen.
MyMvcController.java
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
mvc.xml
<?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>
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 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
remains unchanged
MySecurityConfig.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");
}
}
Access with a browser, log in as the foo
user, and then access / user
.
Server console output
username=foo, authorities=[ADMINISTRATOR, GENERAL_USER]
MyMvcController.java
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!!";
}
--You can receive the object returned by ʻAuthentication.getPrincipal () in the controller argument. --Annotate the argument with
@AuthenticationPrincipal --Argument resolution is done by ʻAuthenticationPrincipalArgumentResolver
provided by Spring Security.
mvc.xml
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
--If you are using namespace, you need to specify ʻAuthenticationPrincipalArgumentResolver in
. --If you are using Java Configuration, this setting is done automatically by using
@EnableWebSecurity`, so no additional settings are required.
MyMvcController.java
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
mvc.xml
<?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>
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 request-matcher="mvc">
<sec:intercept-url pattern="/**" access="permitAll" />
<sec:form-login />
<sec:csrf />
</sec:http>
<sec:authentication-manager />
</beans>
Java Configuration
MyMvcConfig
is unchanged
MySecurityConfig.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("/**").permitAll()
.and()
.formLogin()
.and()
.csrf();
}
}
Access / csrf
Server console output
token=bcac7b2e-f2c0-424c-a563-4b957ff7133e, headerName=X-CSRF-TOKEN, parameterName=_csrf
MyMvcController.java
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 tokens can be received in controller method arguments
--Tokens can be obtained as CsrfToken
instances
mvc.xml
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="org.springframework.security.web.method.annotation.CsrfTokenArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
--In the case of namespace, CsrfTokenArgumentResolver
must be specified in<argument-resolvers>
in order to receive CsrfToken
as an argument.
--In the case of Java Configuration, if you use @EnableWebSecurity
, it will be registered automatically, so no additional settings are required.
Hello World Implementation
build.gradle
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'
}
Main.java
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);
}
}
src/main/resources/static/hello.html
<!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>
** Operation check **
$ 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)
(Omitted)
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']]], []
(Omitted)
Go to http: // localhost: 8080 / hello.html
.
A dialog will appear asking you to enter your username and password. Enter the following:
Input items | value |
---|---|
username | user |
password | 起動時にコンソールに出力されたpassword |
The password is output to the console when the application is started.
Password output to the console
Using default security password: 40890087-600d-417d-962d-a856e139b9c4
After successfully logging in, the contents of hello.html
are displayed.
Description
build.gradle
dependencies {
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'org.springframework.boot:spring-boot-starter-security'
}
--Adding spring-boot-starter-security
adds Spring Security dependencies
--If you do nothing, Basic authentication is enabled by default and a user named ʻuseris prepared in memory (password changes when restarted). --Password can also be specified in the
security.user.passwordproperty --Also, access to
/ js / **,
/ css / **,
/ images / **,
/ webjars / ,
/ favicon.js` is without authentication. Set to be possible
Implementation
MySecurityConfig.java
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");
}
}
** Operation check **
Go to http: //localhost:8080/hello.html
The default login screen is displayed, so log in as the foo
user.
Description
The default behavior can be arbitrarily rewritten by adding the configuration class annotated with @EnableWebSecurity
and clarifying the Spring Security configuration.
Recommended Posts