Eclipseで Spring スタータープロジェクト作成後、MyBatisでクラス自動作成
spring-boot-starter-securityのバージョンが上がり、WebSecurityConfigurerAdapterクラスが使えなくなったため整理。
- generatorConfig.xml
手動でやってるので若干違うかも・・・
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd" >
<generatorConfiguration >
<!-- mysql connector -->
<classPathEntry
location="/Users/[USER]/.m2/repository/mysql/mysql-connector-java/8.0.23/mysql-connector-java-8.0.23.jar" />
<context id="context1" >
<!-- JDBC -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/TestDB"
userId="root" password="">
<!-- Table Configuration matched more than one table 対策 -->
<!-- http://www.mybatis.org/generator/usage/mysql.html -->
<property name="nullCatalogMeansCurrent" value="true"/>
</jdbcConnection>
<!-- 自動生成するエンティティ -->
<javaModelGenerator
targetPackage="com.example.demo.entity"
targetProject="src/main/java/"
/>
<sqlMapGenerator
targetPackage="com.example.demo.entity"
targetProject="src/main/java/"
/>
<javaClientGenerator
targetPackage="com.example.demo.entity"
targetProject="src/main/java/"
type="XMLMAPPER"
/>
<!-- 自動生成対象のテーブル名 -->
<table tableName="LoginUser" domainObjectName="LoginUser">
<generatedKey column="id" sqlStatement="JDBC"/> <!-- auto increament 余計なXMLが入ってしまうため不要かも? -->
</table>
</context>
</generatorConfiguration>
テスト用の対象テーブル
CREATE TABLE IF NOT EXISTS LoginUser (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(64) NOT NULL,
password VARCHAR(128) NOT NULL
);
ライブラリのロードとセキュリティ用のクラス作成
以下のサンプルはアノテーションとimplementsが正しければクラス名はなんでもいいです。
-pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>
- SecurityConfig.java
ユーザー作成の時にパスワードをエンコードした処理をBCryptPasswordEncoderで定義しておく
これがないとユーザー認証ができないので注意
@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean //パスワードのエンコード
BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
//ログイン処理の実装
http.formLogin(login -> login //フォーム認証の設定
.loginProcessingUrl("/login") //ログイン処理のパス
.loginPage("/login") //ログインページの指定
.defaultSuccessUrl("/hello") //ログイン成功後の遷移先(任意で「/hello」とする)
.failureUrl("/login?error=true") //ログイン失敗時の遷移先(任意で「/login?error」とする)
.permitAll()
).logout(logout -> logout //ログアウトの設定記述開始
.logoutSuccessUrl("/login") //ログアウト成功後のリダイレクト先URL
).authorizeHttpRequests(authz -> authz //URLごとの認可設定
.requestMatchers(PathRequest.toStaticResources().atCommonLocations())
.permitAll() //css,jsなどへのアクセス許可
.anyRequest().authenticated() //ログインページ以外のURLへはログイン後のみアクセス可能
);
return http.build();
}
}
SecurityFilterChainがログイン時に自動的に実行される
認証用クラスの作成
UserDetailsクラスをimplementsして作成
ログイン後のこのクラスのインスタンスが保持される
${#authentication.principal} でテンプレート内からアクセスできる
public class AuthUserDetails implements UserDetails {
// 認証するユーザー情報
private final LoginUser loginUser;
private final Collection<? extends GrantedAuthority> authorities;
// コンストラクタ 認証したい任意のユーザー情報を指定
public AuthUserDetails(LoginUser loginUser) {
this.loginUser = loginUser;
// 仮の処理
List<String> list = new ArrayList<String>(Arrays.asList(loginUser.getUsername()));
this.authorities = list
.stream()
.map(role -> new SimpleGrantedAuthority(role))
.toList();
}
// テンプレート内で ${#authentication.principal.loginUser} アクセスできるようにするため
public LoginUser getLoginUser() {
return loginUser;
}
// 各メソッドをオーバーライド
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// TODO 自動生成されたメソッド・スタブ
return authorities;
}
# 略
認証用サービスクラスの作成
UserDetailsServiceクラスをimplementsして作成
loadUserByUsernameメソッドをオーバーライドしてSQLを実行されるようにする
テーブルからユーザーを見つけたらフォームで送信したパスワードはフレームワーク側で認証してくれる
@RequiredArgsConstructor
@Service
public class AuthUserDetailsServiceImpl implements UserDetailsService {
@Autowired
private LoginUserMapper loginUserMapper;
// loadUserByUsername のオーバーライド UserDetails オブジェクトを返却する
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// マッパーまたはレポジトリ内でこの処理は定義しておく
LoginUser loginUser = loginUserMapper.findByUsername(username);
AuthUserDetails user = new AuthUserDetails(loginUser);
return user;
}
}
メソッドの最後でUserDetailsクラスをimplementsして作成したクラスのインスタンスを返却する
ユーザー情報を認証以降も利用する場合は、上記クラス内で持てるようにプロパティ、getter/setterを用意しておく
コントローラー、テンプレートの定義
- コントローラー
テンプレートを返すだけのシンプルな例
@Controller
public class SecurityController {
@GetMapping("/login")
public String login() {
return "login";
}
}
- ログイン用テンプレート
<form th:action="@{/login}" method="post" name="Login_Form"
class="form-signin">
<h2 class="form-signin-heading">ログインしてください</h2>
<hr class="colorgraph">
<p th:if="${param.error}">ログインに失敗しました</p>
<br>
<input type="text" name="username" placeholder="username" value="hoge"/><br>
<input type="password" name="password" placeholder="password" value="12345678"/> <br>
<button name="Submit" value="Login" type="Submit">Login</button>
</form>
- ログイン後のテンプレート
<body th:with="user=${#authentication.principal.loginUser}">
<!-- 認証済みユーザー情報にアクセス -->
<h1>ログイン成功</h1>
<h2><span th:text="${user.username}"></span> さんHello!!!!</h2>
<form th:action="@{/logout}" method="post">
<button>Logout</button>
</form>
</body>
まとめ
アノテーションをつけてクラスを定義するだけで Spring Security が全て自動的に認証してくれるのですごく楽です。