Shiro无状态的探索
[TOC]
Shiro如何做到无状态
一、介绍
1、为什么要做无状态
前后端分离带来的优越性,使得更多的企业选择它。分离后的两者,交互只有后端接口返回Json数据(或其他类型数据),前端页面的跳转等无需经过后端,故传统的Session(通过在浏览器中的JSESSIONID,对应服务内存中的存储的Session数据来完成Session的功能)不在存在,所以后端需要和前端约定认证方式,而Token不失为一种解决方案。
二、使用
声明:以下基于项目 SpringCloud项目 Lemon 完成设计,直接看效果分析,请下载项目,确保sql执行,数据库密码Redis密码修改好(目前已改为配置中心,请按照项目内,开发文档进行使用),启动lemon-user子应用即可。
1、正常配置Shiro(参考网上教程即可)
2、无状态Shiro
A、拦截器
import com.alibaba.fastjson.JSONObject;
import com.lemon.shiro.token.StatelessToken;
import com.lemon.utils.CookieUtils;
import com.lemon.web.constant.base.ConstantApi;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* 无状态拦截器
*
* @author sjp
* @date 2019/4/30
**/
public class StatelessAuthcFilter extends AccessControlFilter {
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 判断请求是否需要被拦截,拦截后执行onAccessDenied方法
*
* @param request req
* @param response res
* @param mappedValue map
* @return true 不拦截 false 拦截
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
Cookie[] cookies = ((HttpServletRequest) request).getCookies();
// 用户唯一id
String uidStr = CookieUtils.getParamFromCookie(cookies, ConstantApi.UID);
if (StringUtils.isEmpty(uidStr)) {
onLoginFail(response);
return false;
}
Long uid = Long.parseLong(uidStr);
// 用户token
String token = CookieUtils.getParamFromCookie(cookies, ConstantApi.TOKEN);
if (StringUtils.isEmpty(token)) {
onLoginFail(response);
return false;
}
// 用户所在平台
String sid = CookieUtils.getParamFromCookie(cookies, ConstantApi.SID);
if (StringUtils.isEmpty(sid)) {
onLoginFail(response);
return false;
}
// 客户端请求的参数列表
Map<String, String[]> params = new HashMap<>(request.getParameterMap());
StatelessToken statelessToken = new StatelessToken(uid, token, params);
try {
// 委托给Realm进行登录
getSubject(request, response).login(statelessToken);
} catch (Exception e) {
logger.info("auth error->uid:{},sid:{},token:{},e:{}", uid, sid, token, e);
// 登录失败
onLoginFail(response);
return false;
}
return true;
}
/**
* 登录失败时默认返回401状态码
*/
private void onLoginFail(ServletResponse response) throws IOException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(HttpServletResponse.SC_OK);
httpResponse.setContentType("application/json;charset=UTF-8");
httpResponse.getWriter().write("error");
}
}B、登录认证器和无状态Token认证器
C、配置类
D、工具类
ConstantApi
cookieUtils
RedissonTools
有了以上的配置,我们就可以正常测试了,大家都知道Shiro的认证器可以多个,策略可以配置成:只要有一个通过即可。这样当用户首次登录的时候,我们希望他通过LoginRelam认证通过,在登录成功后,拿着服务器给的Token请求其他接口。StatelessRealm认证器就会通过认证放行。
为了确保用户的Token可以识别出用户身份,所以加密的时候选择将用户的UID加密进去,待下次请求使用同样的参数进行Md5对比,来确保用户信息安全。
三、心得
1、传统的Session如何做到绑定状态的?
通过JSESSIONID绑定当前登陆用户,如下图所示,浏览器的JsessionId对应着服务器内存中的某一个Session,而服务器的Session保存了代码中设置的信息,常用的 session.setAttribute("userInfo",userInfo) 设置用户的信息到Session中。如果清除掉JSESSIONID,则服务器认为该浏览器未登录,用户将会变为未登录状态。

推荐博客:
Last updated