🔨 review jap-simple remember me code

This commit is contained in:
harrylee 2021-01-24 19:26:19 +08:00
parent 1ce732e5d4
commit abbbee5778
5 changed files with 242 additions and 280 deletions

View File

@ -15,6 +15,8 @@
*/
package com.fujieid.jap.core;
import java.nio.charset.StandardCharsets;
/**
* JAP constant
*
@ -26,4 +28,15 @@ package com.fujieid.jap.core;
public interface JapConst {
String SESSION_USER_KEY = "_jap:session:user";
/**
* Default salt. Default salt is not recommended
*/
byte[] DEFAULT_CREDENTIAL_ENCRYPT_SALT = "jap:123456".getBytes(StandardCharsets.UTF_8);
/**
* default delimiter
*/
char DEFAULT_DELIMITER = ':';
}

View File

@ -0,0 +1,51 @@
package com.fujieid.jap.simple;
/**
* @author harrylee (harryleexyz(a)qq.com)
* @version 1.0.0
* @date 2021/1/24 18:57
* @since 1.0.0
*/
public class RememberMeDetails {
/**
* username
*/
private String username;
/**
* expiry time
*/
private long expiryTime;
/**
* encode value
*/
private String encodeValue;
public String getUsername() {
return username;
}
public RememberMeDetails setUsername(String username) {
this.username = username;
return this;
}
public long getExpiryTime() {
return expiryTime;
}
public RememberMeDetails setExpiryTime(long expiryTime) {
this.expiryTime = expiryTime;
return this;
}
public String getEncodeValue() {
return encodeValue;
}
public RememberMeDetails setEncodeValue(String encodeValue) {
this.encodeValue = encodeValue;
return this;
}
}

View File

@ -0,0 +1,82 @@
package com.fujieid.jap.simple;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import static com.fujieid.jap.core.JapConst.DEFAULT_DELIMITER;
/**
* @author harrylee (harryleexyz(a)qq.com)
* @version 1.0.0
* @date 2021/1/24 19:12
* @since 1.0.0
*/
public class RememberMeDetailsUtils {
/**
* Encrypted acquisition instance.
*
* @param simpleConfig config
* @param username username
* @return
*/
public static RememberMeDetails encode(SimpleConfig simpleConfig, String username) {
long expiryTime = System.currentTimeMillis() + simpleConfig.getRememberMeCookieExpire();
// username:tokenExpiryTime
String md5Data = username + DEFAULT_DELIMITER + expiryTime;
String md5Key = simpleConfig.getCredentialEncrypt().digestHex16(md5Data);
// username:tokenExpiryTime:key
String base64Data = md5Data + DEFAULT_DELIMITER + md5Key;
return new RememberMeDetails()
.setUsername(username)
.setExpiryTime(expiryTime)
.setEncodeValue(new String(Base64.getEncoder().encode(base64Data.getBytes(StandardCharsets.UTF_8))));
}
/**
* Decryption acquisition instance.
*
* @param simpleConfig config
* @param cookieValue cookie value
* @return
*/
public static RememberMeDetails decode(SimpleConfig simpleConfig, String cookieValue) {
if (!simpleConfig.isEnableRememberMe()) {
return null;
}
String base64DecodeValue;
try {
base64DecodeValue = new String(Base64.getDecoder().decode(cookieValue.getBytes(StandardCharsets.UTF_8)));
} catch (RuntimeException e) {
return null;
}
String[] base64DecodeValueSplitArray = StrUtil.splitToArray(base64DecodeValue, DEFAULT_DELIMITER);
// Check and validate keys
if (base64DecodeValueSplitArray.length > 2) {
String username = base64DecodeValueSplitArray[0];
long expiryTime;
try {
expiryTime = Long.parseLong(base64DecodeValueSplitArray[1]);
} catch (RuntimeException e) {
return null;
}
// overdue
if (expiryTime < System.currentTimeMillis()) {
return null;
}
// username:tokenExpiryTime
String md5Data = username + DEFAULT_DELIMITER + expiryTime;
String md5Key = simpleConfig.getCredentialEncrypt().digestHex16(md5Data);
// Check pass returns
if (ObjectUtil.equal(md5Key, base64DecodeValueSplitArray[2])) {
return new RememberMeDetails()
.setUsername(username)
.setExpiryTime(expiryTime)
.setEncodeValue(cookieValue);
}
}
return null;
}
}

