🍃
Spring Security 6.0でロール階層(RoleHierarchy)を実現する
その前に
業務でSpring Securityを使用してロール階層を実装しようとしたときに、ググっても古い実装方法しかでてこなかったため、1人日ほどかけてドキュメントを読み漁ってやっと実装できました。
日本語の記事もなかったためここに書き残そうと思います。
Githubに置きました
ロール階層ってなに?
たとえば、職場に「勤怠打刻システム」があったとして
- システム管理者
- 運用スタッフ
- 一般ユーザー
というロールがあったとき、それぞれのロールは
- 一般ユーザーは、自分の出退勤時刻を管理できる。
- 運用スタッフは、上司が自分の部下の出退勤の時刻を管理できる。
- システム管理者は、所属する会社の全職員の出退勤の時刻を管理できる。
ができるとする。
システム管理者がとある部署の部長で部下の勤怠の管理をし、部長自身も出退勤時刻の管理を行うことができる。
このようなシステムを構築する場合、実際の管理者には「システム管理者、運用スタッフ、一般ユーザー」という権限が付与されなくてはならないので、1ユーザーで3つのロールを持つことになります。
そして、ロジックではユーザーがどのようなロールを持っているかチェックし、操作の可否判定を行います。このような場合、ロール階層を設定しておけば1ユーザーに1ロールを付与すればチェックも一回で済みます。
ソース
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.logout((logout) -> logout.permitAll());
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails userAdmin =
User.withDefaultPasswordEncoder()
.username("admin")
.password("password")
.roles("ADMIN")
.build();
UserDetails userStaff =
User.withDefaultPasswordEncoder()
.username("staff")
.password("password")
.roles("STAFF")
.build();
UserDetails userBasic =
User.withDefaultPasswordEncoder()
.username("basic")
.password("password")
.roles("BASIC")
.build();
return new InMemoryUserDetailsManager(userAdmin, userStaff, userBasic);
}
// --------- ここ! ---------
@Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
String hierarchy = "ROLE_ADMIN > ROLE_STAFF \n ROLE_STAFF > ROLE_BASIC";
roleHierarchy.setHierarchy(hierarchy);
return roleHierarchy;
}
// -------------------------
}
Beanとして定義するだけ
@Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
String hierarchy = "ROLE_ADMIN > ROLE_STAFF \n ROLE_STAFF > ROLE_BASIC";
roleHierarchy.setHierarchy(hierarchy);
return roleHierarchy;
}
本当に動いているか確認
ユーザー「basic」でログイン
ユーザー「staff」でログイン
ユーザー「admin」でログイン
HTMLも載せておきます
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>ユーザ詳細情報</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/purecss@3.0.0/build/pure-min.css" integrity="sha384-X38yfunGUhNzHpBaEBsWLO+A0HDYOQi8ufWDkZ0k9e0eXz/tH3II7uKZ9msv++Ls" crossorigin="anonymous">
</head>
<body>
<div style="
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
">
<h3>ユーザ詳細情報</h3>
<table class="pure-table pure-table-bordered">
<tbody>
<tr>
<td>
ユーザ名
</td>
<td sec:authentication="name">
</td>
</tr>
<tr class="pure-table-odd">
<td>BASICロール</td>
<td>
<span sec:authorize="hasRole('ROLE_BASIC')">〇</span>
<span sec:authorize="!hasRole('ROLE_BASIC')">×</span>
</td>
</tr>
<tr>
<td>STAFFロール</td>
<td>
<span sec:authorize="hasRole('ROLE_STAFF')">〇</span>
<span sec:authorize="!hasRole('ROLE_STAFF')">×</span>
</td>
</tr>
<tr class="pure-table-odd">
<td>ADMINロール</td>
<td>
<span sec:authorize="hasRole('ROLE_ADMIN')">〇</span>
<span sec:authorize="!hasRole('ROLE_ADMIN')">×</span>
</td>
</tr>
<tr>
<td>ロールリスト</td>
<td th:text="${role}">
</td>
</tr>
</tbody>
</table>
<form th:action="@{/logout}" style="margin:10px">
<input type="submit" value="ログアウト">
</form>
</div>
</body>
</html>
参考にしたサイト
Discussion