システムデザイン

Spring Securityを用いたSpring Boot開発

*本記事は旧TechblogからCOLORSに統合した記事です。

目次

1.自己紹介
2.研修の内容
 2.1.作成するアプリの概要
 2.2.必要ライブラリ
 2.3.機能一覧
3.各種機能について
 3.1.ログイン機能
 3.2.セッション管理
 3.3.権限によるアクセス制限
4.まとめ
5.最後に

1.自己紹介

2018年12月に中途入社しました、H.Fと申します。
まずはじめに簡単ではありますが、私の経歴を紹介させていただきます。

前職では現在と同じくSESとして約半年ほど勤務していました。
開発案件には参画できましたが、希望するJavaの経験が
あまり積めそうになかったことなどから転職することにしました。

転職活動をしていたところ、m/fieldでは希望する
Javaでの開発案件も多いことから、入社を決意しました。
また、前職では初めて触れる言語の案件に1人で参画したため、
不安も多かったところ、m/fieldではチーム単位で案件に
アサインされるのも魅力でした。

そんな経験浅めで入社した私ですが、当記事ではm/fieldに入社して
研修にて行ったSpring Securityを用いたSpring Boot開発の内容を
ご紹介していきたいと思います。

2.研修の内容

ざっくり挙げると以下の2点になります。

  • Springの基本とSpring Securityを用いたアプリケーション作成
  • Spring Securityで学ぶWebの基礎

2.1.作成するアプリの概要

今回作成するアプリは、受注管理アプリです。商品の在庫なども管理します。
また、セキュリティに関してはSpring Securityを導入しました。

pom.xmlのdependenciesに以下を追加するとSpring Securityの機能が使えるようになります。
~pom.xml~

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
</dependency>

2.2.必要ライブラリ

  • Spring Security
  • Mybatis
  • maven

2.3.機能一覧

  • ログイン機能 ★
  • 各DBテーブルのCRUD機能
  • セッション / Cookie管理 ★
  • 権限によるアクセス制限 ★
  • ログ出力

3.各種機能について

前項で★のついているセキュリティまわりについてご紹介いたします。

3.1.ログイン機能

~認証の流れ~

図 認証の流れを表したフロー図
  1. WebSecurityConfigurerAdapterを継承したコンフィグレーションクラスでSpring Securityの設定が行われます。
  2. /loginにPOSTリクエストを送ると、Authentication Filterでフィルタリングされます。
  3. このときフォーム認証用のUsernamePasswordAuthenticationFilterが使用されます。
  4. 実際に認証を行うDaoAuthenticationProviderへ処理が委譲され、入力されたユーザ名とパスワードが送られます。
  5. 認証の際にDaoAuthenticationProviderはユーザ情報の検索をUserDetailsServiceに委譲し、DBから取得したユーザ情報をLoginUserにセットします。


DB認証の処理の流れは上図のようになっていますが、今回は実装部分について説明していきます。

~WebSecurityConfig.java~
まずはSpring Securityの設定を記述するWebSecurityConfigクラスを作成します。

