Tagbangers Blog

SpringアプリケーションでIPアドレスの検証がしたい!

オーストラリアからこんにちは!

インターン生の古家です。

みなさんはSpringのアプリケーションでIPアドレスの検証がしたい!と思ったことはありますか?

例えば、アプリケーションにIPアドレスによってアクセス制限をかけたいときなどに、IPアドレスの検証が必要になります。

このサイトにあるように、configレベルで静的に制限をかけるのはSpring Securityの機能で簡単にできます。しかし、例えばユーザーごとに別々のIPアドレス制限をかけるなど、動的に制限をかけたい場合はどうでしょう?

実はSpring Securityには、IpAddressMatcherというクラスがあります。これとカスタムのAuthenticationProviderを組み合わせて使えば、いとも簡単に動的なIPアドレス制限ができてしまうのです!!


IpAddressMatcherとは?

IpAddressMatcherとは、その名の通りIPアドレスマッチングをしてくれるクラスです!


使い方は非常にシンプルです。

まず、許可したいIPアドレスのStringで指定し、それをコンストラクタに渡してnewします。

IpAddressMatcher matcher = new IpAddressMatcher("192.168.1.3");

これだけでMatcherは完成です!なんて簡単なんでしょう。


また、CIDR表記により範囲を指定することも可能です。

matcher = new IpAddressMatcher("192.168.1.3/24");


あとは、そのIpAddressMatcherインスタンスのmatches()メソッドにIPアドレスをStringで渡すだけで、検証を行ってくれます。指定された範囲内のIPであればTrue、そうでなければFalseを返してくれます。

matcher.matches("192.168.1.5") // returns True
matcher.matches("192.168.0.5") // returns False


もう一度いいます。なんて簡単なんでしょう!

オムライスを作るより簡単です。

実際の実装は、以下を参考にしてみてください。


AuthenticationProviderを実装する

validateIpForClient()メソッドで、IpAddressMatcherを用いたIPアドレスの検証を行っています。

@RequiredArgsConstructor
public class CustomIpAuthenticationProvider implements AuthenticationProvider {

    private final UserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        WebAuthenticationDetails details = (WebAuthenticationDetails) authentication.getDetails();
        String userIp = details.getRemoteAddress();

        AuthorizedUser authorizedUser = (AuthorizedUser) userDetailsService.loadUserByUsername(authentication.getName());

        try {
            validateIpForClient(userIp, authorizedUser);
        } catch (Exception e) {
            authentication.setAuthenticated(false);
            throw new BadCredentialsException("Invalid IP Address");
        }

        return new UsernamePasswordAuthenticationToken(authorizedUser, authorizedUser.getPassword(), authorizedUser.getAuthorities());
    }

    static void validateIpForClient(String userIp, AuthorizedUser user) throws AuthenticationException {
        for (IpAddress address: user.getIpAddresses()) {
            IpAddressMatcher matcher = new IpAddressMatcher(address.getAddress());
            if (matcher.matches(userIp)) {
                return;
            }
        }

        throw new BadCredentialsException("Invalid IP Address");
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return clazz.equals(UsernamePasswordAuthenticationToken.class);
    }
}


SecurityConfigでAuthenticationProviderを使用する

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   @Autowired
   private CustomIpAuthenticationProvider authenticationProvider;

   @Override
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.authenticationProvider(authenticationProvider);
   }

   @Override
   protected void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests()
            .antMatchers("/login").permitAll()
            .anyRequest().authenticated()
            .and().formLogin().permitAll()
            .and().csrf().disable();
   }

}


いかがでしたか?なかなか簡単ですよね。

今回は以上です。みなさんも是非つかってみてください。