認証、認可機能追加
登録機能作成(ユーザ登録)で作成したテーブル(user_info, user_role)のデータを利用して認証認可処理を追加する。
1. 認証処理(Formログイン)
- ログイン画面を作成する。
/demo/src/main/resources/templates/login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" th:href="@{/css/common.css}">
<title>Login</title>
</head>
<body>
<div class="header">
<h2>ログイン</h2>
</div>
<div th:if="${param.error}" style="color: red;">
ユーザ名またはパスワードが正しくありません。
</div>
<div th:if="${param.logout}" style="color: blue;">
ログアウトしました。
</div>
<div id="content">
<form th:action="@{/login}" method="post">
<table>
<tr>
<td><label for="username">ユーザ名</label></td>
<td><input name="username" type="text" ></td>
</tr>
<tr>
<td><label for="password">パスワード</label></td>
<td><input name="password" type="password" ></td>
</tr>
</table>
<button>ログイン</button>
</form>
</div>
</body>
</html>
- ログイン画面を表示するためのViewControllerを追加する。
/demo/src/main/java/com/ziqoo/demo/MvcConfig.java
package com.ziqoo.demo;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/loginPage").setViewName("login");
}
}
- ユーザ情報を取得するEntityクラスを作成する。user_infoとuser_roleを一度に取得するため、UserInfoを継承したクラスにUserRoleのSetをフィールドとして持たせる。
/demo/src/main/java/com/ziqoo/demo/security/dao/entity/Account.java
package com.ziqoo.demo.security.dao.entity;
import java.time.LocalDateTime;
import java.util.Set;
import org.springframework.data.relational.core.mapping.MappedCollection;
import org.springframework.data.relational.core.mapping.Table;
import com.ziqoo.demo.dao.table.entity.UserInfo;
import com.ziqoo.demo.dao.table.entity.UserRole;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@Data
@EqualsAndHashCode(callSuper = true)
@Table("user_info")
public class Account extends UserInfo {
@MappedCollection(idColumn = "user_id")
private Set<UserRole> roleSet;
public Account(Integer id, String username, String password, boolean enabled, LocalDateTime createAt,
LocalDateTime updateAt, Set<UserRole> roleSet) {
super(id, username, password, enabled, createAt, updateAt);
this.roleSet = roleSet;
}
}
- ユーザ情報を取得するRepositoryクラスを作成する。findByUsernameメソッドを定義する。
/demo/src/main/java/com/ziqoo/demo/security/dao/repository/AccountRepository.java
package com.ziqoo.demo.security.dao.repository;
import java.util.Optional;
import org.springframework.data.repository.CrudRepository;
import com.ziqoo.demo.security.dao.entity.Account;
public interface AccountRepository extends CrudRepository<Account, Integer> {
Optional<Account> findByUsername(String username);
}
- ログインユーザ情報を保持するUserDetailsクラスを作成する。
/demo/src/main/java/com/ziqoo/demo/security/service/AccountDetails.java
package com.ziqoo.demo.security.service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.ziqoo.demo.security.dao.entity.Account;
import lombok.Getter;
@Getter
public class AccountDetails implements UserDetails {
private Integer id;
private String username;
private String password;
private boolean enabled;
private LocalDateTime createAt;
private LocalDateTime updateAt;
private List<GrantedAuthority> authorities;
public AccountDetails(Account account) {
this.id = account.getId();
this.username = account.getUsername();
this.password = account.getPassword();
this.enabled = account.isEnabled();
this.createAt = account.getCreateAt();
this.updateAt = account.getUpdateAt();
this.authorities = new ArrayList<>();
account.getRoleSet().forEach(r -> authorities.add(new SimpleGrantedAuthority(r.getRole())));
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
}
- ログインユーザ情報を取得するUserDetailsServiceを作成する。
/demo/src/main/java/com/ziqoo/demo/security/service/AccountDetailsService.java
package com.ziqoo.demo.security.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import com.ziqoo.demo.security.dao.entity.Account;
import com.ziqoo.demo.security.dao.repository.AccountRepository;
public class AccountDetailsService implements UserDetailsService {
@Autowired
AccountRepository accountRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account account = accountRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("ユーザが存在しません。"));
return new AccountDetails(account);
}
}
- WebSecurityConfigクラスからinMemoryAuthenticationを設定しているメソッドを削除する。
- formLogin,logoutを以下のように設定する。
- AccountDetailsServiceをBean登録する。
/demo/src/main/java/com/ziqoo/demo/WebSecurityConfig.java
@EnableWebSecurity
@Configuration
public class WebSecurityConfig {
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
// セキュリティ対象外を指定
return web -> web.ignoring().requestMatchers(
new AntPathRequestMatcher("/favicon.ico"),
new AntPathRequestMatcher("/css/**"),
new AntPathRequestMatcher("/img/**"),
new AntPathRequestMatcher("/js/**"));
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.formLogin(login -> login
.loginProcessingUrl("/login")
.loginPage("/loginPage")
.permitAll()
.defaultSuccessUrl("/", true)
).logout(logout -> logout
permitAll()
).authorizeHttpRequests(authz -> authz
.requestMatchers(
new AntPathRequestMatcher("/"),
new AntPathRequestMatcher("/thSamplePage/**")).permitAll()
.anyRequest().authenticated()
);
return http.build();
}
/**
* パスワードエンコーダー.
* @return PasswordEncoder
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService() {
return new AccountDetailsService();
}
}
2. 認可処理
コントローラのメソッドに対して認可処理を追加する。
- WebSecurityConfigクラスに@EnableGlobalMethodSecurity(prePostEnabled = true)を追加する。
/demo/src/main/java/com/ziqoo/demo/WebSecurityConfig.java
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
- 対象のコントローラメソッドに対し、認可するRoleを@PreAuthorize("hasAuthority('ロール文字列')")のように指定する
/demo/src/main/java/com/ziqoo/demo/web/controller/account/AccountController.java
@PreAuthorize("hasAuthority('Admin')") // Adminロールを持つユーザのみ実行可能
@PostMapping("/account/register")
public String register(@Validated AccountForm form, BindingResult bindingResult, Model model,
RedirectAttributes ra) {
- 複数ロールを認可する場合は@PreAuthorize("hasAnyAuthority('ロール文字列', 'ロール文字列')")のようにhasAnyAuthorityを使用する。
3. ログアウト
- URL:/logoutに対してpostすればログアウトできる。ログアウトに成功すると/loginPage?logoutにリダイレクトされる。
実装例
<form id="logoutForm" th:action="@{/logout}" method="post">
<a id="logout" href="javascript:void(0);">ログアウト</a>
</form>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script type="text/javascript">
$(function(){
$("#logout").on("click", function(){
$("#logoutForm").submit();
});
});
</script>

