1. 概要

この記事では、Springセキュリティを使用した基本的な登録プロセスを実装します。 これは、ログインについて説明した前の記事で検討した概念に基づいて構築されています。

ここでの目標は、完全な登録プロセスを追加して、ユーザーがユーザーデータにサインアップ、検証、および永続化できるようにすることです。

2. 登録ページ

まず、次のフィールドを表示する簡単な登録ページを実装しましょう。

  • 名前(姓名)
  • Eメール
  • パスワード(およびパスワード確認フィールド)

次の例は、単純なregisterration.htmlページを示しています。

例2.1。

<html>
<body>
<h1 th:text="#{label.form.title}">form</h1>
<form action="/" th:object="${user}" method="POST" enctype="utf8">
    <div>
        <label th:text="#{label.user.firstName}">first</label>
        <input th:field="*{firstName}"/>
        <p th:each="error: ${#fields.errors('firstName')}" 
          th:text="${error}">Validation error</p>
    </div>
    <div>
        <label th:text="#{label.user.lastName}">last</label>
        <input th:field="*{lastName}"/>
        <p th:each="error : ${#fields.errors('lastName')}" 
          th:text="${error}">Validation error</p>
    </div>
    <div>
        <label th:text="#{label.user.email}">email</label>
        <input type="email" th:field="*{email}"/>
        <p th:each="error : ${#fields.errors('email')}" 
          th:text="${error}">Validation error</p>
    </div>
    <div>
        <label th:text="#{label.user.password}">password</label>
        <input type="password" th:field="*{password}"/>
        <p th:each="error : ${#fields.errors('password')}" 
          th:text="${error}">Validation error</p>
    </div>
    <div>
        <label th:text="#{label.user.confirmPass}">confirm</label>
        <input type="password" th:field="*{matchingPassword}"/>
    </div>
    <button type="submit" th:text="#{label.form.submit}">submit</button>
</form>

<a th:href="@{/login.html}" th:text="#{label.form.loginLink}">login</a>
</body>
</html>

3. ユーザーDTOオブジェクト

すべての登録情報をSpringバックエンドに送信するには、データ転送オブジェクトが必要です。 DTO オブジェクトには、後でUserオブジェクトを作成してデータを設定するときに必要なすべての情報が含まれている必要があります。

public class UserDto {
    @NotNull
    @NotEmpty
    private String firstName;
    
    @NotNull
    @NotEmpty
    private String lastName;
    
    @NotNull
    @NotEmpty
    private String password;
    private String matchingPassword;
    
    @NotNull
    @NotEmpty
    private String email;
    
    // standard getters and setters
}

DTOオブジェクトのフィールドに標準のjavax.validationアノテーションを使用していることに注意してください。 後で、独自のカスタム検証アノテーションを実装して、電子メールアドレスの形式とパスワードの確認を検証します。 (セクション5を参照)

4. 登録コントローラー

ログインページのサインアップリンクをクリックすると、ユーザーは登録ページに移動します。 そのページのこのバックエンドは登録コントローラーにあり、「/ user/registration」にマップされます。

例4.1。 – showRegistrationメソッド

@GetMapping("/user/registration")
public String showRegistrationForm(WebRequest request, Model model) {
    UserDto userDto = new UserDto();
    model.addAttribute("user", userDto);
    return "registration";
}

コントローラがリクエスト“ / user / registering” を受信すると、 registermentフォームをバックアップする新しいUserDtoオブジェクトを作成し、バインドして、–を返します。かなり簡単です。

5. 登録データの検証

次へ–新しいアカウントを登録するときにコントローラーが実行する検証を見てみましょう。

  1. すべての必須フィールドが入力されます(空のフィールドまたはnullフィールドはありません)
  2. メールアドレスは有効です(整形式)
  3. パスワード確認フィールドはパスワードフィールドと一致します
  4. アカウントはまだ存在していません

5.1. 組み込みの検証

簡単なチェックでは、DTOオブジェクトですぐに使用できるbean検証アノテーション( @NotNull @NotEmptyなどのアノテーション)を使用します。

検証プロセスをトリガーするには、コントローラーレイヤーのオブジェクトに@Validアノテーションを付けるだけです。

public ModelAndView registerUserAccount(@ModelAttribute("user") @Valid UserDto userDto,
  HttpServletRequest request, Errors errors) {
    //...
}

5.2. 電子メールの有効性をチェックするためのカスタム検証

次へ–メールアドレスを検証し、整形式であることを確認しましょう。 そのためのカスタムバリデーターと、カスタム検証アノテーションを作成します。これを@ValidEmailと呼びましょう。

