はじめに
Spring Securityでは、デフォルトでForm認証で、パラメータ名がusername
、password
となっている。そんなに凝ったことしなくてもいいのであれば、デフォルトの認証機構を使うことで、コード量を減らせるが、最近だとJSON形式による認証も増えてきているので、Spring Securityでどうカスタマイズしたらいいのか色々調べてみた。
認証パラメータ
今回の認証を行うためのパラメータは以下の通り。
パラメータ名 | 内容 |
---|---|
メールアドレス | |
password | パスワード |
tenantCode | テナントコード |
手順
1. 認証用のパラメータを格納するクラスを用意
Principal.java
@Getter
@Setter
public class Principal {
private String email;
private String tenantCode;
private String password;
}
2. ログインユーザ情報を格納するクラスを用意
LoginUser.java
@Getter
@Setter
public class LoginUser extends org.springframework.security.core.userdetails.User{
private long userId;
private String userName;
// 他に保持したいプロパティがあれば追加
}
3. 認証の前処理を行うフィルターを用意
CustomAuthenticationFilter.java
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) {
try {
Principal principal = new ObjectMapper().readValue(request.getInputStream(),
Principal.class);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
principal, null);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
protected void successfulAuthentication(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth) {
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
4. 認証処理を行うクラスを用意
CustomAuthenticationProvider.java
@Configuration
public class CustomAuthenticationProvider implements AuthenticationProvider {
// ユーザ情報リポジトリ
@Autowired
UserRepository userRepository;
// テナント情報リポジトリ
@Autowired
TenantRepository tenantRepository;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// CustomAuthenticationFilterクラスから渡されたものを取得
Principal principal = (Principal) authentication.getPrincipal();
if (principal == null) {
throw new BadCredentialsException("認証情報がありません");
}
// テナントコードの有効チェック
Tenant tenant = tenantRepository.findByTenantCode(principal.getTenantCode());
if (tenant == null || tenant.getStatus() == 0) {
throw new BadCredentialsException("テナントが無効です");
}
// メールアドレスとテナントコードでユーザの有効チェック
User user = userRepository.findByEmailAndTenantId(principal.getEmail(), tenant.getId());
// ユーザー情報を取得できなかった場合
if (user == null) {
throw new BadCredentialsException("ユーザが存在しません");
}
if (!new BCryptPasswordEncoder().matches(principal.getPassword(), user.getPassword())) {
throw new BadCredentialsException("パスワードが間違っています");
}
List<GrantedAuthority> authorityList = new ArrayList<>();
// 権限付与処理
LoginUser loginUser = new LoginUser();
loginUser.setUserId(user.getId());
loginUser.setUserName(user.getUserName());
return new UsernamePasswordAuthenticationToken(loginUser, principal.getPassword(),
authorityList);
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
5. WebSecurityConfigurerAdapter
の継承クラスを用意
SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
CustomAuthenticationProvider authenticationProvider;
@Override
protected void configure(HttpSecurity http) throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
filter.setRequiresAuthenticationRequestMatcher(
new AntPathRequestMatcher("<ログインのURL>", "POST"));
filter.setAuthenticationManager(authenticationManagerBean());
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("<認証不要なURL>")
.permitAll()
.anyRequest()
.authenticated()
.and()
.logout()
.logoutRequestMatcher(
new AntPathRequestMatcher("<ログアウトのURL>", "POST"))
.and()
.addFilter(filter);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
}