在shiro使用过程中,保持客户端的状态是通过两种方式关联。一种方式是通过cookie返回,一种是通过重定向回写url。但是重定向解决不了ajax请求。 如果客户端禁用了cookie,会导致shiro无法获得seesion保存的认证,授权信息。导致shiro无法使用。
解决思路:在客户端与服务端进行交互之前,普通请求通过重定向会写url带上会话信息。ajax请求通过写消息头形式,返回特定错误让客户端获取相应头得到seesionId。如果请求后台没有用到seesion,可以不用先获取会话,否则所有的请求都必须先获得会话。
客户端获取到消息头后,保存会话id,每次请求在通过header带上会话信息(一定要带上,特别是重定向的请求,必须从url获取到会话id)。
也可以不需要先获取认证信息,如果使用cookie形式的方式,是不需要先获的会话。服务端使用了会话,自动写cookie到客户端。但是禁用了cookie,服务端产生了会话,相应得到了正确处理,然后在响应头里写消息头,客户端每次请求都从消息头里获取会话id,当然这样实现也是可以的。
代码如下,兼容cookie模式。
public class OssWebSessionManager extends DefaultWebSessionManager implements WebSessionManager {
private static final Logger log = LoggerFactory.getLogger(OssWebSessionManager.class);
private boolean sessionIdHeaderEnabled;
public static final String HEADER_SESSION_NAME = "X-AUTH-SESSION";
public static final String HEADER_SESSION_ID_SOURCE = "header";
public OssWebSessionManager() {
super();
//重定向无法手动带上header
super.setSessionIdUrlRewritingEnabled(true);
sessionIdHeaderEnabled=true;
}
@Override
protected Session createExposedSession(Session session,SessionContext context) {
return super.createExposedSession(session, context);
}
@Override
protected Session createExposedSession(Session session, SessionKey key) {
return super.createExposedSession(session, key);
}
@Override
protected void onStart(Session session, SessionContext context) {
super.onStart(session, context);
HttpServletRequest request = WebUtils.getHttpRequest(context);
HttpServletResponse response = WebUtils.getHttpResponse(context);
//用header存储会话sessionId
if (sessionIdHeaderEnabled) {
Serializable sessionId = session.getId();
storeSessionId(sessionId, request, response);
}
}
@Override
public Serializable getSessionId(SessionKey key) {
Serializable id = super.getSessionId(key);
if(null != id){
return id;
}
if (WebUtils.isWeb(key)) {
ServletRequest request = WebUtils.getRequest(key);
ServletResponse response = WebUtils.getResponse(key);
id = getHeaderSessionId(request, response);
}
return id;
}
protected Serializable getHeaderSessionId(ServletRequest request, ServletResponse response) {
String id=getHeaderSessionId((HttpServletRequest)request);
if (id != null) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,OssWebSessionManager.HEADER_SESSION_ID_SOURCE);
}
if (id != null) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
}
request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled());
return id;
}
@Override
protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) {
super.onInvalidation(s, ese, key);
onInvalidation(key);
}
@Override
protected void onInvalidation(Session session, InvalidSessionException ise, SessionKey key) {
super.onInvalidation(session, ise, key);
onInvalidation(key);
}
@Override
protected void onStop(Session session, SessionKey key) {
super.onStop(session, key);
onInvalidation(key);
}
@Override
public boolean isServletContainerSessions() {
return false;
}
private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) {
if (currentId == null) {
String msg = "sessionId cannot be null when persisting for subsequent requests.";
throw new IllegalArgumentException(msg);
}
response.setHeader(OssWebSessionManager.HEADER_SESSION_NAME,currentId.toString());
log.trace("Set session ID header for session with id {}", currentId.toString());
}
/**
* 获取请求的token
*/
private String getHeaderSessionId(HttpServletRequest httpRequest){
String sessionId=null;
//从header中获取session
sessionId = httpRequest.getHeader(OssWebSessionManager.HEADER_SESSION_NAME);
//如果header中不存在token,则从参数中获取token
if(!StringUtils.hasText(sessionId)){
sessionId = httpRequest.getParameter(OssWebSessionManager.HEADER_SESSION_NAME);
}
return sessionId;
}
private void onInvalidation(SessionKey key) {
if (WebUtils.isWeb(key)) {
ServletResponse response = WebUtils.getResponse(key);
if(null != response){
((HttpServletResponse)response).addHeader(OssWebSessionManager.HEADER_SESSION_NAME, "");
}
}
}
public boolean isSessionIdHeaderEnabled() {
return sessionIdHeaderEnabled;
}
public void setSessionIdHeaderEnabled(boolean sessionIdHeaderEnabled) {
this.sessionIdHeaderEnabled = sessionIdHeaderEnabled;
}
}
需要认证的请求
public class OssAuthenticationFilter extends AuthenticationFilter {
private static final Logger log = LoggerFactory.getLogger(OssAuthenticationFilter.class);
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
Subject subject=SecurityUtils.getSubject();
if(isRelogin( request, response, subject)){
return false;
}
return subject.isAuthenticated();
}
protected boolean isRelogin(ServletRequest request, ServletResponse response,Subject subject) throws IOException{
Session session=subject.getSession(false);
if(null == session){
if(isAjax(request)){
//无法手动带上sessionId,从消息头获取sessionId
session=subject.getSession();
response.getWriter().write(JSON.toJSONString(ResultBuilder.genExpResult(new AppBizException(AppExcCodesEnum.SESSION_TIMEOUT))));
}else{
request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, true);
this.saveRequestAndRedirectToLogin(request, response);
}
return true;
}
return false;
}
protected boolean isAjax(ServletRequest request){
String header = ((HttpServletRequest) request).getHeader("X-Requested-With");
if("XMLHttpRequest".equalsIgnoreCase(header)){
return Boolean.TRUE;
}
return Boolean.FALSE;
}
}
不需要认证,只需要有会话的请求public class OssNoAuthenticationFilter extends OssAuthenticationFilter {
private static final Logger log = LoggerFactory.getLogger(OssNoAuthenticationFilter.class);
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
Subject subject=SecurityUtils.getSubject();
if(isRelogin( request, response, subject)){
return false;
}
return true;
}
}
shiro过滤器配置
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- Shiro的核心安全接口,这个属性是必须的 -->
<property name="securityManager" ref="securityManager"/>
<!-- 要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录apos.htmlhtml"页面 -->
<property name="loginUrl" value="${shiro.loginUrl}"/>
<!-- 登录成功后要跳转的连接 -->
<property name="successUrl" value="/sys/manager/index"/>
<!-- 用户访问未对其授权的资源时,所显示的连接 -->
<!-- 若想更明显的测试此属性可以修改它的值,如unauthor.jsp-->
<property name="unauthorizedUrl" value="/sys/manager/login"/>
<property name="filters">
<map>
<entry key="authc">
<bean class="tc.oss.web.shiro.OssAuthenticationFilter" />
</entry>
<entry key="noAuthc">
<bean class="tc.oss.web.shiro.OssNoAuthenticationFilter" />
</entry>
<entry key="validCode">
<bean class="tc.oss.web.shiro.ValidCodeAuthenticationFilter">
<property name="validCodeUrl" value="/sys/manager/validCode" />
</bean>
</entry>
<!-- <entry key="ssl">
<ref bean="sslFilter" />
</entry> -->
</map>
</property>
<property name="filterChainDefinitions">
<value>
/statics/**=anon
/js/**=anon
/page/**=anon
/sys/manager/login=noAuthc
/sys/manager/captcha=noAuthc
/favicon.ico=anon
/sys/manager/validCode=authc
/sys/manager/commitCode=authc
/**=authc
</value>
</property>
</bean>
这样客户端禁用cookie也能用了,请求必须带上header。从url或者响应中获取
url回调的
function refreshCode(){
var sid=window.location.href.split(";")[1].split("=")[1];
$.ajax({
type: "GET",
url: "${contextPath}/sys/manager/captcha?t=" + $.now(),
beforeSend: function (XMLHttpRequest) {
var sessionid = "X-AUTH-SESSION";
XMLHttpRequest.setRequestHeader(sessionid, sid);
},
success: function(r){
$("#captchaImg").attr("src",r);
}
});
}
ajax异步请求的没测试了,应该是没问题的了