Spring BootとSpring Securityのユーザ認証について、忘れがちになるので、備忘録φ(..)メモメモ
認証の方法はいくつかあるけど、今回は、
- 暫定対応時のインメモリDBと
- 本格対応時の独自ユーザテーブル
の2つをメモφ(..)メモメモ
まずはbuild.gradleにDependencyに追加する
Spring Securityを使うには、spring-boot-starter-security
を追加
ロール/権限によるthymeleafのテンプレートの制御のため、thymeleaf-extras-springsecurity4
も追加
(この記事では登場しません。。。まだこんど。。。)
dependencies { //SpringBootのDependency compile('org.springframework.boot:spring-boot-starter-data-jpa') compile('org.springframework.boot:spring-boot-starter-thymeleaf') compile('org.springframework.boot:spring-boot-starter-web') //SpringSecurityを使うため、追加 compile('org.springframework.boot:spring-boot-starter-security') compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity4') ・・・ }
ログインフォームはこんな感じ
username
にユーザIDが、password
にパスワードが詰まって、/login
に遷移する感じ
認証が失敗したら、アラートが出るようにメッセージとth:if
を設定
<html lang="ja" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <meta charset=" utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>タイトル</title> </head> <body onload="document.login.username.focus();"> <!-- ログインが失敗した時のメッセージ --> <div class="alert alert-danger" role="alert" th:if="${session['SPRING_SECURITY_LAST_EXCEPTION']} != null"> <strong>ログインに失敗しました: </strong>ユーザが存在しないか、パスワードが間違っています。 </div> <!-- ログインのフォーム --> <form class="form-signin" action="" th:action="@{/login}" method="post" name="login"> <h2 class="form-signin-heading">ログイン画面</h2> <input type="text" class="form-control" placeholder="ユーザID" name="username" /> <input type="password" class="form-control" placeholder="パスワード" name="password" /> <button class="btn btn-lg btn-primary btn-block" type="submit">ログイン</button> </form> </body> </html>
どちらの認証でも必要な下準備
まずは、Spring Securityを有効にするためのConfigクラスを作成
WebSecurityConfigurerAdapter
を継承したクラスを用意
@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() // アクセス権限の設定 // staticディレクトリにある、'/css/','fonts','/js/'は制限なし .antMatchers("/css/**", "/fonts/**", "/js/**").permitAll() // '/admin/'で始まるURLには、'ADMIN'ロールのみアクセス可 .antMatchers("/admin/**").hasRole("ADMIN") // 他は制限なし .anyRequest().authenticated() .and() // ログイン処理の設定 .formLogin() // ログイン処理のURL .loginPage("/login") // usernameのパラメタ名 .usernameParameter("username") // passwordのパラメタ名 .passwordParameter("password") .permitAll() .and() // ログアウト処理の設定 .logout() // ログアウト処理のURL .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) // ログアウト成功時の遷移先URL .logoutSuccessUrl("/login") // ログアウト時に削除するクッキー名 .deleteCookies("JSESSIONID") // ログアウト時のセッション破棄を有効化 .invalidateHttpSession(true) .permitAll() ; } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //TODO インメモリDBと独自ユーザテーブルで処理が変わる部分 } }
インメモリDBでユーザ認証を実装するには
インメモリDBでの認証は、超簡単!!
これだけ!!
@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { ・・・ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() // ユーザ名'user', パスワード'user',ロール'USER'のユーザを追加 .withUser("user").password("user").roles("USER") .and() // ユーザ名'admin', パスワード'admin',ロール'ADMIN'のユーザを追加 .withUser("admin").password("admin").roles("ADMIN"); } }
独自のユーザテーブルでユーザ認証を実装するには
独自のユーザテーブルは、認証だけではなく、ユーザ情報などを自由に追加できるので、柔軟に設定できる。
でも、その分、やらなければいけないことが多い。。。
- ユーザテーブルの
Entity
- ユーザテーブルの
Repository
- 認証用の
Service
細かいことは、Serviceに任されるので、WebSecurityConfigはとても簡潔
@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserInfoService userInfoService; (中略) @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userInfoService); } }
ユーザテーブルのEntity
をつくる
認証に利用するEntityには、UserDetails
の実装が必要
Entityはこんな感じ。@Columnのgetter/setterはLombokで生成!
@Setter @Getter @Entity @Table(name = "user_info") public class UserInfoEntity implements UserDetails { private static final long serialVersionUID = 1L; public enum Authority {ROLE_USER, ROLE_ADMIN}; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long userId; @Column(nullable = false, unique = true) private String username; @Column(nullable = false) private String password; @Column private String email; @Column(nullable = false) @Enumerated(EnumType.STRING) private Authority authority; @Override public Collection<? extends GrantedAuthority> getAuthorities() { List<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority(authority.toString())); return authorities; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
OverrideのisAccountNonExpired
,isAccountNonLocked
,isCredentialsNonExpired
,isEnabled
は、期限切れやLock状態、有効無効などのフラグ。
これらも認証に使いたい場合は、ここらへんもテーブルのカラムに追加すればOK。
今回は使わないので、常にtrue
を返してます。
ユーザテーブルのRepository
を作る
UserInfoEntityのRepositoryを用意
認証にusernameでのselectが必要なので、それだけ追加。クエリはJPAにおまかせ
public interface UserInfoRepository extends JpaRepository<UserInfoEntity, Long> { public UserInfoEntity findByUsername(String username); }
認証用のService
をつくる
認証用のサービスとして扱うために、UserDetailsService
の実装が必要
中身は、必須チェックと存在チェックして、Entityの返却している感じ
@Service public class UserInfoService implements UserDetailsService { @Autowired private UserInfoRepository userInfoRepo; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if (username == null || "".equals(username)) { throw new UsernameNotFoundException("Username is empty"); } UserInfoEntity userInfo = userInfoRepo.findByUsername(username); if (userInfo == null) { throw new UsernameNotFoundException("User not found for name: " + username); } return userInfo; } }
(おまけ) 認証されたユーザをコントローラで使う
認証されたユーザは、Principal principal
から取得できる
こんな感じ
@Controller public class HogeController { @RequestMapping(value = "/hoge", method = RequestMethod.GET) public String index(Principal principal, Model model) { Authentication authentication = (Authentication) principal; UserInfoEntity user = (UserInfoEntity) authentication.getPrincipal(); model.addAttribute("user", user); return "hoge/index"; } }
以上!!
参考になる書籍
Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発
- 作者: 株式会社NTTデータ
- 出版社/メーカー: 翔泳社
- 発売日: 2016/07/21
- メディア: 大型本
- この商品を含むブログ (1件) を見る
- 作者: 掌田津耶乃
- 出版社/メーカー: 秀和システム
- 発売日: 2017/12/20
- メディア: 単行本
- この商品を含むブログを見る
- 作者: 掌田津耶乃
- 出版社/メーカー: 秀和システム
- 発売日: 2018/01/30
- メディア: 単行本
- この商品を含むブログを見る
はじめてのSpring Boot―スプリング・フレームワークで簡単Javaアプリ開発 (I・O BOOKS)
- 作者: 槙俊明
- 出版社/メーカー: 工学社
- 発売日: 2016/09/01
- メディア: 単行本
- この商品を含むブログ (1件) を見る
参考にしたサイト様
- Spring Bootでユーザ認証 | memorandum
- Spring bootでweb セキュリティ(ログイン認証)編
- spring boot その7 - spring security で 認証を行う - huruyosi’s blog
- SpringBoot(with Thymeleaf)チートシート[随時更新] - Qiita
- Spring Security 画面表示の制御 - NOTEです
- 6.3. 認証 — TERASOLUNA Global Framework Development Guideline 1.0.0.publicreview documentation
- Spring Boot でログイン画面 + 一覧画面 + 登録画面の Webアプリケーションを作る ( その8 )( ログイン画面作成 ) - かんがるーさんの日記