Spring bootでBasic認証後に任意の処理を実行する。

spring bootで認証機能を実装することは日常茶飯事です。
しかし、Basic認証にもかかわらず、認証成功後の処理、認証失敗後のlock処理や失敗回数リセット処理またはlog出力などを行うこともあるかもしれません。

一般的なformLoginやoauth2Loginであれば、
successHandlerやfailureHandlerに実装すれば解決なんですが。。。

日本語の情報が少なかったため投稿します。

## dependency
spring boot 2.0.4.RELEASE
[lombok](https://projectlombok.org/)
を使用しています。

## 実装
security の設定でBasic認証を実装します。
また、SharedObjectでRememberMeServicesインターフェースを実装したclassを登録します。

```java
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    MyRememberMeServices myRememberMeServices;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //ここでRemembeMeServiseを入れ替えることでBasic認証の前後で処理を実行できる。
        http.setSharedObject(RememberMeServices.class,myRememberMeServices);
        http.httpBasic()
                .and()
                    .authorizeRequests()
                    .anyRequest()
                    .authenticated()
                .and()
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); //Cookieによるセッション管理は不要;
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .passwordEncoder(NoOpPasswordEncoder.getInstance()) //spring5からは必須？
                .withUser("LLENN")
                .password("p-chan")
                .roles("GGO_USER");
    }

}
```

RememberMeServicesインターフェースにはSuccessまたはFailの際の処理を実装できます。

```java
@Slf4j
@Service
public class MyRememberMeServices implements RememberMeServices {

	@Override
	public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
		return null;
	}
	@Override
	public void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
		log.info("login:{}",successfulAuthentication.getName());
	}

	@Override
	public void loginFail(HttpServletRequest request, HttpServletResponse response) {
		//認証に使われたユーザーを取得する。
		String base64Credentials = request.getHeader("authorization").substring("Basic".length()).trim();
		String credentialSting = new String(Base64.getDecoder().decode(base64Credentials), Charset.forName("UTF-8"));
		String username = credentialSting.split(":")[0];

		log.error("login fail:{}",username);
	}
}
```


上記実装ではlogを出力しているだけですが、クラスは```@Service```なので好きなcomponentをDIできます。

しかし、**loginSuccessとloginFailはBasic認証処理の後に呼ばれていること**に気をつけてください。

ログインに失敗した場合は認証情報はありません。
そのため、loginFialの際には認証に使われたユーザー名を```Authentication::getName```からは取得することができず、HttpServletRequestのheader情報から取得する必要があります。

以上です。

ソースはgithubに上げました。
参考にしていただけると幸いです。
https://github.com/amanoese/spring-basic-auth-example

##余談

Basic認証のRememberMeServicesの処理を入れ替えるのは良いけど、元の実装ですごい処理してたらどうしよう。。と思ってソースを見たら下記のようなコードでした。

```java:NullRememberMeServices 
package org.springframework.security.web.authentication;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;

public class NullRememberMeServices implements RememberMeServices {
    public NullRememberMeServices() {
    }

    public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
        return null;
    }

    public void loginFail(HttpServletRequest request, HttpServletResponse response) {
    }

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

書き換えるために用意されているクラスみたいですね。
自分が実装を追った限りautoLoginが呼ばれている場所が見当たらなかったため困惑したりしました。。。

springは難しい。