Shiro重构:整合token和cookie实现登陆及验证
认证服务开始只支持PC端的cookie认证方式,因业务需要,要对小程序、H5、App等移动端设备进行认证,这里复用认证服务。由于小程序不支持cookie认证方式,采用token认证方式,这里对认证服务进行重构。认证服务主要是有Shiro框架研发,我们简单熟悉下Cookie的认证流程。
Shiro的cookie认证流程我们简单说明下,
- shiro是在其默认的会话管理器DefaultWebSessionManager中获取请求携带过来的cookie。主要的功能是添加、删除SessionId到Cookie、读取Cookie获得SessionId。
用户首先通过用户名口令或手机号+验证码方式,调用认证服务接口进行登陆操作。
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
subject.login(token);//当调用subject的登入方法时,会跳转到认证的方法上
User user = (User) subject.getPrincipal();//获取当前登陆对象
//TODO: 做之后的事情
这里用户登陆成功后,会将通过DefaultWebSessionManager,生成SessionId,并将sid做为key保存在redis缓存中(通过redis做session共享)。并将sid保存到cookie中,返回浏览器。
- 浏览器发起请求,传递cookie,Shiro框架通过DefaultWebSessionManager中的getSessionId方法,获取cookie中的Sid.
public Serializable getSessionId(SessionKey key) {
Serializable id = super.getSessionId(key);
if (id == null && WebUtils.isWeb(key)) {
ServletRequest request = WebUtils.getRequest(key);
ServletResponse response = WebUtils.getResponse(key);
id = this.getSessionId(request, response);
}
return id;
}
获取到sid后,到redis中查询用户信息,如果能获取到则通过,否则进行拦截处理。
一、重写DefaultWebSessionManager方法
用户请求到服务端后,首先判断该请求是否需要鉴权,如果不需要鉴权则直接放行。如果需要鉴权,则进入DefaultWebSessionManager中的getSessionId方法。这里我们重新定义个SessionManager,重写getSessionId方法。
我们定义个CustomerWebSessionManager继承DefaultWebSessionManager,重写getSessionId方法,我们首先从请求头中判断是否存在token,如果存在我们直接获取请求头中的token信息,并将token设置到请求头中request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token)。
public class CustomerWebSessionManager extends DefaultWebSessionManager {
private static final String AUTH_TOKEN = "token";
private static final String DEVICE = "device";
private static final String MOBILE = "mobile";
public CustomerWebSessionManager() {
super();
}
/**
* 重写父类获取sessionID的方法,若请求为APP或者H5则从请求头中取出token
*
* @param request 请求参数
* @param response 响应参数
* @return id
*/
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
if (!(request instanceof HttpServletRequest)) {
log.debug("Current request is not an HttpServletRequest - cannot get session ID. Returning null.");
return null;
}
HttpServletRequest httpRequest = WebUtils.toHttp(request);
if (StringUtils.hasText(httpRequest.getHeader(AUTH_TOKEN))) {
//从header中获取token
String token = httpRequest.getHeader(AUTH_TOKEN);
// 每次读取之后都把当前的token放入response中
HttpServletResponse httpResponse = WebUtils.toHttp(response);
if (StringUtils.hasText(token)) {
httpResponse.setHeader(AUTH_TOKEN, token);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
}
//sessionIdUrlRewritingEnabled的配置为false,不会在url的后面带上sessionID
request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled());
return token;
}
return getReferencedSessionId(request, response);
}
}
如果请求头中的token不存在,则走getReferencedSessionId方法,获取cookie中的信息。
/**
* shiro默认从cookie中获取sessionId
*
* @param request 请求参数
* @param response 响应参数
* @return
*/
private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) {
String id = getSessionIdCookieValue(request, response);
if (id != null) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
} else {
//not in a cookie, or cookie is disabled - try the request URI as a fallback (i.e. due to URL rewriting):
//try the URI path segment parameters first:
id = getUriPathSegmentParamValue(request, ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
if (id == null) {
//not a URI path segment parameter, try the query parameters:
String name = getSessionIdName();
id = request.getParameter(name);
if (id == null) {
//try lowercase:
id = request.getParameter(name.toLowerCase());
}
}
if (id != null) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
ShiroHttpServletRequest.URL_SESSION_ID_SOURCE);
}
}
if (id != null) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
//automatically mark it valid here. If it is invalid, the
//onUnknownSession method below will be invoked and we'll remove the attribute at that time.
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
}
// always set rewrite flag - SHIRO-361
request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled());
return id;
}
这里验证请求头中的token方法重构完成。
/**
* 重写父类获取sessionID的方法,若请求为APP或者H5则从请求头中取出token
*
* @param request 请求参数
* @param response 响应参数
* @return id
*/
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
if (!(request instanceof HttpServletRequest)) {
log.debug("Current request is not an HttpServletRequest - cannot get session ID. Returning null.");
return null;
}
HttpServletRequest httpRequest = WebUtils.toHttp(request);
if (StringUtils.hasText(httpRequest.getHeader(AUTH_TOKEN))) {
//从header中获取token
String token = httpRequest.getHeader(AUTH_TOKEN);
// 每次读取之后都把当前的token放入response中
HttpServletResponse httpResponse = WebUtils.toHttp(response);
if (StringUtils.hasText(token)) {
httpResponse.setHeader(AUTH_TOKEN, token);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);
request.setAttribute

本文详细介绍了如何在Shiro框架中重构认证服务,以支持移动端(小程序、H5、App)的token认证,同时保留对PC端cookie的支持。重点在于重写DefaultWebSessionManager,处理不同设备间的会话管理并确保登录验证的灵活性。


被折叠的 条评论
为什么被折叠?



