読者です 読者をやめる 読者になる 読者になる

舞台裏

Qiita が表でこっちが裏。こっそりやっていく。

ACL 勉強中苦戦中

たぶんドメインオブジェクト(特定のデータ)単位でアクセス制御をできるようにするための仕組みなんだけど、リファレンスに載ってるサンプルの情報が不足しすぎてて苦戦中。

なんとか必要そうな Bean を定義したが、トランザクション管理が必要と言われたところで今日はあきらめた。

https://github.com/spring-projects/spring-security/blob/master/samples/xml/contacts/src/main/resources/applicationContext-common-business.xml

ここのサンプルを見ながらやれば、なんとかできそうな気がするが、余計な情報が多すぎてまだまだモヤがかかった状態。

もっといい感じのサンプル実装がないかなぁと調べてたら、 Grails のドキュメントがなんかよさげな気配がしたので明日から翻訳してみよう。。。

Spring Security ACL Plugin - Reference Documentation

CORS を勉強した

Spring Security の CORS についていろいろ動作確認した。

わりとすっきりしてて分かりやすい印象。

設定書くだけで preflight request とか勝手にやってくれるのは便利でいいなぁって思った。

CSRF のトークンを Cookie にした場合の secure 属性の指定

よく考えたら記事に書いてなかったなぁと仕事中に思い出したので調べた。

<csrf> タグの属性とかで指定できるのかなぁと思ったら、 IntelliJ の補完で属性が3つしか表示されない。

f:id:opengl-8080:20170512233842j:plain

しかたないので実装(CookieCsrfTokenRepository)を見に行ったら、↓のようになってた。

   @Override
    public void saveToken(CsrfToken token, HttpServletRequest request,
            HttpServletResponse response) {
        ...
        Cookie cookie = new Cookie(this.cookieName, tokenValue);
        cookie.setSecure(request.isSecure());
        ...
    }

HttpServletRequest#isSecure() の値をそのままセットしている。

isSecure()ServletRequest に定義されているメソッドで、 Javadoc を見るとリクエストの通信が保護されているかどうかを返すらしい。

ServletRequest (Java(TM) EE 7 Specification APIs)

例は HTTPS 通信ということで、 HTTPS 通信だったら勝手に secure 属性が有効になるっぽい。

下手にオプションで切り替えるよりも、こっちのほうが設定漏れもなくて確実な気がした。グッドプラクティスっぽい。

19. CORS

Spring Framework provides first class support for CORS.

Spring Framework は CORS をサポートするクラスを提供します。

CORS must be processed before Spring Security because the pre-flight request will not contain any cookies (i.e. the JSESSIONID).

CORS は、 Spring Security の前に処理されなければなりません。なぜなら、 pre-flight リクエストは cookie をまったく含んでいないためです。

If the request does not contain any cookies and Spring Security is first, the request will determine the user is not authenticated (since there are no cookies in the request) and reject it.

もし、リクエストが cookie を含んでいない状態で Spring Security が最初に処理をすると、リクエストはユーザーが未認証であると結論づけられ(リクエストに cookie が無いので)、リクエストが拒否されます。

The easiest way to ensure that CORS is handled first is to use the CorsFilter.

CORS が最初に処理されるようにすることを確実にするための最も簡単な手段は、 CorsFilter を使うことです。

Users can integrate the CorsFilter with Spring Security by providing a CorsConfigurationSource using the following:

ユーザは、 Spring Security が提供する CorsConfigurationSource を次のように使うことで、 CorsFilter を統合できます。

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // by default uses a Bean by the name of corsConfigurationSource
            .cors().and()
            ...
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

or in XML

もしくは xml なら

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

If you are using Spring MVC’s CORS support, you can omit specifying the CorsConfigurationSource and Spring Security will leverage the CORS configuration provided to Spring MVC.

もしあなたが Spring MVC の CORS サポートを使っているなら、あなたは CorsConfigurationSource の指定を省略でし、 Spring MVC が提供する CORS の設定を利用するようにできます。

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // if Spring MVC is on classpath and no CorsConfigurationSource is provided,
            // Spring Security will use CORS configuration provided to Spring MVC
            .cors().and()
            ...
    }
}

or in XML

もしくは XML なら

<http>
    <!-- Default to Spring MVC's CORS configuration -->
    <cors />
    ...
</http>

Remember-Me のログイン時のパラメータの謎について追う

Remember-Me 認証でログインするときに、フォームには <input type="checkbox" name="remember-me" /> を追加しておけばいい。

けど、 value 属性を省略した場合って、なんの値が渡っているのだろう?

実際に動かしたら、 on という値が渡っていた。

<input type="checkbox"> - HTML | MDN

value
(略)省略された場合は、要素の value プロパティの取得結果は文字列の “on” になります。

一応 value を省略したら on がデフォルトになるらしい。

でも、携帯とかだと機種によって値が違う みたいな情報もあった。

