WebSecurityConfigurerAdapterが非推奨になってた
2022.06.02SpringBoot 2.7.0でプロジェクトを作ってSpringSecurityを使おうと、いつものようにWebSecurityConfigurerAdapterを継承しようとしたら非推奨という警告が出てました。
この際、SpringSecurityを初歩から学びなおすのと同時に新しい書き方で、インメモリ認証とJDBC認証を書いてみます。
HttpSecurityとインメモリ認証
新しい書き方で書くと以下のようになります
package com.volkruss.security.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfiguration {
// ログイン後は/homeに遷移させる
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable();
http.headers(header -> {
header.frameOptions().disable();
});
http.authorizeHttpRequests(authorize -> {
authorize.antMatchers("/h2-console/**").permitAll()
.anyRequest().authenticated();
});
http.formLogin(form -> {
form.defaultSuccessUrl("/home");
});
return http.build();
}
// misaka/mikotoでログインする
@Bean
public InMemoryUserDetailsManager userDetailsService(){
UserDetails user = User.withUsername("misaka")
.password(
PasswordEncoderFactories
.createDelegatingPasswordEncoder()
.encode("mikoto"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
- 今まではWebSecurityConfigurerAdapterを継承して、configureをメソッドをオーバーライドすることでHttpSecurityなどを設定していました
- これからはコンポーネントとして設定するようにします
HttpSecurityの設定
- BeanとしてSecurityFilterChainを登録するようにします
- HttpSecurityについては今まで通り設定できますが、最終的にはSecurityFilterChainを返すため、http.build()を返します。
インメモリ認証の設定
- 今まではAuthenticationManagerBuilder#inMemoryAuthenticationで取得できるInMemoryUserDetailsManagerConfigurerに対してUserDetailsを設定していました
- これからは、InMemoryUserDetailsManagerにUserDetailsを渡してInMemoryUserDetailsManagerを返すようにします(Beanとして登録します)
SecurityFilterChain
- FilterChainProxyがリストとして持っているFilterの実装クラスです。
- FilterChainProxyのdoFilterから取得されて利用されます
doFilterからgetFiltersメソッドまで呼ばれる
getFiltersメソッド
private List<Filter> getFilters(HttpServletRequest request) {
int count = 0;
for (SecurityFilterChain chain : this.filterChains) {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Trying to match request against %s (%d/%d)", chain, ++count,
this.filterChains.size()));
}
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
- FilterChainProxyはspringSecurityFilterChainというBean名でDIコンテナに登録されています
- DelegatingFilterProxyからFilterChainProxyは利用されます
- この辺はweb.xmlで書くと明示する必要があります
これで画面からmisaka/mikotoでログインできるようになります
JDBC認証
必要となる以下のテーブルを作成します。デフォルトで利用する場合は、テーブル名も必要なカラム名も指定されています。
- users
- authorities
コードを書きます
package com.volkruss.security.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import javax.sql.DataSource;
@Configuration
public class SecurityConfiguration {
@Autowired
private DataSource dataSource;
// ログイン後は/homeに遷移させる
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable();
http.headers(header -> {
header.frameOptions().disable();
});
http.authorizeHttpRequests(authorize -> {
authorize.antMatchers("/h2-console/**").permitAll()
.anyRequest().authenticated();
});
http.formLogin(form -> {
form.defaultSuccessUrl("/home");
});
return http.build();
}
@Bean
public UserDetailsManager userDetailsService(){
JdbcUserDetailsManager users = new JdbcUserDetailsManager(this.dataSource);
// データベースにshokuhou/misakiというレコードが追加されます
UserDetails user = User.withUsername("shokuhou")
.password(
PasswordEncoderFactories
.createDelegatingPasswordEncoder()
.encode("misaki"))
.roles("USER")
.build();
users.createUser(user);
return users;
}
}
- DataSourceをAutowiredしているのでapplication.yamlにデータベースの接続情報を記載しておきます
- JdbcUserDetailsManagerというのはJdbcDaoImplを継承した拡張クラスです
- またUserDetailsManagerの実装クラスです
- UserDetailsをcreateUserメソッドに渡すと
- そのUserDetailsがusersテーブルにインサートされる
- usernameに紐づく認可レコードがauthoritiesテーブルにインサートされる
- createUserで使われるSQLはJdbcUserDetailsManagerクラスのcreateUserSqlに登録されています
実際にインサートされたレコード
※misakaは予め入れておいたレコードです
これでshokuhou/misakiでログインできます
既存レコードでログイン
既存レコードというのは、ここでいうmisakaです。ソースコードでshokuhouというユーザーをcreateしましたが、データベースに既に登録されているレコードでログインを試します。
この時、ログインさせる条件で大事なことが以下でした
- パスワードがハッシュ化されていること
- authoritiesテーブルにも紐づくレコードが登録されていること
@Bean
public UserDetailsManager userDetailsService(){
// 既存User : misaka/mikoto
JdbcUserDetailsManager users = new JdbcUserDetailsManager(this.dataSource);
return users;
}
- データソースを渡すだけです
- 後はデータベースに紐づくレコードを取得してくれます
その他
もしもusersテーブルやauthoritiesテーブルでなく独自のテーブルを使いたい場合は以下のようにsetUsersByUsernameQueryメソッドなどを使用します
@Bean
public UserDetailsManager userDetailsService(){
String USERQUERY =
"select username,password,enabled from myusers where username = ?";
String AuthoritiesQuery =
"select username,role from myauthorities where username = ?";
JdbcUserDetailsManager users = new JdbcUserDetailsManager(this.dataSource);
// 独自のテーブルやカラム名を使いたい時
users.setUsersByUsernameQuery(USERQUERY);
users.setAuthoritiesByUsernameQuery(AuthoritiesQuery);
return users;
}
- 既存のusersテーブルからmyusersテーブルに変更してログイン可能になります
- カラムも独自に設定したカラム名でログインができます
- 同じようにログインできる条件
- ハッシュ化されたパスワードであること
- authorities(ここではmyauthorities)に紐づくレコードがあること
これで基本的な設定などはできるようになります。
今回のコードはGithubにもあげております
https://github.com/jirentaicho/laravel-new-SecurityConfiguration
使用したSQLは以下です(H2にて実行時に構築しています)
DROP TABLE IF EXISTS users;
CREATE TABLE "users" (
"id" SERIAL NOT NULL,
"username" varchar(64) NOT NULL PRIMARY KEY,
"password" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL
)
;
DROP TABLE IF EXISTS myusers;
CREATE TABLE "myusers" (
"id" SERIAL NOT NULL,
"username" varchar(64) NOT NULL PRIMARY KEY,
"password" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL
)
;
DROP TABLE IF EXISTS myauthorities;
CREATE TABLE "myauthorities" (
"id" SERIAL NOT NULL,
"username" varchar(64) NOT NULL PRIMARY KEY,
"role" TEXT NOT NULL
)
;
DROP TABLE IF EXISTS authorities;
CREATE TABLE "authorities" (
"id" SERIAL NOT NULL,
"username" varchar(64) NOT NULL PRIMARY KEY,
"authority" TEXT NOT NULL
)
;
-- 予め用意しているユーザーレコードを使う場合はハッシュ化されたパスワードを利用することが必須です(これはパスワードmikotoです)
-- またauthoritiesテーブルにもusernameに紐づくレコードが登録されていないとログインできません
INSERT INTO "users" ("username", "password","enabled") VALUES ('misaka', '{bcrypt}$2a$10$t.uzqGjte5WsosZO4wFV5OULSCqYJbn0qC5Lh0Uilj5hF.biixBoG', true);
insert into "authorities" ("username","authority") values ('misaka','level5');
-- 独自に定義したテーブルでJDBC認証を行う場合
INSERT INTO "myusers" ("username", "password","enabled") VALUES ('misaka', '{bcrypt}$2a$10$t.uzqGjte5WsosZO4wFV5OULSCqYJbn0qC5Lh0Uilj5hF.biixBoG',true);
insert into "myauthorities" ("username","role") values ('misaka','level5')
かなりぐちゃぐちゃになっているが奮闘記録もあります
https://zenn.dev/misaka/scraps/3ce785913f1bc3
参考
- https://spring.pleiades.io/spring-security/site/docs/current/api/org/springframework/security/web/FilterChainProxy.html
- https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter
- https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/jdbc.html