首页 > 解决方案 > Spring Boot:如何告诉客户端帐户密码已过期?

问题描述

我在控制器中有一个简单的登录表单和一个 login() 方法:

@PostMapping("/login")
public ResponseEntity<UserVO> login(@RequestBody UserVO userVO) {

    Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                    userVO.getUsername(),
                    userVO.getPassword()
            )
    );

    SecurityContextHolder.getContext().setAuthentication( authentication );

    // ...
    return ResponseEntity.ok( loggedInUser );
}

我有一个包含列的用户表:

  `username`
  `password`
  `password_expiration`
  `account_expiration`
  `account_locked`
  `account_disabled`

当我将 password_expiration 列的值设置为使用户密码过期时,Spring Boot 的authenticationManager.authenticate()方法在下次登录尝试时抛出AccountExpiredException异常:

package org.springframework.security.authentication;

public class AccountExpiredException extends AccountStatusException {
    public AccountExpiredException(String msg) {
        super(msg);
    }

    public AccountExpiredException(String msg, Throwable t) {
        super(msg, t);
    }
}

JSON响应是:

{
"timestamp":"2018-07-26T22:53:05.392+0000",
"status":401,
"error":"Unauthorized",
"message":"Unauthorized",
"path":"/login"
}

每当密码错误或 UserVO 的方法之一(反过来实现 UserDetails)返回 false 时,我都会得到相同的 JSON 响应(401 错误代码):

boolean isAccountNonExpired();

boolean isAccountNonLocked();

boolean isCredentialsNonExpired();

boolean isEnabled();

到目前为止,一切都很好。

当用户登录并且他/她的密码过期时,我想将 UI 重定向到强制密码更改页面。但是如何?我总是得到相同的 JSON 响应。

1) 由于返回的 JSON 输出始终是 HTTP 401 错误,我怎样才能获得更细粒度的响应?(如何告诉客户端代码密码已过期?)

2) 通知用户他/她的帐户被锁定/过期/禁用通常被认为是好的还是坏的做法?(良好的用户体验与向黑客泄露有关帐户状态的信息)

标签: spring-bootjwt

解决方案


也许不是最好的解决方案,但我通过设置“消息”字段解决了它:

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {

    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException {

        String message = "Unauthorized";
        if ( authException instanceof BadCredentialsException )
            message = AUTH_CREDENTIALS_BAD;
        else if ( authException instanceof CredentialsExpiredException )
            message = AUTH_CREDENTIALS_EXPIRED;
        else if ( authException instanceof LockedException )
            message = AUTH_ACCOUNT_LOCKED;
        else if ( authException instanceof DisabledException )
            message = AUTH_ACCOUNT_DISABLED;
        else if ( authException instanceof AccountExpiredException )
            message = AUTH_ACCOUNT_EXPIRED;

        response.sendError( HttpServletResponse.SC_UNAUTHORIZED, message );
    }
}

现在我得到这个作为回应:

{
   "timestamp":"2018-07-27T12:54:53.097+0000",
   "status":401,
   "error":"Unauthorized",
   "message":"credentials_expired",
   "path":"/login"
}

推荐阅读