1概要

このチュートリアルでは、必要なときにユーザーが自分のパスワードを安全に再設定できるように、現在進行中の


Spring Security

シリーズへの登録

を継続しています**


2パスワードリセットトークン

ユーザーパスワードのリセットに使用する

PasswordResetToken

エンティティを作成することから始めましょう。

@Entity
public class PasswordResetToken {

    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;
}

パスワードのリセットが行われると、トークンが作成され、このトークンを含む特別なリンクがユーザーに電子メールで送信されます。

トークンとリンクは設定された期間(この例では24時間)だけ有効です。


3

forgotPassword.html


プロセスの最初のページは

「__パスワードを忘れた」ページです。

– 実際のリセットプロセスを開始するために、ユーザーは電子メールアドレスの入力を求められます。

そのため、ユーザーに電子メールアドレスを要求する簡単な

forgotPassword.html

を作成しましょう。

<html>
<body>
    <h1 th:text="#{message.resetPassword}">reset</h1>

    <label th:text="#{label.user.email}">email</label>
    <input id="email" name="email" type="email" value=""/>
    <button type="submit" onclick="resetPass()"
      th:text="#{message.resetPassword}">reset</button>

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

<script src="jquery.min.js"></script>
<script th:inline="javascript">
var serverContext =[[@{/}]];
function resetPass(){
    var email = $("#email").val();
    $.post(serverContext + "user/resetPassword",{email: email} ,
      function(data){
          window.location.href =
           serverContext + "login?message=" + data.message;
    })
    .fail(function(data) {
        if(data.responseJSON.error.indexOf("MailError") > -1)
        {
            window.location.href = serverContext + "emailError.html";
        }
        else{
            window.location.href =
              serverContext + "login?message=" + data.responseJSON.message;
        }
    });
}

</script>
</body>

</html>

ログインページからこの新しい「

reset password

」ページにリンクする必要があります。

<a th:href="@{/forgetPassword.html}"
  th:text="#{message.resetPassword}">reset</a>


4

PasswordResetToken


を作成します.

まず、新しい

PasswordResetToken

を作成し、それを電子メールでユーザーに送信します。

@RequestMapping(value = "/user/resetPassword",
                method = RequestMethod.POST)
@ResponseBody
public GenericResponse resetPassword(HttpServletRequest request,
  @RequestParam("email") String userEmail) {
    User user = userService.findUserByEmail(userEmail);
    if (user == null) {
        throw new UserNotFoundException();
    }
    String token = UUID.randomUUID().toString();
    userService.createPasswordResetTokenForUser(user, token);
    mailSender.send(constructResetTokenEmail(getAppUrl(request),
      request.getLocale(), token, user));
    return new GenericResponse(
      messages.getMessage("message.resetPasswordEmail", null,
      request.getLocale()));
}

そしてこれがメソッド

createPasswordResetTokenForUser()

です。

public void createPasswordResetTokenForUser(User user, String token) {
    PasswordResetToken myToken = new PasswordResetToken(token, user);
    passwordTokenRepository.save(myToken);
}

そしてここに

constructResetTokenEmail()

というメソッドがあります – リセットトークン付きのEメールを送信するために使用されます。

private SimpleMailMessage constructResetTokenEmail(
  String contextPath, Locale locale, String token, User user) {
    String url = contextPath + "/user/changePassword?id=" +
      user.getId() + "&token=" + token;
    String message = messages.getMessage("message.resetPassword",
      null, locale);
    return constructEmail("Reset Password", message + " \r\n" + url, user);
}

private SimpleMailMessage constructEmail(String subject, String body,
  User user) {
    SimpleMailMessage email = new SimpleMailMessage();
    email.setSubject(subject);
    email.setText(body);
    email.setTo(user.getEmail());
    email.setFrom(env.getProperty("support.email"));
    return email;
}

クライアントへの応答を表すために、単純なオブジェクト

GenericResponse

をどのように使用したかに注意してください。

public class GenericResponse {
    private String message;
    private String error;

    public GenericResponse(String message) {
        super();
        this.message = message;
    }

    public GenericResponse(String message, String error) {
        super();
        this.message = message;
        this.error = error;
    }
}


5

PasswordResetToken


を処理します.

ユーザーは自分のパスワードをリセットするための固有のリンクを含むEメールを受け取り、そのリンクをクリックします。

@RequestMapping(value = "/user/changePassword", method = RequestMethod.GET)
public String showChangePasswordPage(Locale locale, Model model,
  @RequestParam("id") long id, @RequestParam("token") String token) {
    String result = securityService.validatePasswordResetToken(id, token);
    if (result != null) {
        model.addAttribute("message",
          messages.getMessage("auth.message." + result, null, locale));
        return "redirect:/login?lang=" + locale.getLanguage();
    }
    return "redirect:/updatePassword.html?lang=" + locale.getLanguage();
}

そしてこれが

validatePasswordResetToken()

