ホームページ  >  記事  >  Java  >  Spring Boot と Thymeleaf を使用したフォーム ログイン

Spring Boot と Thymeleaf を使用したフォーム ログイン

WBOY
WBOYオリジナル
2024-07-25 19:39:43542ブラウズ

フォームベースのログインは、Spring Boot アプリケーションの Web フロントエンドを保護するための最初の選択肢であることがよくあります。これにより、ユーザーがユーザー名とパスワードで認証された場合にのみアプリケーションの特定の領域にアクセスできるようになり、このステータスがセッションに保存されます。 Spring Boot アプリケーションにフォームベースのログインを追加するにはどのような手順が必要ですか?

簡単なアプリケーションから始める

まず、Bootify Builder を使用して、現在のバージョン 3.3.2 で単純な Spring Boot アプリケーションを作成します。これを行うには、[プロジェクトを開く] をクリックするだけです。そこで、フロントエンド スタックとして Thymeleaf + Bootstrap を選択します。Thymeleaf は Spring Boot で最も使用されているテンプレート エンジンであり、サーバー側のレンダリングを可能にします。 Bootstrap は WebJar としてアプリに統合されます。接続したいデータベースを選択します。今のところは組み込みデータベースでも問題ありません。

エンティティ タブ で、User テーブルと TodoList テーブルを作成し、それらを N:1 関係で接続します。 TodoList の場合、フロントエンドの CRUD オプションをアクティブにします - これは、後で Spring Security で保護される領域になります。

Form Login with Spring Boot and Thymeleaf
非常に単純なデータベース スキーマのプレビュー

これで、完成したアプリケーションをダウンロードして、お気に入りの IDE にインポートできるようになりました。

Form Login with Spring Boot and Thymeleaf
IntelliJ でのアプリケーションの最初のバージョン

Spring Securityの構成

フォームベースのログインは Spring Security の助けを借りて提供されます。したがって、最初に関連する依存関係が必要になり、それぞれ build.gradle または pom.xml に追加します。

implementation('org.springframework.boot:spring-boot-starter-security')
implementation('org.thymeleaf.extras:thymeleaf-extras-springsecurity6')

モジュール spring-boot-starter-security は Spring Security を統合します。 thymeleaf-extras-springsecurity6 には、Thymeleaf テンプレートに認証状態を提供する小さなヘルパーが含まれています。これについては後ほど説明します。

これにより、すでに中央のセキュリティ構成を提供できます - ここでは最終バージョンで直接提供します。

@Configuration
@EnableMethodSecurity(prePostEnabled = true)
public class HttpSecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(
            final AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain configure(final HttpSecurity http) throws Exception {
        return http.cors(withDefaults())
                .csrf(withDefaults())
                .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll())
                .formLogin(form -> form
                    .loginPage("/login")
                    .usernameParameter("email")
                    .failureUrl("/login?loginError=true"))
                .logout(logout -> logout
                    .logoutSuccessUrl("/login?logoutSuccess=true")
                    .deleteCookies("JSESSIONID"))
                .exceptionHandling(exception -> exception
                    .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login?loginRequired=true")))
                .build();
    }

}

フォームログインの設定

理論的には、Spring Security はより少ない構成でログイン フォームを提供できますが、これではユーザーに表示したいデザインといくつかのメッセージが不足します。ただし、最初に構成を見てみましょう。

BCryptPasswordEncoder は現在、パスワードのハッシュを個々のソルトとともに単一フィールド内に保存するのが標準です。従来の要件がない場合は、これを使用する必要があります。また、他のサービスに統合できるように、AuthenticationManager を Bean として提供します。

Spring 3.0 以降では必須のアプローチであるため、3 番目の Bean として SecurityFilterChain を作成します。 CORS と CSRF の両方を適切に設定して、対応する攻撃ベクトルを遮断する必要があります。通常、これにはデフォルトの構成で十分です。

設定クラスに @EnableMethodSecurity アノテーションを配置し、後で @PreAuthorize(...) を使用して目的のコントローラー エンドポイントを保護します。したがって、permitAll() を使用してアプリケーション全体へのアクセスを許可します。注釈ベースのセキュリティを使用しない場合は、保護されたリソースへのパスもこの場所で構成する必要があります。

formLogin() メソッドと logout() メソッドは、常にユーザーに適切なメッセージを表示できるように、後続のコントローラー用にカスタマイズされています。 Spring Security は、ユーザー名とパスワードを POST リクエスト経由で送信できるログイン用のエンドポイントを自動的に提供します。ここでは、ユーザー名フィールドの名前を「email」に変更します。ログアウトは、その後パラメータを使用してログイン ページにリダイレクトされるように変更されます。

データベースからユーザーをロードしています

既に作成されたテーブルからユーザーをロードするには、UserDetailsS​​ervice の実装を Bean として提供する必要があります。これは 自動的に検出され、ユーザー ソースとして使用されます

@Service
public class HttpUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    public HttpUserDetailsService(final UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public HttpUserDetails loadUserByUsername(final String username) {
        final User user = userRepository.findByEmailIgnoreCase(username);
        if (user == null) {
            log.warn("user not found: {}", username);
            throw new UsernameNotFoundException("User " + username + " not found");
        }
        final List<SimpleGrantedAuthority> roles = Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
        return new HttpUserDetails(user.getId(), username, user.getHash(), roles);
    }

}

ユーザー ソースとして自動的に使用される UserDetailsS​​ervice 実装

