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,则服务器认为该浏览器未登录,用户将会变为未登录状态。

1561617653644

推荐博客:

一、Spring Boot集成无状态Shiro--内容详细介绍

二、Shiro学习(20)无状态Web应用集成

Last updated