公式ドキュメントのAnonymous Authenticationにあるとおり、セキュリティにおいては許可を与えたもの以外はすべて拒否がベターである。spring-securityをwebで使う場合はfilter chainで実装される。このとき、認証ユーザはfilter chainを通す一方、未認証ユーザはバイパスすると、未認証ユーザだけ特殊扱いになり不便な場合もある。そこでspring-securityはanonymous認証ユーザを用意している。実質的には未認証な認証ユーザという位置づけになる。
以降のサンプルコードはspring-security 6がベース。build.gradle
は以下の通り。
plugins {
id 'java'
id 'org.springframework.boot' version '3.0.3'
id 'io.spring.dependency-management' version '1.1.0'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}
tasks.named('test') {
useJUnitPlatform()
}
両者の分かりやすい挙動の違いはcontrollerにおいて認証オブジェクトであるAuthentication
の取得とその型。参考:公式ドキュメントのGetting Anonymous Authentications with Spring MVC
@GetMapping("/sample")
String sample(Authentication authentication, @CurrentSecurityContext SecurityContext context) {
...
注意点としては、anonymousだとAuthentication
引数はnull
になる。これを避けたければ@CurrentSecurityContext
を使う。
状態 | 認証済み | 未認証(anonymous) |
---|---|---|
Authentication | UsernamePasswordAuthenticationToken | null |
@CurrentSecurityContext | UsernamePasswordAuthenticationToken | AnonymousAuthenticationToken |
またanonymousはROLE_ANONYMOUS
という認可をデフォルトで持つのでこれでアクセス制御が出来る。たとえば、以下にすると/anon
は未認証ユーザのみアクセス可能になる。参考:https://www.tenohira.xyz/tech/spring-anonymous-only-access/
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.formLogin(login -> login
// 省略
).authorizeHttpRequests(authz -> authz
.requestMatchers("/anon").anonymous()
//.requestMatchers("/anon").hasRole("ANONYMOUS") // 上と同じ意味
.anyRequest().authenticated()
);
return http.build();
}
thymeleafでanonymousかどうかで何等かの表示の出し分けをするのは以下の通り。より細かい使い方については https://kagamihoge.hatenablog.com/entry/2019/11/12/195357 を参照。
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="https://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>anonymous sample</title>
</head>
<body>
<div sec:authorize="isAnonymous()">
ANONYMOUSです。
</div>
<div sec:authorize="hasRole('ANONYMOUS')">
ANONYMOUSです。
</div>
</body>
</html>
anonymousの認可をデフォルトのROLE_ANONYMOUS
から変更したり、principalを変更したり、anonymous認証の無効化は以下のようにする。
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.
// 省略
).anonymous((anon) -> anon
.authorities("ROLE_HOGE", "ROLE_FOO")
.principal("アノニマス")
// .disable() // 無効化
);