私たちのリポジトリには、データベースに対して検索クエリを実行するメソッド User findByEmailIgnoreCase(String email) を追加する必要があります。大文字/小文字を無視すると、ユーザーが電子メールを書くときに小さな間違いが発生する可能性があります。ここでのロールは、各ユーザーに対して常に ROLE_USER です。この時点では利用可能な登録エンドポイントがないため、今のところアプリケーションとともに単純なデータ ローダーを追加できます。アクティブにするには、プロファイル「ローカル」が必要です。

@Component
@Profile("local")
public class UserLoader implements ApplicationRunner {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;

    public UserLoader(final UserRepository userRepository,
                      final PasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public void run(final ApplicationArguments args) {
        if (userRepository.count() != 0) {
            return;
        }
        final User user = new User();
        user.setEmail("test@test.com");
        user.setHash(passwordEncoder.encode("testtest"));
        userRepository.save(user);
    }

}

最初のユーザーをローカルで初期化するヘルパー クラス

Add login controller

With this we can already add the LoginController. Since the POST endpoint is automatically provided by Spring Security, a GET endpoint is sufficient here to show the template to the user.

@Controller
public class AuthenticationController {

    @GetMapping("/login")
    public String login(@RequestParam(name = "loginRequired", required = false) final Boolean loginRequired,
            @RequestParam(name = "loginError", required = false) final Boolean loginError,
            @RequestParam(name = "logoutSuccess", required = false) final Boolean logoutSuccess,
            final Model model) {
        model.addAttribute("authentication", new AuthenticationRequest());
        if (loginRequired == Boolean.TRUE) {
            model.addAttribute(WebUtils.MSG_INFO, WebUtils.getMessage("authentication.login.required"));
        }
        if (loginError == Boolean.TRUE) {
            model.addAttribute(WebUtils.MSG_ERROR, WebUtils.getMessage("authentication.login.error"));
        }
        if (logoutSuccess == Boolean.TRUE) {
            model.addAttribute(WebUtils.MSG_INFO, WebUtils.getMessage("authentication.logout.success"));
        }
        return "authentication/login";
    }

}

  Backend for rendering the login page

The request parameters that we had already specified in our security configuration are converted to corresponding messages here. In our simple application from Bootify the corresponding helpers are already included. Here we also need the AuthenticationRequest object with getters and setters.

public class AuthenticationRequest {

    @NotNull
    @Size(max = 255)
    private String email;

    @NotNull
    @Size(max = 255)
    private String password;

}

The corresponding template for our controller could then look like this.

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
        layout:decorate="~{layout}">
    <head>
        <title>[[#{authentication.login.headline}]]</title>
    </head>
    <body>
        <div layout:fragment="content">
            <h1 class="mb-4">[[#{authentication.login.headline}]]</h1>
            <div th:replace="~{fragments/forms::globalErrors('authentication')}" />
            <form th:action="${requestUri}" method="post">
                <div th:replace="~{fragments/forms::inputRow(object='authentication', field='email')}" />
                <div th:replace="~{fragments/forms::inputRow(object='authentication', field='password', type='password')}" />
                <input type="submit" th:value="#{authentication.login.headline}" class="btn btn-primary mt-4" />
            </form>
        </div>
    </body>
</html>

As Thymeleaf doesn't allow direct access to request object anymore, we're providing the requestUri in the model.

@ModelAttribute("requestUri")
String getRequestServletPath(final HttpServletRequest request) {
    return request.getRequestURI();
}

_  Providing the requestUri - as part of the AuthenticationController or a general ControllerAdvice _

With this template we send a POST request to the /login endpoint. The INFO or ERROR messages are automatically displayed by the layout. All used messages have to be present in our messages.properties.

authentication.login.headline=Login
authentication.email.label=Email
authentication.password.label=Password
authentication.login.required=Please login to access this area.
authentication.login.error=Your login was not successful - please try again.
authentication.logout.success=Your logout was successful.
navigation.login=Login
navigation.logout=Logout

Last we can extend our layout.html. With this we also always show a login / logout link in the header. Spring Security also automatically provides a /logout endpoint, but we have to address it via POST.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
            xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
    <!-- ... -->    
    <a sec:authorize="!isAuthenticated()" th:href="@{/login}" class="nav-link">[[#{navigation.login}]]</a>
    <form sec:authorize="isAuthenticated()" th:action="@{/logout}" method="post" class="nav-link">
        <input th:value="#{navigation.logout}" type="submit" class="unset" />
    </form>
    <!-- ... -->
</html>

  Adding login / logout links to our layout

In the html tag we've extended the namespace to use the helpers from the thymeleaf-extras-springsecurity6 module. As a final step we only need to add the annotation @PreAuthorize("hasAuthority('ROLE_USER')") at our TodoListController.

Starting our application

With this we have all needed pieces of our puzzle together! Now we start our application and when we want to see the todo lists, we should be redirected to the login page. Here we can log in with test@test.com / testtest.

Form Login with Spring Boot and Thymeleaf
  Automatic redirect to the login

In the Free plan of Bootify, Spring Boot prototypes with its own database schema, REST API and frontend can be generated. In the Professional plan, among other things, Spring Security with the form-based login is available to generate the setup described here - exactly matching the created database and the selected settings. A registration endpoint and role source can be specified as well.

» See Features and Pricing

以上がSpring Boot と Thymeleaf を使用したフォーム ログインの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。