mercredi 18 février 2015

Implementing a failure handler form for Spring 4 + Spring Security 3.2.5 (no XML)

Problem



After trying to loggin in the application and receiving a CredentialsExpiredException, how to present a change password form? How to support additional parameters such new password and confirmation?



Solution




  1. Configure your WebSecurityConfigurerAdapter implementation class as:



    http.
    //...
    .antMatchers("/resources/**", "/login", "/index.**").permitAll()
    //...
    .authenticationDetailsSource(req -> new CustomWebAuthenticationDetails(req))
    .failureHandler((req, res, e) -> {
    req.getSession().setAttribute("SPRING_SECURITY_LAST_EXCEPTION", e);
    res.sendRedirect((e instanceof CredentialsExpiredException) ? CHANGE : FAILURE);
    })
    //...



Where:



private static final String FAILURE = "/index.html?login_error=true";
private static final String CHANGE = FAILURE + "&change=true";


And:



public class CustomWebAuthenticationDetails extends WebAuthenticationDetails {

private Map<String, String> params = new HashMap<>();

public CustomWebAuthenticationDetails(HttpServletRequest req) {
super(req);
put(req, "j_enc_np");
put(req, "j_enc_cnp");
}

private void put(HttpServletRequest req, String key) {
if (req.getParameter(key) != null && !"".equals(key)) {
params.put(key, req.getParameter(key));
}
}

public boolean isChange() {
return !params.isEmpty();
}

public String get(String key) {
return params.get(key);
}

}




  1. Prepare your login page for additional parameters:



    <form name="f" id="form-login" action="<c:url value="login"/>" method="post">
    <fieldset>
    <p><label for="j_enc_u"><spring:message code="app.lbl.username" /></label></p>
    <p><input type="text" id="j_enc_u" name="j_enc_u"></p>

    <p><label for="j_enc_p"><c:choose><c:when test="${param.change}"><spring:message code="app.lbl.old.password" /></c:when><c:otherwise><spring:message code="app.lbl.password" /></c:otherwise></c:choose></label></p>
    <p><input type="password" id="j_enc_p" name="j_enc_p"></p>

    <c:if test="${param.change}">
    <p><label for="j_enc_np"><spring:message code="app.lbl.new.password" /></label></p>
    <p><input type="password" id="j_enc_np" name="j_enc_np"></p>

    <p><label for="j_enc_cnp"><spring:message code="app.lbl.new.conf.password" /></label></p>
    <p><input type="password" id="j_enc_cnp" name="j_enc_cnp"></p>
    </c:if>

    <p><input type="submit" value="<spring:message code="app.lbl.validate" />"></p>
    </fieldset>
    </form>

    <c:choose>
    <c:when test="${param.login_error}">
    <div class="error-message"><c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}" /></div>
    </c:when>
    </c:choose>



  2. Prepare your AuthenticationProvider customization:



    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

    final String username = authentication.getPrincipal().toString().trim();
    final String password = authentication.getCredentials().toString().trim();

    final String encryptedPassword = passwordEncryptor.encryptPassword(password);
    final CustomWebAuthenticationDetails cwad = (CustomWebAuthenticationDetails) authentication.getDetails();

    try {
    final SecUserStatus status =
    (cwad.isChange()) ?
    authenticationService.resetPassword(username, encryptedPassword, cwad.get("j_enc_np"), cwad.get("j_enc_cnp")) :
    authenticationService.authenticate(username, encryptedPassword, cwad.getRemoteAddress());

    SecUser secUser = status.getSecUser();

    final UsernamePasswordAuthenticationToken authenticationToken =
    new UsernamePasswordAuthenticationToken(
    secUser,
    secUser.getPassword(),
    secUser.getAuthorities());

    authenticationToken.setDetails(secUser);
    authenticationService.lastLogin(secUser.getId(), new Date());
    return authenticationToken;

    } catch (AuthenticationException e) {
    LOGGER.error(e.getMessage(), e);
    throw e;
    }
    }



Aucun commentaire:

Enregistrer un commentaire