登録 - メールで新しいアカウントを有効にする

1概要

この記事はリンクを続けています:/spring-security-registration[進行中の Spring Security シリーズへの登録 ]登録プロセスの欠けている部分の1つ - ユーザーの電子メールを確認して自分のアカウントを確認 **

登録確認メカニズムにより、ユーザーは登録成功後に送信された「 Confirm Registration 」電子メールに返信して、自分の電子メールアドレスを確認し、アカウントをアクティブにします。これを行うには、電子メールで送信された固有のアクティベーションリンクをクリックします。

このロジックに従って、新しく登録されたユーザーはこのプロセスが完了するまでシステムにログインできません。

** 2検証トークン

**

ユーザーを検証するための重要な成果物として単純な検証トークンを使用します。

** 2.1. VerificationToken エンティティ+

**

VerificationToken エンティティは次の基準を満たす必要があります。

  1. それは User にリンクバックしなければなりません(単方向関係を通して)

  2. 登録直後に作成されます

  3. 作成後 24時間以内に期限が切れます

  4. ユニークな、ランダムに生成された 値を持ちます

要件2と3は登録ロジックの一部です。他の2つは、例2.1のような単純な VerificationToken エンティティに実装されています。

例2.1。**

@Entity
public class VerificationToken {
    private static final int EXPIRATION = 60 **  24;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String token;

    @OneToOne(targetEntity = User.class, fetch = FetchType.EAGER)
    @JoinColumn(nullable = false, name = "user__id")
    private User user;

    private Date expiryDate;

    private Date calculateExpiryDate(int expiryTimeInMinutes) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(new Timestamp(cal.getTime().getTime()));
        cal.add(Calendar.MINUTE, expiryTimeInMinutes);
        return new Date(cal.getTime().getTime());
    }

   //standard constructors, getters and setters
}

VerificationToken < User アソシエーションでデータの整合性と一貫性を確保するために、Userの nullable = false に注意してください。

2.2. enabled フィールドを User に追加します

最初に User が登録されると、この enabled フィールドは false に設定されます。アカウントの確認プロセス中に - 成功した場合 - それは true になります。

User エンティティにフィールドを追加することから始めましょう。

public class User {
    ...
    @Column(name = "enabled")
    private boolean enabled;

    public User() {
        super();
        this.enabled=false;
    }
    ...
}

このフィールドのデフォルト値も false に設定していることに注意してください。

** 3アカウント登録中

ユーザー登録のユースケースにさらに2つのビジネスロジックを追加しましょう。

  1. ユーザー用の VerificationToken を生成し、それを保持します

  2. アカウント確認用の電子メールメッセージを送信する

VerificationToken’s 値を持つ確認リンク

3.1. Springイベントを使用してトークンを作成し、確認メールを送信する

これら2つの追加ロジックは、「付随的な」バックエンドタスクであるため、コントローラによって直接実行しないでください。

コントローラーはこれらのタスクの実行をトリガーするためにSpring ApplicationEvent を発行します。これは、 ApplicationEventPublisher を注入してから、それを使用して登録完了を公開するのと同じくらい簡単です。

実施例3.1。この簡単なロジックを示します。

例3.1。**

@Autowired
ApplicationEventPublisher eventPublisher

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

    if (result.hasErrors()) {
        return new ModelAndView("registration", "user", accountDto);
    }

    User registered = createUserAccount(accountDto);
    if (registered == null) {
        result.rejectValue("email", "message.regError");
    }
    try {
        String appUrl = request.getContextPath();
        eventPublisher.publishEvent(new OnRegistrationCompleteEvent
          (registered, request.getLocale(), appUrl));
    } catch (Exception me) {
        return new ModelAndView("emailError", "user", accountDto);
    }
    return new ModelAndView("successRegister", "user", accountDto);
}

もう1つ注意すべきことは、イベントの発行を囲む try catch ブロックです。このコードでは、イベントの発行後に実行されたロジックに例外がある場合は常にエラーページが表示されます。この場合、電子メールの送信です。

3.2. イベントとリスナー

それでは、コントローラが送信しているこの新しい OnRegistrationCompleteEvent の実際の実装と、それを処理するリスナを見てみましょう。

  • 例3.2.1。** - OnRegistrationCompleteEvent

public class OnRegistrationCompleteEvent extends ApplicationEvent {
    private String appUrl;
    private Locale locale;
    private User user;

    public OnRegistrationCompleteEvent(
      User user, Locale locale, String appUrl) {
        super(user);

        this.user = user;
        this.locale = locale;
        this.appUrl = appUrl;
    }