ここで簡単に説明します。Hibernateは古いイントラネットアドレス形式myaddress@ myserver を次のように見なすため、Hibernateの @Emailではなく独自のカスタムアノテーションを使用しています。有効( Stackoverflow の記事を参照)。これは良くありません。

メール検証アノテーションとカスタムバリデーターは次のとおりです。

例5.2.1。 –電子メール検証用のカスタム注釈

@Target({TYPE, FIELD, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = EmailValidator.class)
@Documented
public @interface ValidEmail {
    String message() default "Invalid email";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

注釈はFIELDレベルで定義されていることに注意してください。これは、概念的に適用される場所だからです。

例5.2.2。 –カスタムEmailValidato r:

public class EmailValidator 
  implements ConstraintValidator<ValidEmail, String> {
    
    private Pattern pattern;
    private Matcher matcher;
    private static final String EMAIL_PATTERN = "^[_A-Za-z0-9-+]+
        (.[_A-Za-z0-9-]+)*@" + "[A-Za-z0-9-]+(.[A-Za-z0-9]+)*
        (.[A-Za-z]{2,})$"; 
    @Override
    public void initialize(ValidEmail constraintAnnotation) {
    }
    @Override
    public boolean isValid(String email, ConstraintValidatorContext context){
        return (validateEmail(email));
    } 
    private boolean validateEmail(String email) {
        pattern = Pattern.compile(EMAIL_PATTERN);
        matcher = pattern.matcher(email);
        return matcher.matches();
    }
}

ここで、UserDto実装で新しいアノテーションを使用しましょう。

@ValidEmail
@NotNull
@NotEmpty
private String email;

5.3. パスワード確認のためのカスタム検証の使用

passwordフィールドとmatchingPasswordフィールドが一致することを確認するためのカスタムアノテーションとバリデーターも必要です。

例5.3.1。 –パスワード確認を検証するためのカスタム注釈

@Target({TYPE,ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = PasswordMatchesValidator.class)
@Documented
public @interface PasswordMatches {
    String message() default "Passwords don't match";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

@Target アノテーションは、これがTYPEレベルのアノテーションであることを示していることに注意してください。 これは、検証を実行するためにUserDtoオブジェクト全体が必要なためです。

このアノテーションによって呼び出されるカスタムバリデーターを以下に示します。

例5.3.2。 PasswordMatchesValidatorカスタムバリデーター

public class PasswordMatchesValidator
  implements ConstraintValidator<PasswordMatches, Object> {
    
    @Override
    public void initialize(PasswordMatches constraintAnnotation) {
    }
    @Override
    public boolean isValid(Object obj, ConstraintValidatorContext context){
        UserDto user = (UserDto) obj;
        return user.getPassword().equals(user.getMatchingPassword());
    }
}

ここで、@PasswordMatchesアノテーションをUserDtoオブジェクトに適用する必要があります。

@PasswordMatches
public class UserDto {
    //...
}

もちろん、すべてのカスタム検証は、検証プロセス全体が実行されるときに、すべての標準アノテーションとともに評価されます。

5.4. アカウントがまだ存在していないことを確認します

実装する4番目のチェックは、emailアカウントがデータベースにまだ存在していないことを確認することです。

これは、フォームが検証され、UserService実装を使用して実行された後に実行されます。

例5.4.1。 –コントローラーのregisterUserAccountメソッドがUserServiceオブジェクトを呼び出します

@PostMapping("/user/registration")
public ModelAndView registerUserAccount(
  @ModelAttribute("user") @Valid UserDto userDto,
  HttpServletRequest request,
  Errors errors) {
    
    try {
        User registered = userService.registerNewUserAccount(userDto);
    } catch (UserAlreadyExistException uaeEx) {
        mav.addObject("message", "An account for that username/email already exists.");
        return mav;
    }

    // rest of the implementation
}

例5.4.2。 –ユーザーサービス重複する電子メールをチェックします

@Service
@Transactional
public class UserService implements IUserService {
    @Autowired
    private UserRepository repository;
    
    @Override
    public User registerNewUserAccount(UserDto userDto) throws UserAlreadyExistException {
        if (emailExist(userDto.getEmail())) {
            throw new UserAlreadyExistException("There is an account with that email address: "
              + userDto.getEmail());
        }

        // the rest of the registration operation
    }
    private boolean emailExist(String email) {
        return userRepository.findByEmail(email) != null;
    }
}

UserServiceは、 UserRepository クラスに依存して、特定の電子メールアドレスを持つユーザーがデータベースにすでに存在するかどうかを確認します。

現在–永続層での UserRepository の実際の実装は、現在の記事には関係ありません。 もちろん、簡単な方法の1つは、SpringDataを使用してリポジトリレイヤーを生成することです。

6. データの永続化とフォーム処理の仕上げ

最後に、コントローラーレイヤーに登録ロジックを実装しましょう。

例6.1.1。 –コントローラーのRegisterAccountメソッド

@PostMapping("/user/registration")
public ModelAndView registerUserAccount(
  @ModelAttribute("user") @Valid UserDto userDto,
  HttpServletRequest request,
  Errors errors) {
    
    try {
        User registered = userService.registerNewUserAccount(userDto);
    } catch (UserAlreadyExistException uaeEx) {
        mav.addObject("message", "An account for that username/email already exists.");
        return mav;
    }

    return new ModelAndView("successRegister", "user", userDto);
}

上記のコードで注意すべき点:

  1. コントローラは、ビューに関連付けられたモデルデータ( user )を送信するための便利なクラスであるModelAndViewオブジェクトを返しています。
  2. 検証時にエラーが設定された場合、コントローラーは登録フォームにリダイレクトします。

7. UserService –登録操作

UserServiceでの登録操作の実装を終了しましょう。

例7.1。 IUserServiceインターフェイス

public interface IUserService {
    User registerNewUserAccount(UserDto userDto);
}

例7.2。 – UserServiceクラス

@Service
@Transactional
public class UserService implements IUserService {
    @Autowired
    private UserRepository repository;
    
    @Override
    public User registerNewUserAccount(UserDto userDto) throws UserAlreadyExistException {
        if (emailExists(userDto.getEmail())) {
            throw new UserAlreadyExistException("There is an account with that email address: "
              + userDto.getEmail());
        }

        User user = new User();
        user.setFirstName(userDto.getFirstName());
        user.setLastName(userDto.getLastName());
        user.setPassword(userDto.getPassword());
        user.setEmail(userDto.getEmail());
        user.setRoles(Arrays.asList("ROLE_USER"));

        return repository.save(user);
    }

    private boolean emailExists(String email) {
        return userRepository.findByEmail(email) != null;
    }
}

8. セキュリティログイン用のユーザー詳細の読み込み

以前の記事では、ログインはハードコードされた資格情報を使用していました。 それを変更して、新しく登録したユーザー情報とクレデンシャルを使用しましょう。 カスタムUserDetailsServiceを実装して、永続層からのログインの資格情報を確認します。

8.1. Custom UserDetailsService

カスタムユーザー詳細サービスの実装から始めましょう。

@Service
@Transactional
public class MyUserDetailsService implements UserDetailsService {
 
    @Autowired
    private UserRepository userRepository;
    
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        User user = userRepository.findByEmail(email);
        if (user == null) {
            throw new UsernameNotFoundException("No user found with username: " + email);
        }
        boolean enabled = true;
        boolean accountNonExpired = true;
        boolean credentialsNonExpired = true;
        boolean accountNonLocked = true;
        
        return new org.springframework.security.core.userdetails.User(
          user.getEmail(), user.getPassword().toLowerCase(), enabled, accountNonExpired,
          credentialsNonExpired, accountNonLocked, getAuthorities(user.getRoles()));
    }
    
    private static List<GrantedAuthority> getAuthorities (List<String> roles) {
        List<GrantedAuthority> authorities = new ArrayList<>();
        for (String role : roles) {
            authorities.add(new SimpleGrantedAuthority(role));
        }
        return authorities;
    }
}

8.2. 新しい認証プロバイダーを有効にする

Spring Security構成で新しいユーザーサービスを有効にするには、authentication-manager要素内にUserDetailsServiceへの参照を追加し、UserDetailsServiceを追加するだけです。豆:

例8.2.-認証マネージャーとUserDetailsService

<authentication-manager>
    <authentication-provider user-service-ref="userDetailsService" />
</authentication-manager>
 
<beans:bean id="userDetailsService" class="com.baeldung.security.MyUserDetailsService" />

または、Java構成を介して:

@Autowired
private MyUserDetailsService userDetailsService;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService);
}

9. 結論

これで完了です。SpringセキュリティとSpringMVCで実装された、ほぼ本番環境に対応した完全な登録プロセスです。 次に、新しいユーザーの電子メールを確認して、新しく登録されたアカウントをアクティブ化するプロセスについて説明します。

このSpringSecurityRESTチュートリアルの実装は、GitHubにあります。