メソッドです。

public String validatePasswordResetToken(long id, String token) {
    PasswordResetToken passToken =
      passwordTokenRepository.findByToken(token);
    if ((passToken == null) || (passToken.getUser()
        .getId() != id)) {
        return "invalidToken";
    }

    Calendar cal = Calendar.getInstance();
    if ((passToken.getExpiryDate()
        .getTime() - cal.getTime()
        .getTime()) <= 0) {
        return "expired";
    }

    User user = passToken.getUser();
    Authentication auth = new UsernamePasswordAuthenticationToken(
      user, null, Arrays.asList(
      new SimpleGrantedAuthority("CHANGE__PASSWORD__PRIVILEGE")));
    SecurityContextHolder.getContext().setAuthentication(auth);
    return null;
}

ご覧のとおり、トークンが有効な場合、ユーザーは

CHANGE

PASSWORD

PRIVILEGE

を付与してパスワードを変更し、パスワードを更新するページに移動することを承認されます。

ここで興味深いのは、この新しい権限は(名前が示すように)パスワードを変更するためにのみ使用可能になるということです。したがって、プログラム的にユーザーに付与するのは安全です。


6. パスワードを変更する

この時点で、ユーザーは単純な

Password Reset

ページを見ることができます – 唯一の可能なオプションは新しいパスワードを提供することです


6.1.

updatePassword.html


<html>
<body>
<div sec:authorize="hasAuthority('CHANGE__PASSWORD__PRIVILEGE')">
    <h1 th:text="#{message.resetYourPassword}">reset</h1>
    <form>
        <label th:text="#{label.user.password}">password</label>
        <input id="password" name="newPassword" type="password" value=""/>

        <label th:text="#{label.user.confirmPass}">confirm</label>
        <input id="matchPassword" type="password" value=""/>

        <div id="globalError" style="display:none"
          th:text="#{PasswordMatches.user}">error</div>
        <button type="submit" onclick="savePass()"
          th:text="#{message.updatePassword}">submit</button>
    </form>

<script th:inline="javascript">
var serverContext =[[@{/}]];
$(document).ready(function () {
    $('form').submit(function(event) {
        savePass(event);
    });

    $(":password").keyup(function(){
        if($("#password").val() != $("#matchPassword").val()){
            $("#globalError").show().html(/** [[#{PasswordMatches.user}]]** /);
        }else{
            $("#globalError").html("").hide();
        }
    });
});

function savePass(event){
    event.preventDefault();
    if($("#password").val() != $("#matchPassword").val()){
        $("#globalError").show().html(/** [[#{PasswordMatches.user}]]** /);
        return;
    }
    var formData= $('form').serialize();
    $.post(serverContext + "user/savePassword",formData ,function(data){
        window.location.href = serverContext + "login?message="+data.message;
    })
    .fail(function(data) {
        if(data.responseJSON.error.indexOf("InternalError") > -1){
            window.location.href = serverContext + "login?message=" + data.responseJSON.message;
        }
        else{
            var errors = $.parseJSON(data.responseJSON.message);
            $.each( errors, function( index,item ){
                $("#globalError").show().html(item.defaultMessage);
            });
            errors = $.parseJSON(data.responseJSON.error);
            $.each( errors, function( index,item ){
                $("#globalError").show().append(item.defaultMessage+"<br/>");
            });
        }
    });
}
</script>
</div>
</body>
</html>


6.2. ユーザーパスワードを保存

最後に、前回の投稿要求が送信されると、新しいユーザーパスワードが保存されます。

@RequestMapping(value = "/user/savePassword", method = RequestMethod.POST)
@ResponseBody
public GenericResponse savePassword(Locale locale,
  @Valid PasswordDto passwordDto) {
    User user =
      (User) SecurityContextHolder.getContext()
                                  .getAuthentication().getPrincipal();

    userService.changeUserPassword(user, passwordDto.getNewPassword());
    return new GenericResponse(
      messages.getMessage("message.resetPasswordSuc", null, locale));
}

そして、これが

changeUserPassword()

メソッドです。

public void changeUserPassword(User user, String password) {
    user.setPassword(passwordEncoder.encode(password));
    repository.save(user);
}

次のように、更新リクエストと保存パスワードリクエストを保護しています。

protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/user/updatePassword** ",
                     "/user/savePassword** ",
                     "/updatePassword** ")
        .hasAuthority("CHANGE__PASSWORD__PRIVILEGE")
...


7. 結論

この記事では、成熟した認証プロセスのための単純だが非常に便利な機能、つまりシステムのユーザーとして自分のパスワードをリセットするオプションを実装しました。

このチュートリアルの

完全な実装

はhttps://github.com/eugenp/spring-security-registration[GitHubプロジェクト]にあります – これはEclipseベースのプロジェクトなので、次のようにインポートして実行するのは簡単です。そうです。




  • «** 前へ


登録API

RESTfulになる]