   //standard getters and setters
}
  • 例3.2.2。 - The RegistrationListener ** OnRegistrationCompleteEvent を処理する

@Component
public class RegistrationListener implements
  ApplicationListener<OnRegistrationCompleteEvent> {

    @Autowired
    private IUserService service;

    @Autowired
    private MessageSource messages;

    @Autowired
    private JavaMailSender mailSender;

    @Override
    public void onApplicationEvent(OnRegistrationCompleteEvent event) {
        this.confirmRegistration(event);
    }

    private void confirmRegistration(OnRegistrationCompleteEvent event) {
        User user = event.getUser();
        String token = UUID.randomUUID().toString();
        service.createVerificationToken(user, token);

        String recipientAddress = user.getEmail();
        String subject = "Registration Confirmation";
        String confirmationUrl
          = event.getAppUrl() + "/regitrationConfirm.html?token=" + token;
        String message = messages.getMessage("message.regSucc", null, event.getLocale());

        SimpleMailMessage email = new SimpleMailMessage();
        email.setTo(recipientAddress);
        email.setSubject(subject);
        email.setText(message + " rn" + "http://localhost:8080" + confirmationUrl);
        mailSender.send(email);
    }
}

ここで、 confirmRegistration メソッドは、 OnRegistrationCompleteEvent を受け取り、そこから必要なすべての User 情報を抽出して検証トークンを作成し、それを保持してから、それを「 Confirm Registration 」リンクのパラメーターとして送信します。

上記のように、 JavaMailSender によってスローされた javax.mail.AuthenticationFailedException は、コントローラによって処理されます。

3.3. 検証トークンパラメータの処理

ユーザが「 登録確認 」リンクを受け取ったら、クリックしてください。

それらがいったん - コントローラは結果として得られるGETリクエストの中のtokenパラメータの値を抽出し、それを使用して User を有効にします。

例3.3.1でこのプロセスを見てみましょう。

  • 例3.3.1 - RegistrationController 登録確認の処理**

@Autowired
private IUserService service;

@RequestMapping(value = "/regitrationConfirm", method = RequestMethod.GET)
public String confirmRegistration
  (WebRequest request, Model model, @RequestParam("token") String token) {

    Locale locale = request.getLocale();

    VerificationToken verificationToken = service.getVerificationToken(token);
    if (verificationToken == null) {
        String message = messages.getMessage("auth.message.invalidToken", null, locale);
        model.addAttribute("message", message);
        return "redirect:/badUser.html?lang=" + locale.getLanguage();
    }

    User user = verificationToken.getUser();
    Calendar cal = Calendar.getInstance();
    if ((verificationToken.getExpiryDate().getTime() - cal.getTime().getTime()) <= 0) {
        String messageValue = messages.getMessage("auth.message.expired", null, locale)
        model.addAttribute("message", messageValue);
        return "redirect:/badUser.html?lang=" + locale.getLanguage();
    }

    user.setEnabled(true);
    service.saveRegisteredUser(user);
    return "redirect:/login.html?lang=" + request.getLocale().getLanguage();
}

次の場合、ユーザーは対応するメッセージを含むエラーページにリダイレクトされます。

  1. VerificationToken が存在しません. 何らかの理由で

  2. VerificationToken は期限切れです

    • エラーページを見るには例3.3.2。** を参照してください。

    • 例3.3.2 - badUser.html **

<html>
<body>
    <h1 th:text="${param.message[0]}>Error Message</h1>
    <a th:href="@{/registration.html}"
      th:text="#{label.form.loginSignUp}">signup</a>
</body>
</html>

エラーが見つからない場合、ユーザーは有効になっています。

VerificationToken チェックおよび有効期限シナリオの処理には、2つの改善の機会があります。

  1. Cron Jobを使用して でトークンの有効期限を確認することができます.

バックグラウンド 。新しいトークンを取得する機会をユーザに与えることができます。

期限切れ

今後の記事のために新しいトークンの生成を延期し、ここでユーザーが実際に自分のトークンを正しく検証したと想定します。

4ログインプロセスへのアカウントアクティベーションチェックの追加

ユーザーが有効かどうかを確認するコードを追加する必要があります。

例4.1でこれを見てみましょう。これは、 MyUserDetailsS​​ervice loadUserByUsername メソッドを示しています。

例4.1。**

@Autowired
UserRepository userRepository;

