Spring Securityを使ってユーザ認証とアクセス制御を試して見ようという事で、実装にチャレンジして、ハマった話(というか脱出できなかった話)。殴り書きご容赦。
#エラー
ユーザフォームにusernameとpasswordを入力し「ログイン」ボタンを押すと以下のエラーが発生
Caused by: java.lang.NullPointerException
at com.ec.train03.service.CustomUserDetailsService.loadUserByUsername(CustomUserDetailsService.java:69)
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:114)
... 46 more
#現象
デバッガでこれを辿ると、JpaRepositoryが、SecurityFilter側からコールされるとAutowiredされない現象が発生している。「CustomUserDetailsService」はNullで無く、この「loadUserByUsername」内から参照されているuserRespositoryがNullになっている。
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private CustomUserDetailsRepository customUserDetailsRepository; ←★ここがAutowiredされない
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// TODO 自動生成されたメソッド・スタブ
if (username == null || "".equals(username)) {
throw new UsernameNotFoundException("Username is empty");
}
User user = customUserDetailsRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found: " + username);
}
return user;
}
}
下の「図モドキ」は正しく書けてはないが、ざっと処理のイメージです。
userDetailsService ← (Autowired) ←userRepository←DB(PostgreSQL)
.loadUserByUsername
↑ ↓ UserDetails
DaoAuthenticationProvider
↑
SecurityFilter
userDetailsServiceのプロパティにuserRepositoryをAutowiredを設定している
Spring data jpaを使ってユーザ情報と権限情報をPosgreSQLのテーブルに保持する。
#原因
不明。
#確認した事
突き止めるために調べたり、試したりした事。
①Annotationをミスしてないか?(@Repositoryの付け忘とか、@Serviceの付け忘れとか)
、、、そもそもWeb.xmlとかpom.xmlとかservlet-context.xmlとかも
②ComponetScanの設定漏れとかミスとか
③JpaRepositoriesの設定漏れとかミスとか
④@Autowiredの設定ミス?
⑤xmlでの設定や、Javaconfigで挙動が違う?(どちらでも再現する?)
⑥springのVersionの問題?
spring-securityやSpring-data-jpaのVersionの問題?
(結局、Versionの問題を調べられるノウハウが無かった)
springframework 4.3.25.RELEASE
spring-security 4.2.0.RELEASE
spring-data-jpa 1.11.4.RELEASE
⑦Jpa絡みの機構が動いてるか?確認するために、⑥の組合せで、認証用のUser(UserDetails)のEntitiyをWebの画面から
入力して永続化(テーブル保存)、1件参照、一覧参照を実装して、動作する事を確認した → 動いた。
⑧Autowiredがダメなのか?と思って、UserDetailsServiceをnewしてconfigureでセットしたが、この場合、当たり前だがuserRepositoryはAutowiredされない。
最後は、テンパってしまって、JpaRespositoryをインプリして、インプリクラス実装しようかと思ったが、それってJpaを利用するメリットを全部投げ捨てる事で「本末転倒するぎる」って事でやめました、、、
#似た話
検索して回ったもの。解決してないようにみえるもの。
- Autowire doesn't work for custom UserDetailsService in Spring Boot
https://stackoverflow.com/questions/38647002/autowire-doesnt-work-for-custom-userdetailsservice-in-spring-boot - Custom UserDetailsService it seems to be not autowired
https://stackoverflow.com/questions/35344135/custom-userdetailsservice-it-seems-to-be-not-autowired - Spring fails to autowire repository
https://stackoverflow.com/questions/32067571/spring-fails-to-autowire-repository - Autowired CrudRepository into UserDetailsService is always null
https://stackoverflow.com/questions/25536596/autowired-crudrepository-into-userdetailsservice-is-always-null - Why is my Spring @Autowired field null?
https://stackoverflow.com/questions/19896870/why-is-my-spring-autowired-field-null
#暫定として(SpringBoot)
⑥で実装したものとは別に、Spring Boot 1.5.22で実装したら、所定の動作で認証できたので、やはり何かの組合せ問題だろうかと、想像している。
そもそもなんで、今時ナマのSpringframeworkって話なのだけど、基本的な動作を追跡するためにやってるのだけども。