[JAVA] Spring Security usage memo CORS

Basic / mechanism story Authentication / Authorization Story Remember-Me story CSRF story Session management story The story of the response header Method security story The story of Run-As The story of ACL Test story Talk about cooperation with MVC and Boot

Extra edition What Spring Security can and cannot do

What is CORS?

@ tomoyukilabs's CORS Summary --Qiita was a learning experience.

Roughly summarized

--Browsers cannot access another origin from resources loaded from one origin --It seems to be the same origin policy --By setting something like "Allow requests from this origin even if they are from different origins" on the server side, the client will be able to send requests beyond the origin. --That is CORS (Cross-Origin Resource Sharing)

It feels like.

What is "origin"?Origin definition|Same origin policy-Web security| MDNSee.

Hello World

Preparing pages to access with cross origin

Place the following html on GitHub so that it can be opened from a browser as a static page.

python


<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>cors test</title>
  </head>
  <body>
    <div>
      <select id="contextPath">
        <option value="namespace" selected>namespace</option>
        <option value="java-config">java-config</option>
      </select>
    </div>

    <div>
      <input id="headerName" placeholder="Header-Name" />
      <input id="headerValue" placeholder="Header-Value" />
    </div>

    <div>
      <input id="responseHeaderName" placeholder="responseHeaderName" />
    </div>

    <button type="button" id="sendButton">Send Request</button>

    <script src="https://code.jquery.com/jquery-3.2.1.min.js"
            integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
            crossorigin="anonymous"></script>

    <script>
      $(function() {
        $('#sendButton').on('click', function() {
          var headers = {};
          var headerName = $('#headerName').val();
          var headerValue = $('#headerValue').val();
          if (headerName) {
            headers[headerName] = headerValue;
          }

          var contextPath = $('#contextPath').val();

          $.ajax({
            url: 'http://localhost:8080/' + contextPath + '/cors',
            type: 'GET',
            dataType: 'text',
            headers: headers
          })
          .done(function(text, status, jqXhr) {
            console.log("text = " + text);

            var responseHeaderName = $('#responseHeaderName').val();
            if (responseHeaderName) {
              console.log("[ResponseHeader] " + responseHeaderName + " : " + jqXhr.getResponseHeader(responseHeaderName));
            }
          })
          .fail(function() {
            console.error(arguments);
          });
        });
      });
    </script>
  </body>
</html>

The one I actually uploaded on GitHub

I am trying to access localhost with Ajax.

Server-side implementation

CorsServlet.java


package sample.spring.security.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/cors")
public class CorsServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setHeader("Original-Header", "CORS!!");
        try (PrintWriter writer = resp.getWriter()) {
            writer.println("Hello CORS!!");
        }
    }
}

An implementation that just returns the appropriate header and body of text.

First, try accessing without CORS settings

namespace

applicationContext.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="/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="hoge" password="hoge" authorities="" />
            </sec:user-service>
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>

Java Configuration

MySpringSecurityConfig.java


package sample.spring.security;

import org.springframework.beans.factory.annotation.Autowired;
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 MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin();
    }

    @Autowired
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.eraseCredentials(false)
            .inMemoryAuthentication()
                .withUser("hoge")
                .password("hoge")
                .roles();
    }
}

No settings for CORS have been made.

When I click the Send Request button on the Verification page, I get the following error: ..

spring-security.jpg

I was blocked by Spring Security's authentication check and redirected to the login page.