public UserDetails loadUserByUsername(String email)
  throws UsernameNotFoundException {

    boolean enabled = true;
    boolean accountNonExpired = true;
    boolean credentialsNonExpired = true;
    boolean accountNonLocked = true;
    try {
        User user = userRepository.findByEmail(email);
        if (user == null) {
            throw new UsernameNotFoundException(
              "No user found with username: " + email);
        }

        return new org.springframework.security.core.userdetails.User(
          user.getEmail(),
          user.getPassword().toLowerCase(),
          user.isEnabled(),
          accountNonExpired,
          credentialsNonExpired,
          accountNonLocked,
          getAuthorities(user.getRole()));
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

ご覧のとおり、 MyUserDetailsS​​ervice はユーザーの enabled フラグを使用していません - そのため、ユーザーの認証を有効にすることしかできません。

それでは、 MyUserDetailsS​​ervice からの例外メッセージをカスタマイズするために AuthenticationFailureHandler を追加します。 CustomAuthenticationFailureHandler を例4.2に示します。

例4.2 - CustomAuthenticationFailureHandler :**

@Component
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    @Autowired
    private MessageSource messages;

    @Autowired
    private LocaleResolver localeResolver;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request,
      HttpServletResponse response, AuthenticationException exception)
      throws IOException, ServletException {
        setDefaultFailureUrl("/login.html?error=true");

        super.onAuthenticationFailure(request, response, exception);

        Locale locale = localeResolver.resolveLocale(request);

        String errorMessage = messages.getMessage("message.badCredentials", null, locale);

        if (exception.getMessage().equalsIgnoreCase("User is disabled")) {
            errorMessage = messages.getMessage("auth.message.disabled", null, locale);
        } else if (exception.getMessage().equalsIgnoreCase("User account has expired")) {
            errorMessage = messages.getMessage("auth.message.expired", null, locale);
        }

        request.getSession().setAttribute(WebAttributes.AUTHENTICATION__EXCEPTION, errorMessage);
    }
}

エラーメッセージを表示するために login.html を修正する必要があります。

例4.3 - login.html にエラーメッセージを表示する:**

<div th:if="${param.error != null}"
  th:text="${session[SPRING__SECURITY__LAST__EXCEPTION]}">error</div>

5持続層の適応

検証トークンとユーザーを含むこれらの操作のいくつかの実際の実装を提供しましょう。

私たちはカバーします:

  1. 新しい VerificationTokenRepository

  2. IUserInterface の新しいメソッドとnewの実装

必要なCRUD操作

実施例5.1〜5.3。新しいインタフェースと実装を示します。

  • 例5.1 ** - 検証トークンリポジトリ

public interface VerificationTokenRepository
  extends JpaRepository<VerificationToken, Long> {

    VerificationToken findByToken(String token);

    VerificationToken findByUser(User user);
}
  • 例5.2 ** - IUserService インターフェース

public interface IUserService {

    User registerNewUserAccount(UserDto accountDto)
      throws EmailExistsException;

    User getUser(String verificationToken);

    void saveRegisteredUser(User user);

    void createVerificationToken(User user, String token);

    VerificationToken getVerificationToken(String VerificationToken);
}
  • 例5.3 ** UserService

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

    @Autowired
    private VerificationTokenRepository tokenRepository;

    @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());
        }

        User user = new User();
        user.setFirstName(accountDto.getFirstName());
        user.setLastName(accountDto.getLastName());
        user.setPassword(accountDto.getPassword());
        user.setEmail(accountDto.getEmail());
        user.setRole(new Role(Integer.valueOf(1), user));
        return repository.save(user);
    }

    private boolean emailExist(String email) {
        User user = repository.findByEmail(email);
        if (user != null) {
            return true;
        }
        return false;
    }

    @Override
    public User getUser(String verificationToken) {
        User user = tokenRepository.findByToken(verificationToken).getUser();
        return user;
    }

    @Override
    public VerificationToken getVerificationToken(String VerificationToken) {
        return tokenRepository.findByToken(VerificationToken);
    }

    @Override
    public void saveRegisteredUser(User user) {
        repository.save(user);
    }

    @Override
    public void createVerificationToken(User user, String token) {
        VerificationToken myToken = new VerificationToken(token, user);
        tokenRepository.save(myToken);
    }
}

6. 結論

この記事では、登録プロセスを拡張して 電子メールベースのアカウントの有効化手順 を追加しました。

アカウントアクティベーションロジックでは、ユーザーが自分の身元を確認するためにそれをコントローラーに送り返すことができるように、確認トークンを電子メールでユーザーに送信する必要があります。

このSpring Securityへの登録のチュートリアルは、https://github.com/eugenp/spring-security-registration[GitHubプロジェクト]にあります。これはEclipseベースのプロジェクトなので、インポートおよび実行が簡単です。そのまま。

"

  • «** 前へ