SpringSecurityのauthorizeHttpRequestsによるアクセス制御
皆さん、こんにちは。技術開発グループのn-ozawanです。
2月14日はバレンタインですね。中国・ベトナムでは男性から女性へプレゼントする日だそうです。男性の方々は家族サービスをしてみてはいかがでしょうか?
本題です。
Spring Securityでは、http.authorizeHttpRequests(...)
を使って、パスへのアクセス制御を行います。このアクセス制御はSpring Securityにおいては基礎中の基礎であり、公式ドキュメントでも1ページ丸ごと使って解説しています。今回はhttp.authorizeHttpRequests(...)
について解説しつつ、前回の別のやり方を示したいと思います。
目次
アクセスを制御する
基本
例えば以下のコードがあるとします。
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/secure/**").authenticated()
)
requestMatchers("/secure/**").authenticated()
は、リクエストのURLが/secure/**
に該当する場合に認証が必要であることを示します。つまり、認証していないユーザーは/secure/**
にアクセスすることが出来ません。コードの書き方としては、最初に制御対象のパスを指定して、そのパスに対してどう制御したいのかを指定します。

この構成を連続して記述することも可能です。
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/login_page").permitAll()
.anyRequest().authenticated()
)
上記の場合、ログイン画面である/login_page
へのアクセスは誰でも見れるようにし、それ以外のアクセスは認証が必要であることを示します。
パスの指定
パスの指定でよく使われるのはanyRequest()
とrequestMatchers(...)
です。anyRequest()
は全てのパスを指定します。requestMatchers(...)
は個別に指定することも可能ですし、ワイルドカードで指定することも可能です。
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/hoge", "/foo", "/bar").permitAll() // 個別に指定することが可能
.requestMatchers("/api/**").permitAll() // ワイルドカードで指定することも可能
.requestMatchers(HttpMethod.GET).permitAll() // HTTPメソッドで指定することも可能
.anyRequest().permitAll() // anyRequest()は全てのパスを指定
)
制御内容
制御には以下があります。
メソッド | 説明 |
---|---|
permitAll() | このリクエストに認証は不要であり、誰でもアクセスが可能です。 |
denyAll() | このリクエストはいかなる状況でも許可されません。 |
authenticated() | このリクエストでは認証が必要になります。 認証されない限り、アクセスすることは出来ません。 |
hasAuthority(…) | このリクエストでは特定の権限を有している必要があります。 権限を有していない場合はアクセス出来ません。 |
hasRole(…) | このリクエストでは特定のロールである必要があります。 異なるロールである場合はアクセス出来ません。 |
keycloakのアクセストークンで、ロールで制御する別の方法
前回、Spring Security でアクセストークンの検証についてお話ししました。その中で、keycloakが発行するアクセストークンには、そのユーザーに与えられたロールが格納されており、そのロールにより拒否するかどうかを検証するやり方を紹介しました。今回はhttp.authorizeHttpRequests(...)
を使って制御するやり方を紹介します。
まずはSecurityFilterChainの生成のところを修正します。
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// アクセストークン検証
http
.authorizeHttpRequests(authorize -> authorize.anyRequest().hasRole("admin"))
.oauth2ResourceServer(oauth2 ->
oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(this.jwtAuthenticationConverter()))
);
return http.build();
}
http.authorizeHttpRequests(...)
のところでは、hasRole("admin")
を指定することにより、adminロールが付与されていることを必要とします。oauth2.jwt(...)
では、前回は自前で用意したJwtDecoderを渡していましたが、今回は自前で用意したJwtAuthenticationConverterを渡します。
Spring Securityはユーザーの認証情報や、アクセストークンの情報などを、SecurityContextに保存します。今回特に注目すべきところは、AuthenticationのAuthoritiesです。Authorities
には認証したユーザーが所有する権限が保存されています。hasRole("admin")
はこのAuthorities
に”ROLE_admin”が存在するかどうかをチェックします。

今回チェックするロールは、keycloak独自のスキーマでアクセストークンに格納されています。なので、アクセストークンからロールを取得してAuthorities
に保存するための、JwtAuthenticationConverterを自前で用意する必要があります。以下がJwtAuthenticationConverterを生成しているコードになります。
private JwtAuthenticationConverter jwtAuthenticationConverter() {
DelegatingJwtGrantedAuthoritiesConverter converter = new DelegatingJwtGrantedAuthoritiesConverter(
new JwtGrantedAuthoritiesConverter(),
new Converter<Jwt, Collection<GrantedAuthority>>() {
@Override
public Collection<GrantedAuthority> convert(Jwt source) {
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
for (String role : getRoles(source)) {
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + role));
}
return grantedAuthorities;
}
@SuppressWarnings("unchecked")
private Collection<String> getRoles(Jwt jwt) {
Map<String, Object> claims = jwt.getClaims();
Map<String, Object> resourceAccess = (Map<String, Object>) claims.get("resource_access");
Map<String, Object> clientts = (Map<String, Object>) resourceAccess.get(claims.get("azp"));
return (Collection<String>) clientts.get("roles");
}
}
);
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(converter);
return jwtAuthenticationConverter;
}
2行目から22行目までが、アクセストークンの内容から、Authorities
に格納するGrantedAuthority
へ変換するための処理となります。3行目のJwtGrantedAuthoritiesConverter
はデフォルト処理となるConverterです。4行目から21行目までが、アクセストークンからロールを取得してGrantedAuthority
に変換する処理となります。
DelegatingJwtGrantedAuthoritiesConverter
は複数のJwtGrantedAuthoritiesConverter
を束ねるクラスです。24行目から26行目にて、JwtAuthenticationConverter
に、DelegatingJwtGrantedAuthoritiesConverter
を設定して返却しています。
おわりに
hasAuthority(...)
やhasRole(...)
などでアクセス制御が出来るようになると、より細かい制御が可能になります。例えば以下のようにすれば、リクエストが/api/admin/**
のAPIのみ、adminロールを必要とし、それ以外のAPIはadminロールなしでもアクセスすることが可能となります。前回紹介したやり方と比べると、より柔軟に制御できるようになります。
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// アクセストークン検証
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/api/admin/**").hasRole("admin"))
.anyRequest().authenticated()
.oauth2ResourceServer(oauth2 ->
oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(this.jwtAuthenticationConverter()))
);
return http.build();
}
ではまた。