// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
package jodd.joy.auth;
import jodd.madvoc.ActionRequest;
import jodd.madvoc.interceptor.BaseActionInterceptor;
import jodd.servlet.CsrfShield;
import jodd.util.StringUtil;
import jodd.log.Logger;
import jodd.log.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* Authentication checking interceptor. Usually invoked before {@link AuthorizationInterceptor}.
* Provides auto-login using cookies.
* <p>
* <b>Authentication</b> pertains to the question "Who are you?". Usually a user
* authenticates himself by successfully associating his "principal"
* (often a username) with his "credentials" (often a password).
*/
public abstract class AuthenticationInterceptor<U> extends BaseActionInterceptor {
private static final Logger log = LoggerFactory.getLogger(AuthenticationInterceptor.class);
/**
* If <code>true</code>, cookie will be created for keeping user sessions.
*/
protected boolean useCookie = true;
/**
* Cookie max age, when cookies are used.
* By default set to 14 days.
*/
protected int cookieMaxAge = 14 * 24 * 60 * 60;
/**
* When user just logs in with cookie, should we recreate the cookie
* (and therefore prolong cookie valid time) or leave it as it is.
*/
protected boolean recreateCookieOnLogin;
public Object intercept(ActionRequest actionRequest) throws Exception {
HttpServletRequest servletRequest = actionRequest.getHttpServletRequest();
HttpServletResponse servletResponse = actionRequest.getHttpServletResponse();
HttpSession session = servletRequest.getSession();
String actionPath = actionRequest.getActionPath();
// LOGOUT
if (isLogoutAction(actionPath)) {
log.debug("logout user");
closeAuthSession(servletRequest, servletResponse);
return resultLogoutSuccess();
}
// any other page then logout
U userSession = (U) AuthUtil.getUserSession(session);
if (userSession != null) {
// USER IS LOGGED IN
if (isLoginAction(actionPath)) {
// never access login path while user is logged in
return resultLoginSuccess(null);
}
// CONTINUE
return actionRequest.invoke();
}
// no user session
// COOKIE
// user session is not available, check the cookie
String[] cookieData = null;
if (useCookie) {
try {
cookieData = AuthUtil.readAuthCookie(servletRequest);
} catch (Exception ex) {
log.warn("invalid cookie", ex);
}
}
if (cookieData != null) {
userSession = loginViaCookie(cookieData);
if (userSession != null) {
log.debug("login with cookie");
startAuthSession(servletRequest, servletResponse, userSession, false);
if (isLoginAction(actionPath)) {
return resultLoginSuccess(null);
}
// continue
return actionRequest.invoke();
}
// no session, invalidate cookie
closeAuthSession(servletRequest, servletResponse);
}
// REGISTER USER
if (isRegisterAction(actionPath)) {
U newUserSession = (U) AuthUtil.getNewUserSession(servletRequest);
if (newUserSession != null) {
log.debug("new user session created");
startAuthSession(servletRequest, servletResponse, newUserSession, true);
return resultRegistrationSuccess();
}
}
if (!isLoginAction(actionPath)) {
// ANY PAGE BUT LOGIN, continue
return actionRequest.invoke();
}
// LOGIN
// session is not active, but user wants to login
String token = servletRequest.getParameter(AuthAction.LOGIN_TOKEN);
// check token
if (!CsrfShield.checkCsrfToken(session, token)) {
log.warn("csrf token validation failed");
return resultLoginFailed(2);
}
userSession = loginViaRequest(servletRequest);
if (userSession == null) {
log.warn("login failed");
return resultLoginFailed(1);
}
startAuthSession(servletRequest, servletResponse, userSession, true);
log.info("login ok");
// LOGGED IN
String path = servletRequest.getParameter(AuthAction.LOGIN_SUCCESS_PATH);
return resultLoginSuccess(path);
}
// ---------------------------------------------------------------- main auth hooks
/**
* Starts auth session by saving session auth object and optionally creating an auth cookie.
* Auth cookie is created for new auth sessions and for old ones if recreation is enabled.
* @param servletRequest http request
* @param servletResponse http response
* @param userSession created session object
* @param isNew if <code>true</code> indicated the session is new (i.e. user is either registered or signed in), if <code>false</code> means that session is continued (i.e. user is signed in via cookie).
*/
protected void startAuthSession(HttpServletRequest servletRequest, HttpServletResponse servletResponse, U userSession, boolean isNew) {
AuthUtil.startUserSession(servletRequest, userSession);
if (!useCookie) {
return;
}
if (isNew || recreateCookieOnLogin) {
String[] cookieData = createCookieData(userSession);
if (cookieData != null) {
AuthUtil.storeAuthCookie(servletResponse, cookieMaxAge, cookieData[0], cookieData[1]);
}
}
}
/**
* Closes auth session by removing auth session object from the http session
* and clearing the auth cookie.
*/
protected void closeAuthSession(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
AuthUtil.closeUserSession(servletRequest);
AuthUtil.removeAuthCookie(servletRequest, servletResponse);
}
// ---------------------------------------------------------------- actions detectors
/**
* Detects login path.
*/
protected boolean isLoginAction(String actionPath) {
return actionPath.equals(AuthAction.LOGIN_ACTION_PATH);
}
/**
* Detects logout path.
*/
protected boolean isLogoutAction(String actionPath) {
return actionPath.equals(AuthAction.LOGOUT_ACTION_PATH);
}
/**
* Detects registration path.
*/
protected boolean isRegisterAction(String actionPath) {
return actionPath.equals(AuthAction.REGISTER_ACTION_PATH);
}
// ---------------------------------------------------------------- results
/**
* Prepares result to continue to, after success login.
* If target path is empty, will continue to the index page.
*/
protected Object resultLoginSuccess(String path) {
if (StringUtil.isEmpty(path)) {
path = AuthAction.ALIAS_INDEX;
}
return "redirect:" + path;
}
/**
* Prepares result for logout success page.
*/
protected Object resultLogoutSuccess() {
return "redirect:" + AuthAction.ALIAS_INDEX;
}
/**
* Prepares result for registration success page.
*/
protected Object resultRegistrationSuccess() {
return "redirect:" + AuthAction.ALIAS_INDEX;
}
/**
* Prepares result for login failed page.
*/
protected Object resultLoginFailed(int reason) {
return "redirect:" + AuthAction.ALIAS_LOGIN + "?err=" + reason;
}
// ---------------------------------------------------------------- abstracts
/**
* Tries to login user with cookie data. Returns session object, otherwise returns <code>null</code>.
*/
protected abstract U loginViaCookie(String[] cookieData);
/**
* Tires to login user with form data. Returns session object, otherwise returns <code>null</code>.
* By default, calls {@link #loginUsernamePassword(String, String)}.
*/
protected U loginViaRequest(HttpServletRequest servletRequest) {
String username = servletRequest.getParameter(AuthAction.LOGIN_USERNAME);
String password = servletRequest.getParameter(AuthAction.LOGIN_PASSWORD);
log.info("login " + username);
return loginUsernamePassword(username, password);
}
/**
* Tries to login a user using username and password.
* Returns session object if login was successful.
*
* @param username entered user name from login form
* @param password entered raw password
*/
protected abstract U loginUsernamePassword(String username, String password);
/**
* Prepares cookie data from session object.
*/
protected abstract String[] createCookieData(U userSession);
}