本の執筆時点から SprignBoot 自身を始め多くのライブラリのバージョンが変わっています。それらバージョン変更に伴う修正点をここにまとめ、ソースコードとともにここに公開します。
変更なし
Java21 が必要なため、Pleiades から最新の Eclipse をインストールします。これには STS や Lombok 等も含まれているため、Eclipse 以外は別途インストールする必要はありません。
新規 Spring スターター・プロジェクトの設定値は以下。
- タイプ(本では型)は Maven
- Java バージョンは 21
- パッケージは com.example
新規 Spring スターター・プロジェクト依存関係の設定値は以下。
-
Spring Boot バージョンは 3.5.0
-
追加するライブラリ
分類 ライブラリ 開発者ツール Spring Boot DevTools
LombokSQL JDBC API
Spring Data JDBC
H2 Databaseテンプレートエンジン Thymeleaf Web Spring Web
Spring 起動時に実行する SQL の設定項目が変更されています。
[application.properties]
変更前
spring.datasouce.username=sa
spring.datasouce.password=
spring.datasource.sql-script-encoding=UTF-8
spring.datasource.initialize=true
spring.datasource.schema=classpath:schema.sql
spring.datasource.data=classpath:data.sql変更後
spring.datasource.username=sa
spring.datasource.password=
spring.sql.init.encoding=UTF-8
spring.sql.init.mode=ALWAYS
spring.sql.init.schema-locations=classpath:schema.sql
spring.sql.init.data-locations=classpath:data.sql変更なし
変更なし
webjars-locator のバージョン 0.52 (2024/7/5 時点の最新)が使用できます。
[pom.xml]
<!-- webjars-locator -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator</artifactId>
<version>0.47</version>
</dependency>Spring Boot 3 から JavaEE が JakartaEE 9 になったため、パッケージ名が javax.* となっているものをすべて jakarta.* に変更する必要があります。
[SignupForm.java]
変更前
import javax.validation.constraints.Email;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;変更後
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;(上記だけでなく、以降に出てくるすべてのコードの javax パッケージを変更します)
変更なし
Spring Boot 3.3 に対応した MyBatis-Spring-Boot-Starter はまだアナウンスされていません(2024/7/5 時点。MyBatis 公式サイト)が、バージョン 3.0.3 (2024/7/5 時点での最新)が使用できます。3.0.2 では Spring 起動時に失敗します。
また ModelMapper-Spring も新しいバージョンが使えるので 3.2.0 (2024/7/5 時点の最新)にします。
[pom.xml]
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<!-- Model Mapper -->
<dependency>
<groupId>org.modelmapper.extensions</groupId>
<artifactId>modelmapper-spring</artifactId>
<version>3.2.0</version>
</dependency>変更なし
Spring Boot 3.2 以降、存在しない URL にリクエストを送ると NoResourceFoundException という例外を発生させるようになったため、404 エラーではなく 500 エラーとなります。この例外を 404 エラーとして扱うには、10.2.3 Web アプリケーション全体の例外処理で作成する GlobalControllAdvice クラスに下記のメソッドを追加してください。
[SecurityConfig.java]
@ControllerAdvice
public class GlobalControllAdvice {
...(省略)
/** 静的リソースが見つからないときは HTTP 404 を返すようにする */
@ExceptionHandler(NoResourceFoundException.class)
public String handleNoResourceFound(NoResourceFoundException e, Model model) {
model.addAttribute("error", e.getMessage());
model.addAttribute("status", HttpStatus.NOT_FOUND);
return "error/404";
}
}Spring Boot 3 で Spring-Boot-Starter-Security を入れると SpringSecurity のバージョンが 5 ではなく 6 となります。それに合わせて Thymeleaf 拡張ライブラリ(セキュリティ)を Thymeleaf-Extras-SpringSecurity6 に変更します。
[pom.xml]
<!-- SpringSecurity -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Thymeleaf拡張ライブラリ-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>SpringSecurity 5.7 以降から、セキュリティ設定クラスの書き方が大きく変更されています。
-
WebSecurityConfigurerAdapter を継承せず、configure で行っている HttpSecurity http へのセキュリティ設定は SecurityFilterChain を Bean 定義して行う
-
webjars や css などはセキュリティ対象外として設定するのではなく、ログイン不要ページとして設定する
-
authorizeRequests()ではなく authorizeHttpRequests()を使う
-
設定はラムダ式で記述する
-
antMatchers()ではなく requestMatchers()を使う
-
requestMatchers()の引数に、文字列でパスを指定("/login"など)するとエラーになる(※)ため、文字列ではなく MvcRequestMatcher インスタンスをセットする具体的には、MvcRequestMatcher.Builder を Bean 登録して securityFilterChain の引数 mvc に DI でセットし、mvc.pattern("/user/signup") のようにして指定する※SpringMVC の管轄の "/" と H2 データベースの管轄の "/h2-console" が並存しており、文字列だけでは SpringMVC の管轄かどうかが判断できないためと思われる -
一般的な静的リソースの場所の指定(/webjars/**, /css/**, /js/**)は、PathRequest.toStaticResources().atCommonLocations() としてまとめて指定する
-
"/login" への直リンク許可設定は、次節のログイン処理設定で行うためここではまだ行わない
-
csrf().disable() は非推奨となったため、ラムダ式で csrf(csrf -> csrf.disabe()) のように指定する
-
H2 コンソールのパス("/h2-console/**")は、PathRequest.toH2Console() として指定する
-
H2 コンソールを表示させるためには、さらに以下の設定が必要
http.headers(headers -> headers.frameOptions(FrameOptionsConfig::disable)); http.csrf(csrf -> csrf.ignoringRequestMatchers(PathRequest.toH2Console()));
[SecurityConfig.java]
package com.example.config;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity // このアノテーションはこのアプリではなくてよい
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.requestMatchers(PathRequest.toH2Console()).permitAll()
.requestMatchers("/user/signup").permitAll()
.anyRequest().authenticated()
);
http.headers(headers -> headers
.frameOptions(FrameOptionsConfig::disable)
);
// CSRF 対策を無効に設定 (一時的)
http.csrf(csrf -> csrf
.ignoringRequestMatchers(PathRequest.toH2Console())
.disable()
);
return http.build();
}
}authorizeHttpRequests()を使用するようになったことで、直リンクをしたときは 403 エラーの共通画面ではなく、ログインページにリダイレクトするようになっています。 ただし、ここではまだログイン処理を実装していない(11.2.2 で実装)ためリダイレクトされず、403 のエラーコードだけが返されるようになっているため、アプリで用意した共通エラー画面ではなくブラウザのエラー画面が表示されます。 (403 エラーは 11.3「認可」のところで出すことができます。)
セキュリティ設定クラスの http.formLogin() もラムダ式で設定します。またこの設定のメソッドチェーンの最後に .permitAll() を追加し、ログイン関連処理("/login" 等)の直リンクを許可します。
[SecurityConfig.java]
@Configuration
@EnableWebSecurity
@EnableMethodSecurity // このアノテーションはこのアプリではなくてよい
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.requestMatchers(PathRequest.toH2Console()).permitAll()
.requestMatchers("/user/signup").permitAll()
.anyRequest().authenticated()
);
// 変更点 ここから
http.formLogin(login -> login
.loginProcessingUrl("/login")
.loginPage("/login")
.failureUrl("/login?error")
.usernameParameter("userId")
.passwordParameter("password")
.defaultSuccessUrl("/user/list", true)
.permitAll()
);
// ここまで
http.headers(headers -> headers
.frameOptions(FrameOptionsConfig::disable)
);
// CSRF 対策を無効に設定 (一時的)
http.csrf(csrf -> csrf
.ignoringRequestMatchers(PathRequest.toH2Console())
.disable()
);
return http.build();
}
}インメモリ認証の設定は auth.inMemoryAuthentication() メソッドではなく、 InMemoryUserDetailsManager を Bean 定義して行うように変更します。この中では SpringSecurity で用意されている User クラスを使って user と admin の2人のユーザーを作成しています。
[SecurityConfig.java]
@Configuration
@EnableWebSecurity
@EnableMethodSecurity // このアノテーションはこのアプリではなくてよい
public class SecurityConfig {
// 変更点 ここから
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// ここまで
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
...(省略)
}
// 変更点 ここから
@Bean
InMemoryUserDetailsManager userDetailsService() {
UserDetails user = User.withUsername("user")
.password("user")
.roles("GENERAL")
.build();
UserDetails admin = User.withUsername("admin")
.password("admin")
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
// ここまで
}バージョンが上がってから、デフォルトで Spring が用意しているメッセージソースが使用されるので、massages.properties の変更だけではメッセージ変更できなくなっています。
メッセージを変更するには、下記の修正を加えて、AuthenticationProvider のメッセージソースを変更してやる必要があります。
[JavaConfig.java]
@Configuration
public class JavaConfig {
@Bean
ModelMapper modelMapper() {
return new ModelMapper();
}
// 変更点ここから
@Bean
AuthenticationProvider daoAuthenticationProvider(PasswordEncoder passwordEncoder,
UserDetailsService userDetailsService, MessageSource messageSource) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder);
provider.setMessageSource(messageSource);
return provider;
}
// ここまで
}パスワードの暗号化は本の通りです。
[SecurityConfig.java]
@Configuration
@EnableWebSecurity
@EnableMethodSecurity // このアノテーションはこのアプリではなくてよい
public class SecurityConfig {
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
...(省略)
}
// 変更点 ここから
@Bean
InMemoryUserDetailsManager userDetailsService() {
PasswordEncoder encoder = passwordEncoder();
UserDetails user = User.withUsername("user")
.password(encoder.encode("user"))
.roles("GENERAL")
.build();
UserDetails admin = User.withUsername("admin")
.password(encoder.encode("admin"))
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
// ここまで
}本の通りに UserDetailService の実装クラス UserDetailServiceImpl を作成すれば、SecurityConfig から InMemoryUserDetailsManager の Bean 定義を削除するだけでユーザーデータ認証が行われるようになります。
[SecurityConfig.java]
@Configuration
@EnableWebSecurity
@EnableMethodSecurity // このアノテーションはこのアプリではなくてよい
public class SecurityConfig {
@Bean
PasswordEncoder passwordEncoder() {
...(省略)
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
...(省略)
}
// 変更点 ここから
/*
@Bean
InMemoryUserDetailsManager userDetailsService() {
UserDetails user = User.withUsername("user")
.password(encoder.encode("user"))
.roles("GENERAL")
.build();
UserDetails admin = User.withUsername("admin")
.password(encoder.encode("admin"))
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
*/
// ここまで
}http.logout()もラムダ式で記述します。http.*() のメソッドはメソッドチェーンで繋げて書くこともできます。
[SecurityConfig.java]
@Configuration
@EnableWebSecurity
@EnableMethodSecurity // このアノテーションはこのアプリではなくてよい
public class SecurityConfig {
@Bean
PasswordEncoder passwordEncoder() {
...(省略)
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.requestMatchers(PathRequest.toH2Console()).permitAll()
.requestMatchers("/user/signup").permitAll()
.anyRequest().authenticated()
);
http.formLogin(login -> login
.loginProcessingUrl("/login")
.loginPage("/login")
.failureUrl("/login?error")
.usernameParameter("userId")
.passwordParameter("password")
.defaultSuccessUrl("/user/list", true)
.permitAll()
// 変更点 ここから
).logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.permitAll() // logoutSuccessUrl で独自のログアウト成功エンドポイントを指定したら、それを許可する必要がある
// ここまで
);
http.headers(headers -> headers
.frameOptions(FrameOptionsConfig::disable)
);
// CSRF 対策を無効に設定 (一時的)
http.csrf(csrf -> csrf
.ignoringRequestMatchers(PathRequest.toH2Console())
.disable()
);
return http.build();
}
...(省略)
}コメントアウトするのはラムダ式内の.disable()のみで、.ignoringRequestMatchers(PathRequest.toH2Console())は残します。
[SecurityConfig.java]
@Configuration
@EnableWebSecurity
@EnableMethodSecurity // このアノテーションはこのアプリではなくてよい
public class SecurityConfig {
@Bean
PasswordEncoder passwordEncoder() {
...(省略)
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
...(省略)
// CSRF 対策を無効に設定 (一時的)
http.csrf(csrf -> csrf
.ignoringRequestMatchers(PathRequest.toH2Console())
// 変更点 ここから
//.disable()
// ここまで
);
return http.build();
}
...(省略)
}セキュリティ設定クラスへの URL 認可の設定も、antMatchers() ではなく requestMatchers() を使います。
[SecurityConfig.java]
@Configuration
@EnableWebSecurity
@EnableMethodSecurity // このアノテーションはこのアプリではなくてよい
public class SecurityConfig {
@Bean
PasswordEncoder passwordEncoder() {
...(省略)
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.requestMatchers(PathRequest.toH2Console()).permitAll()
.requestMatchers("/user/signup").permitAll()
// 変更点 ここから
.requestMatchers("/admin").hasAuthority("ROLE_ADMIN")
// ここまで
.anyRequest().authenticated()
);
...(省略)
}
...(省略)
}DataTables は新しいバージョンが使えるので 1.13.5 (2023/9/4 時点の最新) を使用します。それに伴い DataTables の言語設定ファイル名が変わっているので、list.js にも修正が必要です。
[pom.xml]
<!-- datatables -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>datatables</artifactId>
<version>1.13.5</version>
</dependency>
<!-- datatables-plugins -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>datatables-plugins</artifactId>
<version>1.13.5</version>
<scope>runtime</scope>
</dependency>[list.js]
'use strict'
var userData = null;
var table = null;
jQuery(function ($) {
...(省略)
});
function search() {
...(省略)
}
function createDataTables() {
if (table != null) {
table.destroy();
}
table = $('#user-list-table').DataTable({
language: {
url: '/webjars/datatables-plugins/i18n/ja.json'
},
...(省略)
});
}javax パッケージは jakarta パッケージに修正してください。(MUser, Department, SalaryKey, Salary の 4 クラス)