@kawasimaさんが以前に書かれていた記事「業務システムにおけるロールベースアクセス制御」で公開されていたサンプルコードを、Spring Security + Spring MVC + Thymeleafで作成してみました(Spring Boot 2も利用してます)。
コード -> https://github.com/MasatoshiTada/rbac-example-springsecurity
この記事を読む前に、上記@kawasimaさんのブログを先に読んでおくことをおすすめします。
Spring SecurityのJava Config作成
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.formLogin(formLogin -> formLogin
// configure login
.loginPage("/login")
.usernameParameter("account")
.passwordParameter("password")
.defaultSuccessUrl("/", true)
.permitAll()
).logout(logout -> logout
// configure logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.permitAll()
.invalidateHttpSession(true)
).authorizeHttpRequests(authorize -> authorize
// configure URL authorization
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.mvcMatchers("/signup").permitAll()
.mvcMatchers(HttpMethod.GET, "/issues/").hasAuthority("readIssue")
.mvcMatchers(HttpMethod.GET, "/issue/new").hasAuthority("writeIssue")
.mvcMatchers(HttpMethod.POST, "/issues/").hasAuthority("writeIssue")
.mvcMatchers("/users").hasAuthority("manageUser")
.anyRequest().authenticated()
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
ロールではなくパーミッションをhasAuthority()
でチェックしているのがポイントです。
@kimullaaさんのブログ「SpringSecurity 権限に基づいた認可をする」を参考にしました。
ビジネスロジッククラス
@Service
public class IssueService {
// フィールド等省略
@PreAuthorize("hasAuthority('readIssue')")
@Transactional(readOnly = true)
public List<Issue> findAll() {
return issueRepository.findAll();
}
@PreAuthorize("hasAuthority('writeIssue')")
@Transactional
public void register(Issue issue, String account) {
issueRepository.register(issue, account);
}
}
@Service
public class UserService {
// フィールド等省略
@Transactional
public void register(User user, String roleName) {
String encodedPassword = passwordEncoder.encode(user.getPassword());
user.setPassword(encodedPassword);
userRepository.register(user, roleName);
}
@PreAuthorize("hasAuthority('manageUser')")
@Transactional(readOnly = true)
public List<User> findAll() {
return userRepository.findAll();
}
}
@PreAuthorize("hasAuthority('パーミッション名')")
で権限チェックをしています。やはりここでも、ロール名ではなくパーミッション名で指定しています。
ビュー
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<!--/* 省略 */-->
</head>
<body>
<div class="ui fixed inverted menu">
<div class="ui container">
<a href="#" class="header item">
RBAC Example
</a>
<a th:href="@{/}" href="../index.html" class="item"
sec:authorize="isAuthenticated()">
Home
</a>
<a th:href="@{/issues/}" href="../issue/list.html" class="item"
sec:authorize="isAuthenticated() and hasAuthority('readIssue')">
Issue
</a>
<a th:href="@{/users/}" href="../userAdmin/list.html" class="item"
sec:authorize="isAuthenticated() and hasAuthority('manageUser')">
Users
</a>
<!--/* 省略 */-->
sec:authorize
属性とhasAuthority('パーミッション名')
で権限チェックをしています。やはりここでも、ロール名ではなくパーミッション名で指定しています。
ロールをハードコーディングしない!
あるロールができることが増える(=あるロールが持つパーミッションが増える)ことって、けっこう頻繁に発生すると思います。例えば、guestロールでもIssueの書き込みができるようにしたい、とか。
そのような変更が発生した場合、ロールでチェックしているとソースコードへの修正が必要になります。
しかし、ソースコード内ではパーミッションでチェックしておけば、データベース側にレコードを追加するだけで済みます。