Edited at

Spring Security 使い方メモ CORS

More than 1 year has passed since last update.

基礎・仕組み的な話

認証・認可の話

Remember-Me の話

CSRF の話

セッション管理の話

レスポンスヘッダーの話

メソッドセキュリティの話

Run-As の話

ACL の話

テストの話

MVC, Boot との連携の話

番外編

Spring Security にできること・できないこと


CORS とは

@tomoyukilabs さんの CORSまとめ - Qiita が勉強になりました。

雑にまとめると、


  • ブラウザは、あるオリジンから読み込まれたリソースから別のオリジンにアクセスできないようになっている


    • 同一オリジンポリシーというらしい



  • サーバー側で、「このオリジンからのリクエストなら別オリジンであっても許可する」みたいな設定をすることで、クライアントはオリジンを超えてリクエストを送信できるようになる

  • それが CORS (Cross-Origin Resource Sharing)

みたいな感じ。

「オリジン」が何なのかは オリジンの定義 | 同一オリジンポリシー - Web セキュリティ | MDN とかを参照。


Hello World


クロスオリジンでアクセスするページの準備

以下の html を GitHub 上に配置し、静的ページとしてブラウザから開けるようにしておく。

<!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>

実際に GitHub 上に上げたやつ

localhost に対して Ajax でアクセスするようにしている。


サーバー側の実装


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!!");
}
}
}


適当なヘッダーとテキストのボディを返すだけの実装。


まずは CORS の設定なしでアクセスしてみる

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();
}
}


CORS 用の設定などは一切していない状態。

先ほどの検証用ページSend Request ボタンをクリックすると、次のようなエラーが発生する。

spring-security.jpg

Spring Security の認証チェックに阻まれて、ログインページにリダイレクトさせられている。

また、 CORS の仕様で定義されている Access-Control-Allow-Origin のヘッダーがレスポンスにないため、アクセスできません、と言っている。


Spring Security の設定を変更してアクセスできるようにする

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;
}

...
}


もう一度実行する。

spring-security.jpg

今度はアクセスが成功してサーバーが返したテキストを出力できている。


説明


CORS を有効にする


applicationContext.xml

    <bean id="corsSource" class="org.springframework.web.cors.UrlBasedCorsConfigurationSource">

...
</bean>

<sec:http>
...
<sec:cors configuration-source-ref="corsSource" />
</sec:http>




  • <http> タグの下に <cors> を追加することで CORS の制御が有効になる

  • このとき、 configuration-source-ref 属性に CORS の細かい設定を定義した CorsConfigurationSource の Bean を指定する


MySpringSecurityConfig.java

    @Override

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

private CorsConfigurationSource corsConfigurationSource() {
...
}



  • Java Configuration の場合は .cors().configurationSource(CorsConfigurationSource) で指定する


CORS の設定


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>



  • CORS の設定には2つのクラスを使用する



    • CorsConfigurationSourceCorsConfiguration




  • CorsConfiguration にどの HTTP メソッドを許可するか、どのオリジンからのアクセスを許可するか、といった詳細なルールを設定する


    • 上記設定の場合は、 allowedOrigins プロパティで許可するオリジンのリストを、 allowedMethods で許可する HTTP メソッドを指定している


    • CorsConfiguration は、デフォルトだとすべてのリクエストを許可しない設定になっているので、何かしら許可の設定をする必要がある




  • CorsConfigurationSource は複数の CorsConfiguration を保持し、現在のリクエストに対してどの CorsConfiguration を適用すべきかを判断する


    • 上記設定では、実装クラスとして UrlBasedCorsConfigurationSource を使用している


    • UrlBasedCorsConfigurationSource は、どの CorsConfiguration を適用すべきかどうかをパスパターンと紐づけることで管理している

    • 上記設定の場合は、 /cors にアクセスが来た場合に corsConfiguration Bean で設定したルールを適用するようにしている

    • パスパターンは Ant 形式で指定できる




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;
}



  • Java Configuration の場合は addAllowedMethod(String)registerCorsConfiguration(String, CorsConfiguration) など、1つずつ設定できるメソッドが利用できる


    • (XML でも利用できなくはないが、記述が煩雑になるだけなので普通は使わないと思う)




preflight request へ対応させる

CORS の仕様では、デフォルトで許可されている HTTP メソッドや設定可能なヘッダーは限られている。

デフォルトで許可されていないヘッダーを送りたい、いった場合は、 prefilight request という「許可されているか確認する」ための事前リクエストを送ることになっている。

このリクエストに対してサーバーが「OKよ」と返した場合に限り、クライアントは本番のリクエストを送信する。


まずは許可されていない HTTP ヘッダーを送信してみる

spring-security.jpg

Hoge という許可されていないヘッダーを載せてリクエストを送信してみる。

spring-security.jpg

むっちゃ怒られた。

preflight request で許可されていないヘッダーがあったのでアクセスできなかった、と言っている。

リクエストとレスポンスのヘッダーを確認してみる。

spring-security.jpg

リクエストヘッダーに、 Access-Control-Request-Headers: hoge とあり、 hoge というヘッダーは許可されるか確認している。

しかし、レスポンスには hoge は許可しているよ、ということを表すヘッダーがない。

このため、ブラウザは hoge は許可されないヘッダーだと判断して処理を中断している。


Hoge ヘッダーを許可する

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"> ★追加
<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"); ★追加

...
}

...
}


CorsConfigurationallowedHeaderHoge を追加している。

再度動作検証する。

spring-security.jpg

今度はちゃんとリクエストが通った。

HTTP リクエストの様子を確認する。

spring-security.jpg

先に OPTIONS メソッドによる preflight request が実行され、その後本番の GET によるリクエストが送信されている。

OPTIONS の方のヘッダーを見てみる。

spring-security.jpg

レスポンスヘッダーに Access-Control-Allow-Headers: hoge が追加され、 hoge ヘッダーが許可されていることが示されている。

Spring Security は、 CorsConfiguration に設定した許可のルールに従い、自動的に preflight request への回答を行ってくれるようになっている。

クライアントが任意のレスポンスヘッダーにアクセスする場合もデフォルトでは制限があるが、 exposedHeaders を設定することで許可することができる。


参考