在项目初期使用shiro进行权限管理,后期因为要进行移动端开发,就想试试shiro的无状态登录,在网上找了一些资料发现如果不修改现有框架进行权限管理基本不可能,于是尝试通过修改sessionid,后面发现这个方法行不通,于是通过重写Subject类+缓存完成功能,该方法有什么漏洞还需要在后续开发过程中发现,步骤如下:
- 重写Subject类,通过对登录的subject进行输出发现该类实际是subject类的子类:WebDelegatingSubject,而WebDelegatingSubject是不支持缓存存储,也就是没有实现Serializable接口,因此需要重写该类,代码如下:
import java.io.Serializable; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.Session; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.support.DelegatingSubject; import org.apache.shiro.web.subject.support.WebDelegatingSubject; public class StatelessSubject extends WebDelegatingSubject implements Serializable { public StatelessSubject(PrincipalCollection principals, boolean authenticated, String host, Session session, ServletRequest request, ServletResponse response, SecurityManager securityManager) { super(principals, authenticated, host, session, request, response, securityManager); // TODO Auto-generated constructor stub } public StatelessSubject(WebDelegatingSubject subject) { super(subject.getPrincipals(), subject.isAuthenticated(), subject.getHost(), null, null, null, subject.getSecurityManager()); // TODO Auto-generated constructor stub } public WebDelegatingSubject coverToSuper() { return new WebDelegatingSubject(this.principals, this.authenticated, this.host, null, null, null, this.securityManager); } }
在用户登陆成功之后,加入缓存
currentUser.login(token); EHCacheUtil.addSubjectCache(request.getSession().getId(), new StatelessSubject((WebDelegatingSubject) currentUser));
-
重写StatelessSecurityManager类进行拦截处理,代码如下:
import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.apache.shiro.session.mgt.SessionKey; import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.SubjectContext; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.subject.support.WebDelegatingSubject; import org.apache.shiro.web.util.WebUtils; import com.txy.util.EHCacheUtil; public class StatelessSecurityManager extends DefaultWebSecurityManager { @Override public Subject createSubject(SubjectContext subjectContext) { SessionKey sessionKey = getSessionKey(subjectContext); if (WebUtils.isHttp(sessionKey)) { ServletRequest request = WebUtils.getRequest(sessionKey); ServletResponse response = WebUtils.getResponse(sessionKey); String token = request.getParameter("token"); if (token != null) { try { Subject subjectCache = EHCacheUtil.getSubjectCache(token); if (subjectCache != null) { Subject subject = super.createSubject(subjectContext); WebDelegatingSubject subject2 = (WebDelegatingSubject) subject; return new WebDelegatingSubject(subjectCache.getPrincipals(), subjectCache.isAuthenticated(), subject2.getHost(), subject2.getSession(), request, response, subject2.getSecurityManager()); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return super.createSubject(subjectContext); } }
注意其中的token就是移动端需要传上来的缓存id,其实只需要subject的PrincipalCollection principals, boolean authenticated这两个属性是当前用户的,其他都取当前的subject就ok了,
-
修改配置文件
<bean id="securityManager" class="XXx.XXX.StatelessSecurityManager"> <property name="realm" ref="systemRealm" /> <property name="cacheManager" ref="shiroEhcacheManager" /> </bean>
测试代码如下:
String name = request.getParameter("name");
String password = request.getParameter("password");
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(name, ShiroUtil.encriptPwd(password));
if (currentUser.isAuthenticated()) {
System.out.println("已登录");
} else {
currentUser.login(token);
EHCacheUtil.addSubjectCache(request.getSession().getId(),
new StatelessSubject((WebDelegatingSubject) currentUser));
System.out.println("登录中");
}
测试结果如下:
第一次使用postman登录输出:登录中,并获取sessionId为token;
第二次使用javahttp登录,参数为第一次登录的token,输出:已登录
两次请求的sessionId不同但是返回的subject关键信息是一致的。
初步测试权限部分使用正常。