Spring Security | 認証 | BCryptPasswordEncoder: パスワードハッシュ

パスワードをハッシュ化する理由

データが漏洩した場合、パスワードを平文で保存していると非常に危険です。通常、元の文字列を特定できない形式で保存します。復号が不要な場合は、ハッシュ関数を使用します。

BCrypt

MD5やSHAはよく知られたハッシュ関数ですが、パスワードのハッシュ化にはBCryptが便利です。

ハッシュ関数を1回適用するだけでは、総当たり攻撃、辞書攻撃、レインボーテーブルに対して脆弱です。

BCryptはランダムなsaltを追加し、複数回ハッシュ化するため、元のパスワードを推測しにくくします。Spring SecurityもBCryptを推奨しています。

実装

DaoAuthenticationProviderPasswordEncoderを設定します。BCryptを使用する場合は、BCryptPasswordEncoder実装を使用します。BCryptのハッシュ値は事前に計算して保存します。

XML Configuration

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="passwordEncoder"
          class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

    <sec:authentication-manager>
        <sec:authentication-provider>
            <sec:user-service>
                <sec:user
                    name="hoge"
                    password="$2a$08$CekzJRYhb5bzp5mx/eZmX.grG92fRXo267QVVyRs0IE.V.zeCIw8S"
                    authorities="ROLE_USER" />
            </sec:user-service>

            <sec:password-encoder ref="passwordEncoder" />
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>
  • BCryptPasswordEncoderをBeanとして定義し、<password-encoder>ref属性へ指定します。
  • <password-encoder><authentication-provider>の子要素として設定します。

Java Configuration

MySpringSecurityConfig.java

package sample.spring.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {

    ...

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Autowired
    public void configure(AuthenticationManagerBuilder auth, PasswordEncoder passwordEncoder) throws Exception {
        auth.inMemoryAuthentication()
            .passwordEncoder(passwordEncoder)
            .withUser("hoge")
            .password("$2a$08$CekzJRYhb5bzp5mx/eZmX.grG92fRXo267QVVyRs0IE.V.zeCIw8S")
            .roles("USER");
    }
}
  • AbstractDaoAuthenticationConfigurerpasswordEncoder()メソッドへPasswordEncoderを指定します。
  • AbstractDaoAuthenticationConfigurerは、AuthenticationManagerBuilder.inMemoryAuthentication()が返すInMemoryUserDetailsManagerConfigurerの親クラスです。

パスワードのエンコード

パスワードをエンコードするには、コンテナからPasswordEncoderを取得し、encode()を使用します。

EncodePasswordServlet.java

package sample.spring.security.servlet;

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

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;

@WebServlet("/encode-password")
public class EncodePasswordServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(req.getServletContext());
        PasswordEncoder passwordEncoder = context.getBean(PasswordEncoder.class);

        String password = req.getParameter("password");
        String encode = passwordEncoder.encode(password);
        System.out.println(encode);
    }
}
  • 設定は前述と同じで、BCryptPasswordEncoderをBeanとして登録します。
  • 例では動作確認のためPasswordEncoderを明示的に取得しています。実際にはコンテナ管理Beanへ注入します。
  • http://localhost:8080/encode-password?password=fugaへアクセスします。

サーバーコンソール出力

$2a$10$2qVDkAqxp8eDrxR8Be2ZpubYGOCVZ7Qy9uK/XzOIY1ZoxpChtrWDK