Skip to content

Instantly share code, notes, and snippets.

@greenyleaf
Created June 14, 2020 07:40
Show Gist options
  • Select an option

  • Save greenyleaf/b5a49f5f5459686a8daf69407efc1ff7 to your computer and use it in GitHub Desktop.

Select an option

Save greenyleaf/b5a49f5f5459686a8daf69407efc1ff7 to your computer and use it in GitHub Desktop.
customized Apache Shiro, Remember Me Manager code, with spring-mybatis JDBC based persistent login, references some posts on Stackoverflow.
package top.sdrkyj.custom.shiro;
import top.sdrkyj.custom.entity.Account;
import top.sdrkyj.custom.entity.PersistentLogin;
import top.sdrkyj.custom.service.PersistentLoginService;
import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.Sha512Hash;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.subject.WebSubjectContext;
import org.apache.shiro.web.util.RequestPairSource;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.MessageFormat;
import java.util.Base64;
@Component("rememberMeManager")
public class CustomRemeberMeManager extends CookieRememberMeManager {
private final static Logger logger = LoggerFactory.getLogger(CustomRemeberMeManager.class);
private final static RandomNumberGenerator rng = new SecureRandomNumberGenerator();
private final PersistentLoginService persistentLoginService;
public CustomRemeberMeManager(PersistentLoginService persistentLoginService) {
this.persistentLoginService = persistentLoginService;
getCookie().setName("remember_me");
}
@Override
public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {
//修改, 根据cookie 值的token, 对应用户id, 查询用户信息。
logger.info("getRememberedPrincipals entered");
//PrincipalCollection principals = null;
//try {
String value = getRememberedCookieValue(subjectContext);
//SHIRO-138 - only call convertBytesToPrincipals if bytes exist:
if (value != null && value.length() > 0) {
int pos = value.indexOf(':');
if (pos <= 0 || pos >= value.length() - 1) {
onRememberedPrincipalFailure(new RuntimeException("无效的客户端持久化登录内容,不包含 ':' 分隔符。"), subjectContext);
}
String series = value.substring(0, pos);
//... dao, read by cookie value ;
Account byToken;
PersistentLogin persistentLogin = persistentLoginService.findBySeries(series);
if (persistentLogin != null) {
//比较 token 解 base4 编码后的散列值
String clientToken = value.substring(pos + 1);
byte[] clientTokenBytes = null;
try {
clientTokenBytes = Base64.getDecoder().decode(clientToken);
} catch (IllegalArgumentException ex) {
onRememberedPrincipalFailure(new RuntimeException("无效的客户端持久化登录内容,series 不是有效的 base64 编码。", ex), subjectContext);
}
if (!persistentLogin.getToken().equals(new Sha512Hash(clientTokenBytes).toBase64())) {
onRememberedPrincipalFailure(new RuntimeException("无效的客户端持久化登录内容, 客户端 token 不匹配存储的的 token 。"), subjectContext);
}
byToken = persistentLogin.getAccount();
SimplePrincipalCollection spc = new SimplePrincipalCollection();
spc.add(byToken.getId(), "tokenRealm");
spc.add(byToken, "tokenRealm");
// 生成新的 token ,持久存储散列值;按照 series, 更新持久登录表。
//更新客户端 token
ByteSource byteSource2 = rng.nextBytes(80);
String token = byteSource2.toBase64();
if (persistentLoginService.update(series, new Sha512Hash(byteSource2.getBytes()).toBase64())) {
//rememberSerializedIdentity(subject, byteSource.getBytes());
spc.add(series, "series");
//logger.debug("subjectContext instanceof RequestPairSource, {}", subjectContext instanceof RequestPairSource);
//logger.debug("subjectContext.getClass(), {}", subjectContext.getClass());
rememberToken(subjectContext, MessageFormat.format("{0}:{1}", series, token));
}
return spc;
}
}
return null;
}
private boolean isIdentityRemoved(WebSubjectContext subjectContext) {
ServletRequest request = subjectContext.resolveServletRequest();
if (request != null) {
Boolean removed = (Boolean) request.getAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY);
return removed != null && removed;
}
return false;
}
protected String getRememberedCookieValue(SubjectContext subjectContext) {
if (!WebUtils.isHttp(subjectContext)) {
String msg = "SubjectContext argument is not an HTTP-aware instance. This is required to obtain a " +
"servlet request and response in order to retrieve the rememberMe cookie. Returning " +
"immediately and ignoring rememberMe operation.";
logger.debug(msg);
return null;
}
WebSubjectContext wsc = (WebSubjectContext) subjectContext;
if (isIdentityRemoved(wsc)) {
return null;
}
HttpServletRequest request = WebUtils.getHttpRequest(wsc);
HttpServletResponse response = WebUtils.getHttpResponse(wsc);
String cookieValue = getCookie().readValue(request, response);
// Browsers do not always remove cookies immediately (SHIRO-183)
// ignore cookies that are scheduled for removal
if (Cookie.DELETED_COOKIE_VALUE.equals(cookieValue)) return null;
//may be null, new visitor
return cookieValue;
}
@Override
protected void rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) {
// 生成cookie token, 保存token和用户id的对应关系。增加对token的管理
String accountId = (String) accountPrincipals.getPrimaryPrincipal();
ByteSource byteSource1 = rng.nextBytes(32);
ByteSource byteSource2 = rng.nextBytes(80);
String series = byteSource1.toBase64();
String token = byteSource2.toBase64();
if (persistentLoginService.create(accountId, series, new Sha512Hash(byteSource2.getBytes()).toBase64())) {
((SimplePrincipalCollection) accountPrincipals).add(token, "series");
rememberToken(subject, MessageFormat.format("{0}:{1}", series, token));
}
}
protected void rememberToken(SubjectContext subjectContext, String token) {
if (!WebUtils.isHttp(subjectContext)) {
String msg = "Subject argument is not an HTTP-aware instance. This is required to obtain a servlet " +
"request and response in order to set the rememberMe cookie. Returning immediately and " +
"ignoring rememberMe operation.";
logger.debug(msg);
return;
}
rememberToken3((RequestPairSource) subjectContext, token);
}
protected void rememberToken(Subject subject, String token) {
if (!WebUtils.isHttp(subject)) {
String msg = "Subject argument is not an HTTP-aware instance. This is required to obtain a servlet " +
"request and response in order to set the rememberMe cookie. Returning immediately and " +
"ignoring rememberMe operation.";
logger.debug(msg);
return;
}
rememberToken3((RequestPairSource) subject, token);
}
protected void rememberToken3(RequestPairSource subject, String token) {
if (!WebUtils.isHttp(subject)) {
String msg = "Subject argument is not an HTTP-aware instance. This is required to obtain a servlet " +
"request and response in order to set the rememberMe cookie. Returning immediately and " +
"ignoring rememberMe operation.";
logger.debug(msg);
return;
}
HttpServletRequest request = WebUtils.getHttpRequest(subject);
HttpServletResponse response = WebUtils.getHttpResponse(subject);
//base 64 encode it and store as a cookie:
Cookie template = getCookie(); //the class attribute is really a template for the outgoing cookies
Cookie cookie = new SimpleCookie(template);
cookie.setValue(token);
cookie.saveTo(request, response);
}
@Override
protected void forgetIdentity(Subject subject) {
//退出之后,被调用; 或者自动登录后,在登录验证时勾选自动登录
logger.debug("forgetIdentity entered");
if (WebUtils.isHttp(subject)) {
forgetIdentity3((RequestPairSource) subject);
}
}
@Override
public void forgetIdentity(SubjectContext subjectContext) {
if (WebUtils.isHttp(subjectContext)) {
forgetIdentity3((RequestPairSource) subjectContext);
}
}
private void forgetIdentity3(RequestPairSource source) {
//退出之后,被调用; 或者自动登录后,在登录验证时勾选自动登录
logger.debug("forgetIdentity entered");
if (WebUtils.isHttp(source)) {
HttpServletRequest request = WebUtils.getHttpRequest(source);
HttpServletResponse response = WebUtils.getHttpResponse(source);
Cookie cookie = getCookie();
String value = cookie.readValue(request, response);
//提取 series
cookie.removeFrom(request, response);
if (value != null) {
int pos = value.indexOf(':');
if (pos > 0 && pos < value.length() - 1) {
String series = value.substring(0, pos);
logger.debug("persistent login, {}, {}", value, series);
persistentLoginService.removeBySeries(series);
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment