Spring Securityの登録プロセス

Spring Securityによる登録プロセス

1. 概要

この記事では、SpringSecurityを使用した基本的な登録プロセスを実装します。 これは、ログインを調べたprevious articleで調査された概念に基づいて構築されています。

ここでの目標は、ユーザーがサインアップし、ユーザーデータを検証し、永続化できるようにするa full registration processを追加することです。

参考文献:

Spring MVCおよびSpring Securityによるサーブレット3非同期サポート

Spring MVCでの非同期リクエストに対するSpring Securityサポートの簡単な紹介。

Thymeleafを使用したSpring Security

Spring SecurityとThymeleafを統合するためのクイックガイド

Spring Security-キャッシュコントロールヘッダー

Spring SecurityでHTTPキャッシュコントロールヘッダーを制御するためのガイド。

2. 登録ページ

まず、the following fieldsを表示する簡単な登録ページを実装しましょう。

  • name(姓名)

  • Eメール

  • password(およびパスワード確認フィールド)

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

例2.1



form

Validation error

Validation error

Validation error

Validation error

login

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

すべての登録情報をSpringバックエンドに送信するには、Data Transfer Objectが必要です。 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注釈を使用したことに注意してください。 後で、implement our own custom validation annotationsを使用して、メールアドレスの形式を検証し、パスワードを確認します。 (Section 5)を参照

4. 登録コントローラー

loginページのSign-Upリンクは、ユーザーをregistrationページに移動します。 そのページのこのバックエンドは登録コントローラーにあり、“/user/registration”にマップされます。

例4.1。 –showRegistrationメソッド

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

コントローラはリクエスト“/user/registration”を受信すると、registrationフォームをバックアップする新しいUserDtoオブジェクトを作成し、それをバインドして返します。これは非常に簡単です。

5. 登録データの検証

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

  1. すべての必須フィールドが入力されています(空またはヌルのフィールドはありません)

  2. メールアドレスは有効です(整形式)

  3. パスワード確認フィールドはパスワードフィールドと一致します

  4. アカウントはまだ存在していません

5.1. 組み込みの検証

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

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

public ModelAndView registerUserAccount(
  @ModelAttribute("user") @Valid UserDto accountDto,
  BindingResult result, WebRequest request, Errors errors) {
    ...
}

5.2. 電子メールの有効性を確認するためのカスタム検証

次へ–メールアドレスを検証し、整形式であることを確認しましょう。 そのためのcustom validatorcustom validation annotationを作成します。これを@ValidEmailと呼びましょう。

ここで簡単な補足– Hibernateは古いイントラネットアドレス形式:[email protected]を有効と見なすため(Stackoverflowの記事を参照)、独自のカスタムアノテーションinstead of Hibernate’s@Emailをロールしています。良くないです。

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

例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[] payload() default {};
}

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

Example 5.2.2. – The Custom EmailValidator:

public class EmailValidator
  implements ConstraintValidator {

    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の実装でuse the new annotationを見てみましょう。

@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[] payload() default {};
}

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

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

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

public class PasswordMatchesValidator
  implements ConstraintValidator {

    @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。 –コントローラーのcreateUserAccountメソッドがUserService Objectを呼び出します

@RequestMapping(value = "/user/registration", method = RequestMethod.POST)
public ModelAndView registerUserAccount
      (@ModelAttribute("user") @Valid UserDto accountDto,
      BindingResult result, WebRequest request, Errors errors) {
    User registered = new User();
    if (!result.hasErrors()) {
        registered = createUserAccount(accountDto, result);
    }
    if (registered == null) {
        result.rejectValue("email", "message.regError");
    }
    // rest of the implementation
}
private User createUserAccount(UserDto accountDto, BindingResult result) {
    User registered = null;
    try {
        registered = service.registerNewUserAccount(accountDto);
    } catch (EmailExistsException e) {
        return null;
    }
    return registered;
}

例5.4.2。 –UserService重複する電子メールをチェックします

@Service
public class UserService implements IUserService {
    @Autowired
    private UserRepository repository;

    @Transactional
    @Override
    public User registerNewUserAccount(UserDto accountDto)
      throws EmailExistsException {

        if (emailExist(accountDto.getEmail())) {
            throw new EmailExistsException(
              "There is an account with that email adress: "
              +  accountDto.getEmail());
        }
        ...
        // the rest of the registration operation
    }
    private boolean emailExist(String email) {
        User user = repository.findByEmail(email);
        if (user != null) {
            return true;
        }
        return false;
    }
}

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

現在–永続層でのUserRepositoryの実際の実装は、現在の記事には関係ありません。 もちろん、簡単な方法の1つは、use Spring Data to generate the repository layerです。 __

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

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

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

@RequestMapping(value = "/user/registration", method = RequestMethod.POST)
public ModelAndView registerUserAccount(
  @ModelAttribute("user") @Valid UserDto accountDto,
  BindingResult result,
  WebRequest request,
  Errors errors) {

    User registered = new User();
    if (!result.hasErrors()) {
        registered = createUserAccount(accountDto, result);
    }
    if (registered == null) {
        result.rejectValue("email", "message.regError");
    }
    if (result.hasErrors()) {
        return new ModelAndView("registration", "user", accountDto);
    }
    else {
        return new ModelAndView("successRegister", "user", accountDto);
    }
}
private User createUserAccount(UserDto accountDto, BindingResult result) {
    User registered = null;
    try {
        registered = service.registerNewUserAccount(accountDto);
    } catch (EmailExistsException e) {
        return null;
    }
    return registered;
}

上記のコードで注目すべきこと:

  1. コントローラは、ビューに関連付けられたモデルデータ(user)を送信するための便利なクラスであるModelAndViewオブジェクトを返します。

  2. 検証時にエラーが設定されている場合、コントローラーは登録フォームにリダイレクトします。

  3. createUserAccountメソッドは、データの永続性のためにUserServiceを呼び出します。 次のセクションでは、UserServiceの実装について説明します。

7. UserService - レジスタ操作

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

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

public interface IUserService {
    User registerNewUserAccount(UserDto accountDto)
      throws EmailExistsException;
}

例7.2。 –UserServiceクラス

@Service
public class UserService implements IUserService {
    @Autowired
    private UserRepository repository;

    @Transactional
    @Override
    public User registerNewUserAccount(UserDto accountDto)
      throws EmailExistsException {

        if (emailExists(accountDto.getEmail())) {
            throw new EmailExistsException(
              "There is an account with that email address:  + accountDto.getEmail());
        }
        User user = new User();
        user.setFirstName(accountDto.getFirstName());
        user.setLastName(accountDto.getLastName());
        user.setPassword(accountDto.getPassword());
        user.setEmail(accountDto.getEmail());
        user.setRoles(Arrays.asList("ROLE_USER"));
        return repository.save(user);
    }
    private boolean emailExists(String email) {
        User user = repository.findByEmail(email);
        if (user != null) {
            return true;
        }
        return false;
    }
}

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

previous articleでは、ログインはハードコードされた資格情報を使用していました。 それとuse the newly registered user informationと資格情報を変更しましょう。 カスタムUserDetailsServiceを実装して、永続層からログインするための資格情報を確認します。

8.1. カスタム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 getAuthorities (List roles) {
        List authorities = new ArrayList<>();
        for (String role : roles) {
            authorities.add(new SimpleGrantedAuthority(role));
        }
        return authorities;
    }
}

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

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

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


    


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

@Autowired
private MyUserDetailsService userDetailsService;

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

9. 結論

これで完了です。SpringSecurityとSpringMVCで完全かつほぼproduction ready registration processが実装されています。 次に、新しいユーザーのメールアドレスを確認して、新しく登録したアカウントをアクティブ化するプロセスについて説明します。

このSpringSecurity RESTチュートリアルの実装はthe GitHub projectにあります。これはEclipseベースのプロジェクトであるため、そのままインポートして実行するのは簡単です。