[code lang=”java”]
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
public void configure(WebSecurity web) throws Exception {
// Spring Securityの制限を無視する(ロゴ画像を置いているところなど)
web.ignoring().antMatchers(&amp;amp;amp;amp;amp;quot;/css/**&amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;quot;/js/**&amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;quot;/images/**&amp;amp;amp;amp;amp;quot;);
} ・・・➀

@Override
protected void configure(HttpSecurity http) throws Exception {
// 未ログイン時でもアクセスを許可するhttp://localhost:8080以下のアドレス
http.authorizeRequests()
.antMatchers(&amp;amp;amp;amp;amp;quot;/&amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;quot;/login&amp;amp;amp;amp;amp;quot;).permitAll()
.anyRequest().authenticated(); ・・・➁

// ログイン処理
http.formLogin()
// ログイン処理を行う
.loginProcessingUrl(&amp;amp;amp;amp;amp;quot;/login&amp;amp;amp;amp;amp;quot;)
// ログインページURL
.loginPage(&amp;amp;amp;amp;amp;quot;/login&amp;amp;amp;amp;amp;quot;)
// ログイン成功時の遷移先URL(第二引数をtrueにすると必ず第一引数へ飛ぶ)
.defaultSuccessUrl(&amp;amp;amp;amp;amp;quot;/index&amp;amp;amp;amp;amp;quot;, true)
// ログイン画面のフォームのname属性を指定
.usernameParameter(&amp;amp;amp;amp;amp;quot;username&amp;amp;amp;amp;amp;quot;)
.passwordParameter(&amp;amp;amp;amp;amp;quot;password&amp;amp;amp;amp;amp;quot;); ・・・➂

// ログアウト処理
http.logout()
.logoutRequestMatcher(new AntPathRequestMatcher(&amp;amp;amp;amp;amp;quot;/logout**&amp;amp;amp;amp;amp;quot;))
// ログアウト時の遷移先URL
.logoutSuccessUrl(&amp;amp;amp;amp;amp;quot;/login&amp;amp;amp;amp;amp;quot;)
// ログアウトするとCookieのJSESSIONIDを削除
.deleteCookies(&amp;amp;amp;amp;amp;quot;JSESSIONID&amp;amp;amp;amp;amp;quot;); ・・・➃
}

// Password暗号化
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
} ・・・➄

}
[/code]

① CSSやJavaScriptのアドレスをignoring()で認証処理にかけないようにする。
② permitAll()で未ログイン時にもアクセスを許可する。
③ ログイン処理の設定。ログインページのURLやログイン成功時の遷移先、
  ログインフォームのname属性の指定などを設定できる。
④ ログアウト処理の設定。ログアウト時の遷移先やクッキーの削除などを設定できる。
⑤ パスワードの暗号化のためにBeanを設定しておく。



~UserDetailsServiceImpl.java~
次に、ユーザ情報を取得するためにUserDetailsServiceインターフェースを実装したUserDetailsServiceImplクラスを作成します。

[code lang=”java”]
@Component
public class UserDetailsServiceImpl implements UserDetailsService {

@Autowired
private UserMapper userMapper;

@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {

UserAccount user = null;
try {
// 入力されたusernameをもとにDBを検索
user = userMapper.findUser(username);
} catch (Exception e) {
throw new UsernameNotFoundException(&amp;amp;amp;amp;amp;quot;It could not be got the user&amp;amp;amp;amp;amp;quot;);
}

if(user == null) {
throw new UsernameNotFoundException(&amp;amp;amp;amp;amp;quot;User not found with Username : &amp;amp;amp;amp;amp;quot; + username);
}

return new LoginUser(user);
}
}

[/code]

入力されたusernameをもとにuserテーブルを検索し、ユーザを取得できなかった場合にUsernameNotFoundExceptionを返します。
ユーザを検索出来たら、そのユーザ情報をもとにLoginUserオブジェクトを作成します。
このLoginUserオブジェクトを用いてフォームに入力されたユーザ名・パスワードとの認証が行われます。

~LoginUser.java~
Spring Securityで実装されているUserクラスを継承したLoginUserクラスを作成します。

[code lang=”java”]public class LoginUser extends org.springframework.security.core.userdetails.User {
private static final long serialVersionUID = 1L;
private final UserAccount userAccount;

public LoginUser(UserAccount user) {
super(user.getUsername(), user.getPassword(),
// 権限によるアクセス制限のために取得
user.getAuthorities());
this.userAccount = user;
}

public UserAccount getUser() {
return userAccount;
}
}[/code]

ここではorg.springframework.security.core.userdetails.Userを継承しているのがポイントです。
認証ユーザ情報をスーパークラスのユーザ名、パスワード、権限にセットしています。

最後に、ユーザテーブル定義を書いておきます。

CREATE TABLE users (
    "id" INT NOT NULL AUTO_INCREMENT,
    "username" VARCHAR(20),  // ユーザ名
    "password" VARCHAR(60),  // パスワード
    "role" VARCHAR(20)       // 権限
)

passwordについては、BCryptPasswordEncoderを用いてハッシュ化すると60文字になるためVARCHAR(60)と設定しています。

3.2.セッション管理

セッション管理は ログインした後は一旦アプリケーションを終了させても、以降はログインなしでサービスを利用することができる機能です。
WebSecurityConfigurerAdapterを継承してコンフィグレーションクラスを作成している場合は、デフォルトでセッション管理機能が適用されています。
しかし、そのままセッションを継続するのはセキュリティとして脆弱性があるので、セッションタイムアウト時間を設定しておきましょう。

~application.properties~

[code lang=”java”]server.servlet.session.timeout=600 //秒単位で設定[/code]


次に、セッション固定攻撃対策として、ログイン前後でセッションIDを変更する機能がデフォルトで有効となります。
対策方法を変更する場合は以下のオプションから選択できます。
changeSessionId()(デフォルト)、migrateSession()、newSession()、none()

~WebSecurityConfig.java~

[code lang=”java”]http.sessionManagement().sessionFixation().changeSessionId();[/code]

また、セッションハイジャック対策として、Spring SecurityではCookieにHttpOnly属性を指定してJavaScriptからCookieにアクセスさせない機能がデフォルトで有効となっています。

3.3.権限によるアクセス制限

ログインユーザのもつ権限によってアクセス制限をかけます。
権限のないユーザが制限されたURLにアクセスしようとしてもアクセスできなくなります。

~WebSecurityConfig.java~

[code lang=”java”]@EnableGlobalMethodSecurity(securedEnabled = true)[/code]

コンフィグレーションクラスに@EnableGlobalMechodSecurityを追加することでメソッド呼び出しに対する認可処理が有効になります。(securedEnabled = trueの指定で@Securedが使用できるようになります。)
次に、コントローラやメソッドに@Secured(“ROLE_ADMIN”)を付与することで、ROLE_ADMIN権限を持つユーザのみがアクセス可能となります。
複数指定する場合は、@Secured({“ROLE_ADMIN”, “ROLE_USER”})と指定することで、ROLE_ADMINとROLE_USERどちらもアクセスできるようになります。

補足:Spring Securityの認可処理のためには権限情報に「ROLE_」のプレフィックスを付与しなければなりません。
  (例:ROLE_ADMIN、ROLE_USER)

4.まとめ

5.最後に

最後に、m/fieldでは未経験者採用を行っています。
私も経験が浅い中、そばに研修指導者がいてくれるので、分からないところはすぐに聞くことができるので、
上記のようなWebアプリケーションを作成することができました。
この記事を読んで、m/fieldに興味を持たれた方はm/field採用までご連絡ください。
心よりお待ちしております。

▶「m/field採用ページ」
https://www.ambl.co.jp/recruit/