It also says that it cannot be accessed because the ʻAccess-Control-Allow-Origin` header defined in the CORS specification is missing from the response.

Change Spring Security settings to make them accessible

namespace

applicationContext.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">
    
    <bean id="corsConfiguration" class="org.springframework.web.cors.CorsConfiguration">
        <property name="allowedOrigins">
            <list>
                <value>http://opengl-8080.github.io</value>
            </list>
        </property>
        <property name="allowedMethods">
            <list>
                <value>GET</value>
            </list>
        </property>
    </bean>

    <bean id="corsSource" class="org.springframework.web.cors.UrlBasedCorsConfigurationSource">
        <property name="corsConfigurations">
            <map>
                <entry key="/cors" value-ref="corsConfiguration" />
            </map>
        </property>
    </bean>
    
    <sec:http>
        <sec:intercept-url pattern="/login" access="permitAll" />
        <sec:intercept-url pattern="/cors" access="permitAll" />
        <sec:intercept-url pattern="/**" access="isAuthenticated()" />
        <sec:form-login />
        <sec:logout />
        <sec:cors configuration-source-ref="corsSource" />
    </sec:http>

    ...
</beans>

Java Configuration

MySpringSecurityConfig.java


package sample.spring.security;

import org.springframework.beans.factory.annotation.Autowired;
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;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login").permitAll()
                .antMatchers("/cors").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .and()
            .cors()
                .configurationSource(this.corsConfigurationSource());
    }

    private CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedMethod("GET");
        corsConfiguration.addAllowedOrigin("http://opengl-8080.github.io");

        UrlBasedCorsConfigurationSource corsSource = new UrlBasedCorsConfigurationSource();
        corsSource.registerCorsConfiguration("/cors", corsConfiguration);

        return corsSource;
    }

    ...
}

Run again.

spring-security.jpg

This time the access is successful and the text returned by the server can be output.

Description

Enable CORS

applicationContext.xml


    <bean id="corsSource" class="org.springframework.web.cors.UrlBasedCorsConfigurationSource">
        ...
    </bean>
    
    <sec:http>
        ...
        <sec:cors configuration-source-ref="corsSource" />
    </sec:http>

--CORS control is enabled by adding <cors> under the <http> tag. --At this time, specify the bean of CorsConfigurationSource that defines the detailed CORS settings in the configuration-source-ref attribute.

MySpringSecurityConfig.java


    @Override
    public void configure(HttpSecurity http) throws Exception {
        http...
                .and()
            .cors()
                .configurationSource(this.corsConfigurationSource());
    }

    private CorsConfigurationSource corsConfigurationSource() {
        ...
    }

--For Java Configuration, specify with .cors (). ConfigurationSource (CorsConfigurationSource)

CORS settings

applicationContext.xml


    <bean id="corsConfiguration" class="org.springframework.web.cors.CorsConfiguration">
        <property name="allowedOrigins">
            <list>
                <value>http://opengl-8080.github.io</value>
            </list>
        </property>
        <property name="allowedMethods">
            <list>
                <value>GET</value>
            </list>
        </property>
    </bean>

    <bean id="corsSource" class="org.springframework.web.cors.UrlBasedCorsConfigurationSource">
        <property name="corsConfigurations">
            <map>
                <entry key="/cors" value-ref="corsConfiguration" />
            </map>
        </property>
    </bean>

--Use two classes to configure CORS --CorsConfigurationSource and CorsConfiguration --Set detailed rules such as which HTTP method is allowed in CorsConfiguration and which origin is allowed to access. --In the case of the above settings, the HTTP method that allows the list of origins allowed by the ʻallowedOrigins property is specified by ʻallowedMethods --CorsConfiguration is set to ** not allow ** all requests by default, so you need to set some permission. --CorsConfigurationSource keeps multiple CorsConfigurations and determines which CorsConfiguration should be applied to the current request --In the above settings, ʻUrlBasedCorsConfigurationSource is used as the implementation class. --ʻUrlBasedCorsConfigurationSource manages which CorsConfiguration should be applied by associating it with the path pattern. --In the case of the above settings, the rules set in the corsConfiguration bean are applied when the / cors is accessed. --Path pattern can be specified in Ant format

MySpringSecurityConfig.java


    private CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedMethod("GET");
        corsConfiguration.addAllowedOrigin("http://opengl-8080.github.io");

        UrlBasedCorsConfigurationSource corsSource = new UrlBasedCorsConfigurationSource();
        corsSource.registerCorsConfiguration("/cors", corsConfiguration);

        return corsSource;
    }

--For Java Configuration, you can use methods that can be set one by one, such as ʻaddAllowedMethod (String) and registerCorsConfiguration (String, CorsConfiguration) `. -(Although it can be used in XML, I don't think it's usually used because it only complicates the description.)

