Posted at

Spring SecurityでRBACなサンプルを作ってみた

More than 1 year has passed since last update.

@kawasimaさんが以前に書かれていた記事「業務システムにおけるロールベースアクセス制御」で公開されていたサンプルコードを、Spring Security + Spring MVC + Thymeleafで作成してみました(Spring Boot 2も利用してます)。

コード -> https://github.com/MasatoshiTada/rbac-example-springsecurity

この記事を読む前に、上記@kawasimaさんのブログを先に読んでおくことをおすすめします。


Spring SecurityのJava Config作成


src/main/java/com/example/security/SecurityConfig.java

@EnableWebSecurity

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
public void configure(WebSecurity web) throws Exception {
// Spring Security ignores URLs of static resources
web.ignoring().requestMatchers(
PathRequest.toStaticResources().atCommonLocations());
}

@Override
protected void configure(HttpSecurity http) throws Exception {
// configure login
http.formLogin()
.loginPage("/login")
.usernameParameter("account")
.passwordParameter("password")
.defaultSuccessUrl("/", true)
.permitAll();
// configure logout
http.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.permitAll()
.invalidateHttpSession(true);
// configure URL authorization
http.authorizeRequests()
.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();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}


ロールではなくパーミッションをhasAuthority()でチェックしているのがポイントです。

@kimullaaさんのブログ「SpringSecurity 権限に基づいた認可をする」を参考にしました。


ビジネスロジッククラス


src/main/java/com/example/services/IssueService.java

@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);
}
}



src/main/java/com/example/services/UserService.java

@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の書き込みができるようにしたい、とか。

そのような変更が発生した場合、ロールでチェックしているとソースコードへの修正が必要になります。

しかし、ソースコード内ではパーミッションでチェックしておけば、データベース側にレコードを追加するだけで済みます。