Spring Security はどうやって値を取得しているのだろう?

ログイン時に Remember-Me を有効にするかどうかを判定しているところを探す。

<remember-me> タグの remember-me-parameter 属性でリクエストパラメータの値を変更できるので、これを使っているところを追えばおのずとログイン時の処理の場所を特定できそう。

spring-security-config-4.2.1.RELEASE.jar の中を remember-me-parameterGREP 検索する(IntelliJ だと jar の中を GREP できるので便利)

RememberMeBeanDefinitionParser というのが唯一引っ掛かった。

class RememberMeBeanDefinitionParser implements BeanDefinitionParser {
    ...
    static final String ATT_FORM_REMEMBERME_PARAMETER = "remember-me-parameter";

この定数を使っているところを追うと、同じクラスの以下の場所にたどり着いた。

           if (remembermeParameterSet) {
                services.getPropertyValues().addPropertyValue("parameter",
                        remembermeParameter);
            }

なんか、 servicesparameter プロパティに設定している。

services が何なのか上の方を辿ると、

        if (isPersistent) {
            Object tokenRepo;
            services = new RootBeanDefinition(
                    PersistentTokenBasedRememberMeServices.class);

            ...
        }
        else if (!servicesRefSet) {
            services = new RootBeanDefinition(TokenBasedRememberMeServices.class);
        }

となってた。
つまり、 PersistentTokenBasedRememberMeServicesTokenBasedRememberMeServices ということになる。

共通の親クラスである AbstractRememberMeServices を見ると、 parameter がいた。

public abstract class AbstractRememberMeServices implements RememberMeServices,
        ...
    public static final String DEFAULT_PARAMETER = "remember-me";

        ...
    private String parameter = DEFAULT_PARAMETER;

間違いなさそう。

この parameter を使ってる場所を見てみる。

   public final void loginSuccess(HttpServletRequest request,
            HttpServletResponse response, Authentication successfulAuthentication) {

        if (!rememberMeRequested(request, parameter)) {
            logger.debug("Remember-me login not requested.");
            return;
        }

        onLoginSuccess(request, response, successfulAuthentication);
    }

rememberMeRequested(request, parameter) ってところが、完全に Remember-Me を有効にすべきかどうか判定しているっぽい。

false なら何もせずに return し、 true なら onLoginSuccess() 呼んでるので、おそらく間違いない。

rememberMeRequested() を見てみる。

    protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
        if (alwaysRemember) {
            return true;
        }

        String paramValue = request.getParameter(parameter);

        if (paramValue != null) {
            if (paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on")
                    || paramValue.equalsIgnoreCase("yes") || paramValue.equals("1")) {
                return true;
            }
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Did not send remember-me cookie (principal did not set parameter '"
                    + parameter + "')");
        }

        return false;
    }

泥臭いことしてた!

学び

  • Spring Security はチェックボックスvalue が省略された場合の値がブラウザによって異なることを知っているので、可能性を全て網羅する形で値をチェックしている!
  • 逆に言うと、あらゆるブラウザのチェックボックスのデフォルト value に対応しようと思った場合は、上のようにすればいい

Run-As についていろいろ試した

Run-As について Hello World から気になる点までを一通り調べた。

ただ、結局 RunAsImplAuthenticationProvider の価値については分からなかった。

そもそも、この機能を使う機会はあるのかもあやしいが。

34. Run-As Authentication Replacement

34. Run-As Authentication Replacement

34.1 Overview

The AbstractSecurityInterceptor is able to temporarily replace the Authentication object in the SecurityContext and SecurityContextHolder during the secure object callback phase.

AbstractSecurityInterceptor は、セキュアオブジェクトが呼び出されているフェーズの間、 SecurityContextSecurityContextHolder の中にある Authentication オブジェクトを一時的に置き換えることができます。

This only occurs if the original Authentication object was successfully processed by the AuthenticationManager and AccessDecisionManager.

これは、オリジナルの Authentication オブジェクトが AuthenticationManagerAccessDecisionManager によって成功裏に処理されている場合にだけ発生します。

The RunAsManager will indicate the replacement Authentication object, if any, that should be used during the SecurityInterceptorCallback.

RunAsManagerSecurityInterceptorCallback の間に使用されるべき Authentication オブジェクトを示します。

By temporarily replacing the Authentication object during the secure object callback phase, the secured invocation will be able to call other objects which require different authentication and authorization credentials.

セキュアオブジェクトの呼び出しフェーズの間に使用される一時的に置き換えられた Authentication オブジェクトによって、保護された実行は他の認証と認可を必要とする他のオブジェクトを呼ぶことができます。

It will also be able to perform any internal security checks for specific GrantedAuthority objects.

それは、指定された GrantedAuthority オブジェクトのための任意の内部セキュリティチェックも行うことができるでしょう。