View File

@ -15,13 +15,12 @@
*/
package com.fujieid.jap.simple;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.MD5;
import com.fujieid.jap.core.AuthenticateConfig;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import static com.fujieid.jap.core.JapConst.DEFAULT_CREDENTIAL_ENCRYPT_SALT;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
@ -31,26 +30,6 @@ import java.util.Base64;
*/
public class SimpleConfig extends AuthenticateConfig {
/**
* Credential encryption algorithm: MD5 encryption
*/
private final MD5 credentialEncrypt;
/**
* Default salt. Default salt is not recommended
*/
private static final byte[] DEFAULT_CREDENTIAL_ENCRYPT_SALT = "jap:123456".getBytes(StandardCharsets.UTF_8);
/**
* default delimiter
*/
private static final char DEFAULT_DELIMITER = ':';
/**
* Whether to enable remember me
*/
private final boolean enableRememberMe;
/**
* Get the user name from request through {@code request.getParameter(`usernameField`)}, which defaults to "username"
*/
@ -60,23 +39,33 @@ public class SimpleConfig extends AuthenticateConfig {
*/
private String passwordField = "password";
/**
* Credential encryption algorithm: MD5 encryption
*/
private final MD5 credentialEncrypt;
/**
* Whether to enable remember me
*/
private final boolean enableRememberMe;
/**
* Get the remember-me from request through {@code request.getParameter(`rememberMeField`)}, which defaults to "remember-me"
*/
private String rememberMeField = "remember-me";
/**
* By default, remember me cookie key
* Default remember me cookie key
*/
private String rememberMeCookieKey = "_jap_remember_me";
/**
* remember me cookie expire, unit: second, default 60*60*24*30[month]
* Remember me cookie expire, unit: second, default 60*60*24*30[month]
*/
private Integer rememberMeCookieExpire = 2592000;
/**
* remember me cookie domain
* Remember me cookie domain
*/
private String rememberMeCookieDomain;
@ -84,19 +73,11 @@ public class SimpleConfig extends AuthenticateConfig {
* Generate the MD5 algorithm using the default key
*/
public SimpleConfig() {
// disabled
this.enableRememberMe = false;
this.credentialEncrypt = new MD5(DEFAULT_CREDENTIAL_ENCRYPT_SALT);
}
/**
* Whether enable RememberMe
*
* @return
*/
public boolean isEnableRememberMe() {
return enableRememberMe;
}
/**
* Customize MD5 algorithms
*
@ -135,164 +116,47 @@ public class SimpleConfig extends AuthenticateConfig {
return this;
}
public boolean isEnableRememberMe() {
return enableRememberMe;
}
public MD5 getCredentialEncrypt() {
return credentialEncrypt;
}
public String getRememberMeField() {
return rememberMeField;
}
public void setRememberMeField(String rememberMeField) {
public SimpleConfig setRememberMeField(String rememberMeField) {
this.rememberMeField = rememberMeField;
return this;
}
public String getRememberMeCookieKey() {
return rememberMeCookieKey;
}
public void setRememberMeCookieKey(String rememberMeCookieKey) {
public SimpleConfig setRememberMeCookieKey(String rememberMeCookieKey) {
this.rememberMeCookieKey = rememberMeCookieKey;
return this;
}
public Integer getRememberMeCookieExpire() {
return rememberMeCookieExpire;
}
public void setRememberMeCookieExpire(Integer rememberMeCookieExpire) {
public SimpleConfig setRememberMeCookieExpire(Integer rememberMeCookieExpire) {
this.rememberMeCookieExpire = rememberMeCookieExpire;
return this;
}
public String getRememberMeCookieDomain() {
return rememberMeCookieDomain;
}
public void setRememberMeCookieDomain(String rememberMeCookieDomain) {
public SimpleConfig setRememberMeCookieDomain(String rememberMeCookieDomain) {
this.rememberMeCookieDomain = rememberMeCookieDomain;
return this;
}
/**
* encode
*
* @param username username
* @param password password [keep]
* @return
*/
public RememberMeDetails encode(String username, String password) {
return RememberMeDetails.encodeInstance(this, username);
}
/**
* decode
*
* @param cookieValue
* @return
*/
public RememberMeDetails decode(String cookieValue) {
return RememberMeDetails.decodeInstance(this, cookieValue);
}
/**
* @author harrylee (harryleexyz(a)qq.com)
* @version 1.0.0
* @date 2021/1/24 14:57
* @since 1.0.0
*/
public static class RememberMeDetails {
/**
* username
*/
private String username;
/**
* expiry time
*/
private long expiryTime;
/**
* encode value
*/
private String encodeValue;
public String getUsername() {
return username;
}
public long getExpiryTime() {
return expiryTime;
}
public String getEncodeValue() {
return encodeValue;
}
private SimpleConfig simpleConfig;
private RememberMeDetails() {
}
/**
* Encrypted acquisition instance.
*
* @param simpleConfig config
* @param username username
* @return
*/
public static RememberMeDetails encodeInstance(SimpleConfig simpleConfig, String username) {
RememberMeDetails rememberMeDetails = new RememberMeDetails();
rememberMeDetails.username = username;
long expiryTime = System.currentTimeMillis() + simpleConfig.getRememberMeCookieExpire();
rememberMeDetails.expiryTime = expiryTime;
// username:tokenExpiryTime
String md5Data = username + DEFAULT_DELIMITER + expiryTime;
String md5Key = simpleConfig.credentialEncrypt.digestHex16(md5Data);
// username:tokenExpiryTime:key
String base64Data = md5Data + DEFAULT_DELIMITER + md5Key;
rememberMeDetails.encodeValue = new String(Base64.getEncoder().encode(base64Data.getBytes(StandardCharsets.UTF_8)));
return rememberMeDetails;
}
/**
* Decryption acquisition instance.
*
* @param simpleConfig config
* @param cookieValue cookie value
* @return
*/
public static RememberMeDetails decodeInstance(SimpleConfig simpleConfig, String cookieValue) {
if (!simpleConfig.enableRememberMe) {
return null;
}
RememberMeDetails rememberMeDetails = new RememberMeDetails();
rememberMeDetails.encodeValue = cookieValue;
String base64DecodeValue;
try {
base64DecodeValue = new String(Base64.getDecoder().decode(cookieValue.getBytes(StandardCharsets.UTF_8)));
} catch (RuntimeException e) {
return null;
}
String[] base64DecodeValueSplitArray = StrUtil.splitToArray(base64DecodeValue, DEFAULT_DELIMITER);
// Check and validate keys
if (base64DecodeValueSplitArray.length > 2) {
String username = base64DecodeValueSplitArray[0];
long expiryTime;
try {
expiryTime = Long.parseLong(base64DecodeValueSplitArray[1]);
} catch (RuntimeException e) {
return null;
}
// overdue
if (expiryTime < System.currentTimeMillis()) {
return null;
}
// username:tokenExpiryTime
String md5Data = username + DEFAULT_DELIMITER + expiryTime;
String md5Key = simpleConfig.credentialEncrypt.digestHex16(md5Data);
// Check pass returns
if (ObjectUtil.equal(md5Key, base64DecodeValueSplitArray[2])) {
rememberMeDetails.username = username;
rememberMeDetails.expiryTime = expiryTime;
return rememberMeDetails;
}
}
return null;
}
}
}

View File

@ -15,12 +15,13 @@
*/
package com.fujieid.jap.simple;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.fujieid.jap.core.AuthenticateConfig;
import com.fujieid.jap.core.JapConfig;
import com.fujieid.jap.core.JapUser;
import com.fujieid.jap.core.JapUserService;
import com.fujieid.jap.core.exception.JapException;
import com.fujieid.jap.core.exception.JapUserException;
import com.fujieid.jap.core.store.JapUserStore;
import com.fujieid.jap.core.strategy.AbstractJapStrategy;
@ -28,7 +29,6 @@ import com.fujieid.jap.core.strategy.AbstractJapStrategy;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* The local authentication strategy authenticates requests based on the credentials submitted through an HTML-based
@ -63,15 +63,14 @@ public class SimpleStrategy extends AbstractJapStrategy {
@Override
public void authenticate(AuthenticateConfig config, HttpServletRequest request, HttpServletResponse response) {
if (this.checkSession(request, response)) {
return;
}
// Convert AuthenticateConfig to SimpleConfig
this.checkAuthenticateConfig(config, SimpleConfig.class);
SimpleConfig simpleConfig = (SimpleConfig) config;
if (this.checkSessionAndCookie(simpleConfig, request, response)) {
return;
}
UsernamePasswordCredential credential = this.doResolveCredential(request, simpleConfig);
JapUser user = japUserService.getByName(credential.getUsername());
if (null == user) {
@ -83,35 +82,65 @@ public class SimpleStrategy extends AbstractJapStrategy {
throw new JapUserException("Passwords don't match.");
}
this.loginSuccess(simpleConfig, credential, user, request, response);
}
/**
* 登录成功
*
* @param simpleConfig
* @param credential
* @param user
* @param request
* @param response
*/
protected void loginSuccess(SimpleConfig simpleConfig, UsernamePasswordCredential credential, JapUser user, HttpServletRequest request, HttpServletResponse response) {
if (simpleConfig.isEnableRememberMe() && credential.isRememberMe()) {
// add cookie
this.addRememberMeCookie(user, simpleConfig, request, response);
ServletUtil.addCookie(response,
simpleConfig.getRememberMeCookieKey(),
this.encodeCookieValue(user, simpleConfig),
simpleConfig.getRememberMeCookieExpire(),
this.getRequestPath(request),
ObjectUtil.isNotEmpty(simpleConfig.getRememberMeCookieDomain()) ? simpleConfig.getRememberMeCookieDomain() : null
);
}
this.loginSuccess(user, request, response);
}
/**
* math cookie
* check session and cookie
*
* @param simpleConfig config
* @param request The request to authenticate
* @param simpleConfig
* @param request
* @param response
* @return
*/
protected String matchRememberMeCookie(SimpleConfig simpleConfig, HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if ((cookies == null) || (cookies.length == 0)) {
return null;
protected boolean checkSessionAndCookie(SimpleConfig simpleConfig, HttpServletRequest request, HttpServletResponse response) {
if (this.checkSession(request, response)) {
return true;
}
if (!simpleConfig.isEnableRememberMe()) {
return false;
}
for (Cookie cookie : cookies) {
if (ObjectUtil.equal(simpleConfig.getRememberMeCookieKey(), cookie.getName())) {
return cookie.getValue();
}
Cookie cookie = ServletUtil.getCookie(request, simpleConfig.getRememberMeCookieKey());
if (ObjectUtil.isNull(cookie)) {
return false;
}
return null;
UsernamePasswordCredential credential = this.decodeCookieValue(simpleConfig, cookie.getValue());
if (ObjectUtil.isNull(credential)) {
return false;
}
JapUser user = japUserService.getByName(credential.getUsername());
if (null == user) {
throw new JapUserException("The user does not exist.");
}
// redirect login successful
this.loginSuccess(user, request, response);
return true;
}
/**
@ -122,75 +151,15 @@ public class SimpleStrategy extends AbstractJapStrategy {
* @return
*/
protected UsernamePasswordCredential decodeCookieValue(SimpleConfig simpleConfig, String cookieValue) {
SimpleConfig.RememberMeDetails decode = simpleConfig.decode(cookieValue);
if (ObjectUtil.isNotNull(decode)) {
RememberMeDetails details = RememberMeDetailsUtils.decode(simpleConfig, cookieValue);
if (ObjectUtil.isNotNull(details)) {
// return no longer password and remember me
return new UsernamePasswordCredential()
.setUsername(decode.getUsername());
.setUsername(details.getUsername());
}
return null;
}
/**
* authenticate cookie
*
* @param config config
* @param request The request to authenticate
* @param response The response to authenticate
*/
public void authenticateCookie(AuthenticateConfig config, HttpServletRequest request, HttpServletResponse response) {
if (this.checkSession(request, response)) {
return;
}
// Convert AuthenticateConfig to SimpleConfig
this.checkAuthenticateConfig(config, SimpleConfig.class);
SimpleConfig simpleConfig = (SimpleConfig) config;
String cookieValue = this.matchRememberMeCookie(simpleConfig, request);
if (ObjectUtil.isNotNull(cookieValue)) {
UsernamePasswordCredential credential = this.decodeCookieValue(simpleConfig, cookieValue);
if (ObjectUtil.isNotNull(cookieValue)) {
JapUser user = japUserService.getByName(credential.getUsername());
if (null == user) {
throw new JapUserException("The user does not exist.");
}
// redirect login successful
this.loginSuccess(user, request, response);
return;
}
}
// redirect login url
try {
response.sendRedirect(japConfig.getLoginUrl());
} catch (IOException e) {
throw new JapException("JAP failed to redirect via HttpServletResponse.", e);
}
}
/**
* add RememberMe Cookie
*
* @param user
* @param simpleConfig
* @param request
* @param response
*/
protected void addRememberMeCookie(JapUser user, SimpleConfig simpleConfig, HttpServletRequest request, HttpServletResponse response) {
String cookieValue = this.encodeCookieValue(user, simpleConfig);
Cookie cookie = new Cookie(simpleConfig.getRememberMeCookieKey(), cookieValue);
cookie.setMaxAge(simpleConfig.getRememberMeCookieExpire());
cookie.setPath(this.getRequestPath(request));
cookie.setSecure(request.isSecure());
// Custom domain name
if (ObjectUtil.isNotEmpty(simpleConfig.getRememberMeCookieDomain())) {
cookie.setDomain(simpleConfig.getRememberMeCookieDomain());
}
response.addCookie(cookie);
}
/**
* Clear the cookie to log out of use
*
@ -199,14 +168,11 @@ public class SimpleStrategy extends AbstractJapStrategy {
* @param response
*/
public void cancelRememberMeCookie(SimpleConfig simpleConfig, HttpServletRequest request, HttpServletResponse response) {
Cookie cookie = new Cookie(simpleConfig.getRememberMeCookieKey(), null);
cookie.setMaxAge(0);
cookie.setPath(this.getRequestPath(request));
// Custom domain name
if (ObjectUtil.isNotEmpty(simpleConfig.getRememberMeCookieDomain())) {
cookie.setDomain(simpleConfig.getRememberMeCookieDomain());
}
response.addCookie(cookie);
ServletUtil.addCookie(response,
simpleConfig.getRememberMeCookieKey(),
null, 0,
this.getRequestPath(request),
ObjectUtil.isNotEmpty(simpleConfig.getRememberMeCookieDomain()) ? simpleConfig.getRememberMeCookieDomain() : null);
}
/**
@ -217,7 +183,7 @@ public class SimpleStrategy extends AbstractJapStrategy {
* @return
*/
protected String encodeCookieValue(JapUser user, SimpleConfig simpleConfig) {
return simpleConfig.encode(user.getUsername(), user.getPassword()).getEncodeValue();
return RememberMeDetailsUtils.encode(simpleConfig, user.getUsername()).getEncodeValue();
}
/**
@ -237,26 +203,12 @@ public class SimpleStrategy extends AbstractJapStrategy {
if (null == username || null == password) {
throw new JapUserException("Missing credentials");
}
boolean rememberMe = this.rememberRequest(request, simpleConfig);
return new UsernamePasswordCredential()
.setUsername(username)
.setPassword(password)
.setRememberMe(rememberMe);
}
/**
* Retrieve Remember me from the request
*
* @param request
* @param simpleConfig
* @return
*/
protected boolean rememberRequest(HttpServletRequest request, SimpleConfig simpleConfig) {
String rememberParameter = request.getParameter(simpleConfig.getRememberMeField());
if (rememberParameter != null) {
return rememberParameter.equalsIgnoreCase("true") || rememberParameter.equalsIgnoreCase("on")
|| rememberParameter.equalsIgnoreCase("yes") || rememberParameter.equals("1");
}
return false;
.setRememberMe(
BooleanUtil.toBoolean(
request.getParameter(simpleConfig.getRememberMeField()))
);
}
}