Help us understand the problem. What is going on with this article?

Spring Security&Thymeleafで認証・認可

More than 1 year has passed since last update.

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.htmluser.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 に遷移させる
      • ログインページに対しては、例外的にアクセス制御を無効にする
    • ログアウトページに対しては、例外的にアクセス制御を無効にする
    • CSRF 用のトークンを有効にする
  • #configure(WebSecurity)
    • デフォルト設定だと静的ファイルに対してもアクセス制御がかかってしまうため、以下については除外する
      • html
      • css
      • BootStrap テンプレートのファイル

こんな感じ。なんとなく設定内容が想像できる実装になっていると思います。

今回は Tymeleaf と連携するため、SpringTemplateEngineTemplateResolver のインスタンスを設定する必要があります。具体的な実装は以下の通りです。

@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対策用のトークンが生成されていること

を確認してみましょう。

参考資料

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした