Spring Security とは
Spring Security は、 Spring で作られたアプリケーションに認証・認可の機能を提供するフレームワークです。いくつか設定を追加するだけで、以下のような機能をWebアプリケーションに持たせることができます。
- 脆弱性対策
- セッションフィクセイション対策
- クリックジャッキング対策
- CSRF対策
- 認証・認可
- フォーム認証などを用いたログイン機構
- ロールを用いたアクセス制御
前提
今回やること
今回は、Spring Security の機能を使って以下を実現します。
- 認証
- ユーザとパスワードを使ってフォーム認証する
- (データベースでもできるが)インメモリに持たせたユーザとパスワードを使う
- ユーザまたはパスワードが違っていたらエラーメッセージを返す
- ユーザとパスワードを使ってフォーム認証する
- 認可
- 管理者用アカウントと一般ユーザ用でロールを分ける
- 管理者は
/admin
と/user
にアクセス可能 - 一般ユーザは
/user
のみにアクセス可能
- 管理者は
- 管理者用アカウントと一般ユーザ用でロールを分ける
なお、今回は Spring Boot アプリケーションに対してセキュリティを有効にする手順を記載します。
ライブラリのバージョン
Spring Boot は 1.4 系、Spring Securiry は 4.x 系のものを使用します。
バージョンが違う場合、文法がまるで異なるためご注意ください。
サンプルコード
実際に動作するサンプルコードは GitHub で公開しています。コードを読めばわかるよ、という人は直接下記にアクセスしてみてください。
前準備
依存ライブラリの追加
今回はサーバサイドを Spring Boot で構築し、テンプレートエンジンに Tymeleaf を使用して画面描画を行います。そのためには以下のライブラリが必要となります。
spring-boot-starter-web
spring-boot-starter-thymeleaf
また Spring Securirty の機能を有効にするために、以下のライブラリが必要となります。
spring-boot-starter-security
spring-security-web
spring-security-config
今回は Spring Security と Tymeleaf をインテグレーションさせるため、以下のライブラリも追加しましょう。
thymeleaf-extras-springsecurity4
まとめると、 build.gradle
には以下のような設定が追加されることになります。
dependencies {
compile("org.springframework.boot:spring-boot-starter-web:1.4.1.RELEASE")
compile("org.springframework.boot:spring-boot-starter-thymeleaf:1.4.1.RELEASE")
compile("org.springframework.boot:spring-boot-starter-security:1.4.1.RELEASE")
compile("org.thymeleaf.extras:thymeleaf-extras-springsecurity4:2.1.3.RELEASE")
compile("org.springframework.security:spring-security-core:4.2.3.RELEASE")
compile("org.springframework.security:spring-security-web:4.2.3.RELEASE")
compile("org.springframework.security:spring-security-config:4.2.3.RELEASE")
}
Controller
まずは、管理者用の画面と一般ユーザ用の画面を作ります。
それぞれ、管理者用は AdminController
一般ユーザ用は UserController
とします。
@Controller
@RequestMapping("/admin")
@Slf4j
public class AdminController {
@RequestMapping
public String index() {
return "admin";
}
}
@Controller
@RequestMapping("/user")
@Slf4j
public class UserController {
@RequestMapping
public String index() {
return "user";
}
}
Template
上記の Controller に対応する Tymeleaf テンプレートを用意します。
それぞれ、 admin.html
と user.html
とします(詳細はサンプルコード参照)。
あとは、 SpringBoot のお作法に従って Application.java
を作成し、 gradle bootRun
が通れば準備完了です。
実装例
事前準備ができたため、ここから Spring Security の話に移ります。
Config
まずは、 Spring Security を有効にするための設定クラスを作ります。WebSecurityConfigurerAdapter
を継承し、 @EnableWebSecurity
を付与したクラスを用意することで、 Spring Security が有効になります。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static String ROLE_USER = "USER";
private static String ROLE_ADMIN = "ADMIN";
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/user").hasAnyRole(ROLE_USER, ROLE_ADMIN)
.antMatchers("/admin").hasRole(ROLE_ADMIN)
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/user")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.logout()
.permitAll()
.and()
.csrf();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/*.html", "/*.css")
.antMatchers("/bootstrap/**");
}
}
上記の例でやっていることをざっくり解説すると、
-
#configure(HttpSecurity)
- アクセス制御を有効にする
-
/user
は一般ユーザと管理者ユーザのロールでアクセス可能とする -
/admin
は管理者ユーザのみアクセス可能とする
-
- フォームログインを有効にする
- ログインページの URL は
/login
とする- ユーザ名は
username
パスワードはpassword
というプロパティ名とする
- ユーザ名は
- ログインに成功した場合は
/user
に遷移させる - ログインページに対しては、例外的にアクセス制御を無効にする
- ログインページの URL は
- ログアウトページに対しては、例外的にアクセス制御を無効にする
- CSRF 用のトークンを有効にする
- アクセス制御を有効にする
-
#configure(WebSecurity)
- デフォルト設定だと静的ファイルに対してもアクセス制御がかかってしまうため、以下については除外する
- html
- css
- BootStrap テンプレートのファイル
- デフォルト設定だと静的ファイルに対してもアクセス制御がかかってしまうため、以下については除外する
こんな感じ。なんとなく設定内容が想像できる実装になっていると思います。
今回は Tymeleaf と連携するため、SpringTemplateEngine
に TemplateResolver
のインスタンスを設定する必要があります。具体的な実装は以下の通りです。
@Autowired
public TemplateResolver templateResolver;
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.addDialect(new SpringSecurityDialect());
engine.setTemplateResolver(templateResolver);
return engine;
}
加えて、認証に使うユーザ名とパスワードの情報を設定します。今回は実装簡略化のため、データベースからではなく、インメモリで設定しています。 UserDto
についてはサンプルコード参照。
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
UserDto support = supportUser();
UserDto admin = adminUser();
auth.inMemoryAuthentication()
.withUser(support.getUsername()).password(support.getPassword()).roles(ROLE_USER)
.and()
.withUser(admin.getUsername()).password(admin.getPassword()).roles(ROLE_ADMIN);
}
@Bean
@ConfigurationProperties("inmotion.admin")
public UserDto adminUser() {
return new UserDto();
}
@Bean
@ConfigurationProperties("inmotion.user")
public UserDto supportUser() {
return new UserDto();
}
実際のユーザ名、パスワードは application.yml
に外出ししておくことにします。
inmotion:
user:
username: user
password: pass12345
admin:
username: admin
password: pass12345
Controller
あとは、ログイン画面に対応する Controller を用意して、
@Controller
public class AuthenticationController {
@RequestMapping({"/", "/login"})
public String login(@RequestParam(value = "error", required = false) String error, Model model) {
if (error != null) {
// エラーメッセージを出力する処理
}
return "login";
}
}
Template
/login
で POST するためのログインフォームを用意すれば OK です。
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<body>
<div class="container">
<form class="form-signin" method="post" th:action="@{/login}">
<div class="alert alert-dismissible alert-danger" th:if="${errorMessage}">
<button type="button" class="close" data-dismiss="alert">×</button>
<p th:text="${errorMessage}"></p>
</div>
<h2 class="form-signin-heading">Please sign in</h2>
<label for="username" class="sr-only">Username</label>
<input type="text" id="username" name="username" class="form-control" placeholder="UserName"/>
<label for="password" class="sr-only">Password</label>
<input type="password" id="password" name="password" class="form-control" placeholder="Password"/>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
</div>
</body>
</html>
gradle bootRun
でアプリケーションを起動し、
- 指定したユーザ名、パスワードでログインできること
- アクセス制御が有効になっていること
- 認可していない画面にアクセスしようとすると 403 が返ること
- CSRF対策用のトークンが生成されていること
を確認してみましょう。