開発環境
OS:Win10
IDE:Eclipse
Java8
Spring Boot 2.2.1
依存:spring security,thymeleaf,spring web,devtools,lombok,mysql connector
DB:MySQL
参考URL:spring security reference
Springスターター・プロジェクト依存関係
プロジェクトを作成する際に、Spring Securityを選択してください。
Entity Classの作成
作成物の想定としては、簡易的なCRM(顧客管理システム)です。
CRMにログインするために、USER権限とADMIN権限の2種類を作成しました。
DBには「アカウントユーザーが有効か無効か」と「Admin権限が付与されているかいないか」を判断するカラムをBoolean型で持たせています。
※DB側では TINYNT型 で設定。
デフォルトコンストラクタ以外に、アカウント新規登録用のコンストラクタも作成しております。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | package com.example.image.entity; import java.util.List;... @Entity @Table (name = "accounts" ) @Setter @Getter public class EmployeeEntity { @Id @Column (name = "id" ) @GeneratedValue (strategy = GenerationType.IDENTITY) private Integer id; // login accountID @NotBlank (message = "必須項目です" ) @Column (name = "employee_number" , nullable = false , unique = true ) private String employeeNumber; @NotBlank (message = "必須項目です" ) @Column (name = "employee_name" , nullable = false ) private String employeeName; // ユーザーのアイコン @Column (name = "employee_photo" ) private String employeePhoto; @NotBlank (message = "必須項目です" ) @Column (name = "password" , nullable = false ) private String password; // Userが有効かのチェック @Column (name = "enabled" , nullable = false ) private boolean enabled; // admin権限保有者かのチェック @Column (name = "admin" , nullable = false ) private boolean admin; // デフォルトコンストラクタ protected EmployeeEntity() { } // UserDetails用コンストラクタ public EmployeeEntity(String employeeNumber, String employeeName, String password, boolean isAdmin) { setEmployeeNumber(employeeNumber); setEmployeeName(employeeName); setPassword(password); setAdmin(isAdmin); setEnabled( true ); setEmployeePhoto( "noimage.jpg" ); } } |
Repositoryの作成
Repositoryは、JpaRepositoryかCrudRepositoryのいずれかをextendsする。JPAの方が使いなれているので、今回はJpaRepositoryを継承しました。
継承したら、社員番号からユーザー情報を探すメソッドを定義する。
01 02 03 04 05 06 07 08 09 10 11 12 | package com.example.image.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Repository; import com.example.image.entity.EmployeeEntity; @Repository public interface EmployeeRepository extends JpaRepository<EmployeeEntity,Integer>,JpaSpecificationExecutor<EmployeeEntity> { public EmployeeEntity findByEmployeeNumber(String employeeNumber); } |
UserDetailsを継承するClassの作成
UserDetailsは認証情報を保持する場所で、すでに用意されているUserDetailsInterfaceをオーバーライドしていく。
ログインIDがgetUsername()にデフォルト設定されているが、今回は、社員番号をログインIDとして使用したいため、戻り値をEntity classのアクセッサーで指定します。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | public class LoginAccount implements UserDetails { private static final long serialVersionUID = 1L; private EmployeeEntity employeeEntity; private Collection<GrantedAuthority> authorities; // デフォルトコンストラクタ protected LoginAccount() {} // アカウントと認証情報を処理するコンストラクタ public LoginAccount(EmployeeEntity employeeEntity,Collection<GrantedAuthority> authorities) { this .employeeEntity = employeeEntity; this .authorities = authorities; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return this .authorities; } @Override public String getPassword() { return employeeEntity.getPassword(); } // 社員番号をログインIDにする @Override public String getUsername() { return employeeEntity.getEmployeeNumber(); } // 使用しないのでtrue @Override public boolean isAccountNonExpired() { return true ; } // 使用しないのでtrue @Override public boolean isAccountNonLocked() { return true ; } // 使用しないのでtrue @Override public boolean isCredentialsNonExpired() { return true ; } // ユーザーが有効かどうか @Override public boolean isEnabled() { return employeeEntity.isEnabled(); } } |
UserDetailsServiceを実装する
service classでは、ログイン許可を判断し、ROLE_権限を付与してUserDetailsに受け渡す処理を記載していきます。
権限はROLE_で始まるという暗黙条件がありますので、ルールに従いましょう。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | @Service public class EmployeeService implements UserDetailsService { @Autowired private EmployeeRepository repository; @Override public UserDetails loadUserByUsername(String employeeNumber) throws UsernameNotFoundException { // ユーザー入力がnullの場合 if (employeeNumber == null || "" .equals(employeeNumber)) { System.out.println( "Username is empty" ); throw new UsernameNotFoundException( "Username is empty" ); } // Entityに格納されている(EmployeeRepository)ユーザー情報と突合 EmployeeEntity employeeEntity = repository.findByEmployeeNumber(employeeNumber); if (employeeEntity == null ) { System.out.println( "Username not found:" + employeeNumber); throw new UsernameNotFoundException( "Username not found:" + employeeNumber); } // ユーザーが有効でないとき if (!employeeEntity.isEnabled()) { System.out.println( "Username not anabled:" + employeeNumber); throw new UsernameNotFoundException( "Username not anabled:" + employeeNumber); } LoginAccount account = new LoginAccount(employeeEntity, getAuthrities(employeeEntity)); return account; } // 権限の付与 private Collection<GrantedAuthority> getAuthrities(EmployeeEntity employeeEntity) { if (employeeEntity.isAdmin()) { // admin権限がtrueのとき return AuthorityUtils.createAuthorityList( "ROLE_ADMIN" , "ROLE_USER" ); } else { // admin権限がfalseの時、USER権限を付与する return AuthorityUtils.createAuthorityList( "ROLE_USER" ); } } } |
spring securityを有効にする
securityを発動させるには、configクラスを作成して認証に関わる各種パラメータの設定を行う必要があります。
パスワードのハッシュ化にはBCryptを使用。
Configクラスの設定はこちらのサイトを参考にしました。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | @Configuration @EnableWebSecurity public class WebSecurityConfigurerAdapter extends org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; // ハッシュ化をBeanに登録 @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // 認証を除外するもの @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers( "/favicon.ico" , "/css/**" , "/js/**" , "/images/**" , "/fonts/**" , "/upload/**" ); } // 認証設定 @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers( "/login" ).permitAll(). antMatchers( "/admin/**" ).hasRole( "ADMIN" ) .anyRequest().authenticated(); // 認証にかかわるパラメータを指定 // .loginPageには、htmlのform object名を入力 http.formLogin().loginProcessingUrl( "/login" ).loginPage( "/login" ) .failureUrl( "/login?error" ).defaultSuccessUrl( "/top" , true ).usernameParameter( "employeeNumber" ).passwordParameter( "password" ); // logoutしたらcookieを削除し、セッションを無効にする。 http.logout().logoutUrl( "/logout" ).logoutSuccessUrl( "/login" ) .deleteCookies( "JESSIONID" ).invalidateHttpSession( true ); } // 独自認証の設定 @Autowired protected void congigureAuthenticationManager(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } } |
Controller Classの作成
保有している権限をViewで表示したい時は、@AuthenticationPrincipalを使用する。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 | @RequestMapping ( "/top" ) public String top( @AuthenticationPrincipal LoginAccount account, Model model) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth.getPrincipal() instanceof LoginAccount) { account = LoginAccount. class .cast(auth.getPrincipal()); model.addAttribute( "authorities" , account.getAuthorities().toString()); } else { model.addAttribute( "authorities" , "" ); } // employeNumberを利用してemployee nameを検索。そしてviewへ受け渡す EmployeeEntity user = repository.findByEmployeeNumber(account.getUsername()); model.addAttribute( "userName" , user.getEmployeeName()); return "top" ; } |
HTMLでのコンテンツ切り替え
thymeleafにはspring securityを使用できるがうまくいかなかったのでif文でコンテンツの表示・非表示を実装しました。
UserとAdminで見ることのできるnavメニューを制限しました。
User権限>アカウント編集とアカウント追加は見れない
Admin権限>すべてのメニューが選択可能
※参考
thymeleafのsecを使用する場合は、htmlタグ内に、
xmlns:sec=”http://www.thymeleaf.org/thymeleaf-extras-springsecurity5″
を追加します。
1 2 3 4 5 6 7 8 9 | < nav > < ul class = "menu" > < li >< a href = "clientlist" >顧客検索・一覧</ a ></ li > < li >< a href = "registerclient" >新規顧客追加</ a ></ li > < li th:if = "${authorities} == '[ROLE_ADMIN, ROLE_USER]'" >< a href = "admin" >CRMアカウント編集</ a ></ li > < li th:if = "${authorities} == '[ROLE_ADMIN, ROLE_USER]'" >< a href = "/admin/register" >CRMアカウント追加</ a ></ li > </ ul > </ nav > |