Because Spring Security provides a number of helper classes that automatically configure remoting protocols based on the contents of the SecurityContextHolder, these run-as replacements are particularly useful when calling remote web services.

なぜなら、 Spring Security は SecurityContextHolder の内容をベースにした遠隔プロトコルを自動で設定する多くのヘルパークラスを提供しています。
run-as 置き換えは、遠隔の Web サービスを呼び出すときに特に便利です。

34.2 Configuration

A RunAsManager interface is provided by Spring Security:

RunAsManager インターフェースは Spring Security によって提供されています。

Authentication buildRunAs(Authentication authentication, Object object,
    List<ConfigAttribute> config);

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

The first method returns the Authentication object that should replace the existing Authentication object for the duration of the method invocation.

最初のメソッドは、既存の Authentication オブジェクトをメソッド実行の間だけ置き換えるための Authentication オブジェクトを返します。

If the method returns null, it indicates no replacement should be made.

もしメソッドが null を返す場合、置き換えが不要であることを示します。

The second method is used by the AbstractSecurityInterceptor as part of its startup validation of configuration attributes.

2つ目のメソッドは、 AbstractSecurityInterceptor によって起動時の configuration attributes の検証の一部として使用されます。

The supports(Class) method is called by a security interceptor implementation to ensure the configured RunAsManager supports the type of secure object that the security interceptor will present.

supports(Class) メソッドはセキュリティインターセプターの実装によって、セキュアインターセプターが提供したセキュアオブジェクトの型を、設定された RunAsManager がサポートすることを確実にするために実行されます。

One concrete implementation of a RunAsManager is provided with Spring Security.

1つの RunAsManager の実装クラスが Spring Security によって提供されています。

The RunAsManagerImpl class returns a replacement RunAsUserToken if any ConfigAttribute starts with RUN_AS_.

RunAsManagerImpl クラスは、もし任意の ConfigAttributeRUN_AS_ で始まる場合に、置き換えられた RunAsUserToken を返します。

If any such ConfigAttribute is found, the replacement RunAsUserToken will contain the same principal, credentials and granted authorities as the original Authentication object, along with a new GrantedAuthorityImpl for each RUN_AS_ ConfigAttribute.

もしそのような ConfigAttribute が見つかった場合、置き換えられた RunAsUserToken は、オリジナルの Authentication オブジェクトと同じプリンシパル、資格情報、そして与えられた権限と、個々の ConfigAttribute で設定された RUN_AS_ による新しい GrantedAuthorityImpl を含みます。

Each new GrantedAuthorityImpl will be prefixed with ROLE_, followed by the RUN_AS ConfigAttribute.

それぞれの GrantedAuthorityImpl は前に ROLE_ が付き、後ろに RUN_AS ConfigAttribute による設定が続きます

For example, a RUN_AS_SERVER will result in the replacement RunAsUserToken containing a ROLE_RUN_AS_SERVER granted authority.

例えば、 RUN_AS_SERVERRunAsUserToken に置き換えられると、 ROLE_RUN_AS_SERVER が設定されます。

The replacement RunAsUserToken is just like any other Authentication object.

置き換えられた RunAsUserToken は、ちょうどその他の Authentication オブジェクトのようなものです。

It needs to be authenticated by the AuthenticationManager, probably via delegation to a suitable AuthenticationProvider.

それは、適切な AuthenticationProvider に委譲し、 AuthenticationManager によって認証されている必要があります。

The RunAsImplAuthenticationProvider performs such authentication.

RunAsImplAuthenticationProvider は、そのような認証を行います。

It simply accepts as valid any RunAsUserToken presented.

それは単純に任意の提供された RunAsUserToken を有効として許可します。

To ensure malicious code does not create a RunAsUserToken and present it for guaranteed acceptance by the RunAsImplAuthenticationProvider, the hash of a key is stored in all generated tokens.

悪意のあるコードが RunAsUserToken を作成せず、 RunAsImplAuthenticationProvider によって確実に受け入れられるようにするために、生成されたすべてのトークンにキーのハッシュが格納されます。

The RunAsManagerImpl and RunAsImplAuthenticationProvider is created in the bean context with the same key:

RunAsManagerImplRunAsImplAuthenticationProvider は同じキーでコンテキストの Bean として作られます。

<bean id="runAsManager"
      class="org.springframework.security.access.intercept.RunAsManagerImpl">
  <property name="key" value="my_run_as_password"/>
</bean>

<bean id="runAsAuthenticationProvider"
      class="org.springframework.security.access.intercept.RunAsImplAuthenticationProvider">
  <property name="key" value="my_run_as_password"/>
</bean>

By using the same key, each RunAsUserToken can be validated it was created by an approved RunAsManagerImpl.

同じキーを使うことで、それぞれの RunAsUserToken は許可された RunAsManagerImpl によって作られたことを検証できます。

The RunAsUserToken is immutable after creation for security reasons

RunAsUserToken はセキュリティのため作られた後は変更できません。