Spring Securityで独自の認証を実装する
環境
Spring Boot 1.4
やりたいこと
ログイン画面で所属部署コード、ユーザーID、パスワードを入力してログイン。認証はDBやAPI等、何かしら独自のロジックで行う。
用意するクラス
ユーザー情報クラス
ユーザー情報を保持するためのクラス。認証に必要な項目だけではなく、氏名や部署名など付随する情報も持てるようにしておくとあとあと便利に使える。
public class User { private String id; private String name; private String deptCode; private String deptName; // 以下getter setter }
AuthenticationFilter
認証のためのトークンを作成するクラス。まずはフォームから受け取ったパラメータでユーザー情報を作成。そしてユーザー情報とパスワードからトークンを作成し、後続の認証処理に渡す。
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter { @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) { User user = new User(); user.setId(request.getParameter("userId")); user.setDeptCode(request.getParameter("deptCode")); String password = obtainPassword(request); // トークンの作成 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(user, password); setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } }
AuthenticationProvider
実際に認証を行うクラス。受け取ったトークンからユーザー情報とパスワードを取得してなんらかの独自認証を行う。ロールの付与もここで行う。
import java.util.ArrayList; import java.util.Collection; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @Configuration public class CustomAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication auth) throws AuthenticationException { User user = (User)auth.getPrincipal(); String password = (String)auth.getCredentials(); // ここで認証とロールの付与 Collection<GrantedAuthority> authorityList = new ArrayList<>(); if (user.getId().equals("admin")) { authorityList.add(new SimpleGrantedAuthority("ROLE_ADMIN")); } else if (user.getId().equals("user")) authorityList.add(new SimpleGrantedAuthority("ROLE_USER")); else { throw new BadCredentialsException("Authentication Error"); } return new UsernamePasswordAuthenticationToken(user, password, authorityList); } @Override public boolean supports(Class<?> authentication) { return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); } }
supports
メソッドには受け取ったトークンが処理すべきものなのか判定する処理を書く。false
を返すとフレームワークは別の認証プロバイダーを探そうとするので、これを利用して複数の認証方法を提供することもできる。
設定
WebSecurityConfigurerAdapter
を継承したクラスで設定を行う。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; 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.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired CustomAuthenticationProvider authenticationProvider; @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/css/**", "/js/**", "/images/**", "/loginForm"); } @Override public void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/login").permitAll() .anyRequest().authenticated() .and() .logout() .logoutUrl("/logout").permitAll() .logoutSuccessUrl("/loginForm"); CustomAuthenticationFilter filter = new CustomAuthenticationFilter(); filter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login", "POST")); filter.setAuthenticationManager(authenticationManagerBean()); filter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/loginForm?error")); http.addFilterBefore(filter, CustomAuthenticationFilter.class); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(authenticationProvider); } }
上記はログインフォームのURLが/loginForm
、ログインのリクエストを受け取るURLが/login
となるサンプル。ポイントは前述したCustomAuthenticationProvider
とCustomAuthenticationFilter
をセットしているところ。
あとがき
DBで認証するならばAuthenticationProviderは自作せずにDaoAuthenticationProvider
を使用するのが正攻法なのかもしれない。が、調べた感じだとむしろ面倒くさくなりそうではある。