Correspond to preflight request

The CORS specification limits the HTTP methods and configurable headers that are allowed by default.

If you want to send a header that isn't allowed by default, you're supposed to send a prefilight request, a prefilight request to "check if it's allowed".

The client sends the production request only if the server returns "OK" to this request.

First try sending an HTTP header that is not allowed

spring-security.jpg

Try submitting a request with an unauthorized header called Hoge.

spring-security.jpg

I was very angry.

It says it couldn't be accessed because there was a header that wasn't allowed in the preflight request.

Check the request and response headers.

spring-security.jpg

The request header says ʻAccess-Control-Request-Headers: hoge, and I'm checking if the header hogeis allowed. However, the response does not have a header stating thathoge` is allowed.

For this reason, the browser determines that hoge is an unauthorized header and suspends processing.

Allow Hoge headers

namespace

applicationContext.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:sec="http://www.springframework.org/schema/security"
       ...>
    
    <bean id="corsConfiguration" class="org.springframework.web.cors.CorsConfiguration">
        <property name="allowedOrigins">
            <list>
                <value>http://opengl-8080.github.io</value>
            </list>
        </property>
        <property name="allowedMethods">
            <list>
                <value>GET</value>
            </list>
        </property>
        <property name="allowedHeaders">★ Add
            <list>
                <value>Hoge</value>
            </list>
        </property>
    </bean>

    ...
</beans>

Java Configuration

MySpringSecurityConfig.java


package sample.spring.security;

...

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
    ...

    private CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        ...
        corsConfiguration.addAllowedHeader("Hoge");★ Add

        ...
    }

    ...
}

Hoge is added to ʻallowedHeader of CorsConfiguration`.

Verify the operation again.

spring-security.jpg

This time the request passed properly.

Check the state of the HTTP request.

spring-security.jpg

The preflight request by the ʻOPTIONSmethod is executed first, and then the request by the productionGET` is sent.

Take a look at the header towards ʻOPTIONS`.

spring-security.jpg

ʻAccess-Control-Allow-Headers: hogehas been added to the response headers to indicate that thehoge` header is allowed.

Spring Security automatically responds to preflight requests according to the permission rules set in CorsConfiguration.

There are restrictions by default when clients access arbitrary response headers, but [exposedHeaders](http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/cors/ It can be allowed by setting CorsConfiguration.html # addExposedHeader-java.lang.String-).

reference

Recommended Posts

Spring Security usage memo CORS
Spring Security usage memo CSRF
Spring Security usage memo Run-As
Spring Security Usage memo Method security
Spring Security usage memo Remember-Me
Spring Security usage memo test
Spring Security usage memo Authentication / authorization
Spring Security usage memo response header
Spring Security usage memo session management
Spring Security usage memo Basic / mechanism
Spring Security Usage Memo Domain Object Security (ACL)
Spring Shell usage memo
Spring Security usage memo: Cooperation with Spring MVC and Boot
Spring retrospective memo
JavaParser usage memo
WatchService usage memo
JUnit5 usage memo
JJUG CCC Spring 2018 memo
About Spring Security authentication
Spring boot memo writing (1)
Spring Security causes 403 forbidden
Spring boot memo writing (2)
[Personal memo] About Spring framework
JJUG CCC 2018 Spring participation memo
Spring Framework self-study memo series_1
Login function with Spring Security
Dependency Management Plugin Usage memo
Try using Spring Boot Security
Spring boot controller method memo
◆ Spring Boot + gradle environment construction memo
JCA (Java Cryptography Architecture) Usage Memo
Implemented authentication function with Spring Security ③
Spring Boot Tutorial Using Spring Security Authentication
spring framework Simple study memo (2): AOP
Implemented authentication function with Spring Security ①
Learn Spring Security authentication processing architecture
Memo after the first Spring project-MVC-
A memo that touched Spring Boot
Spring thorough introduction version upgrade memo
Authentication / authorization with Spring Security & Thymeleaf
Memo after the first Spring project-Database-
Thymeleaf usage notes in Spring Boot