shiro複用session實現前後端分離鑑權

承接上文 Shiro實現session和無狀態token認證共存
項目在爲前後端分離部分接口時複用shiro鑑權,由於項目的token生成沒有符合服務器無關性,所以沒有采用了將sessionid賦值給token參數,從而實現api的shiro鑑權,這樣做更快捷。

建一個過濾器攔截api請求(認證失敗時 返回json)

重寫 onAccessDenied
認證失敗時 返回json格式的錯誤碼而不是跳轉

/**
 * api驗證
 * @author bbq
 * @version 2020-01-02
 */
@Service
public class ApiAuthenticationFilter extends org.apache.shiro.web.filter.authc.FormAuthenticationFilter {
	/**
		這裏爲自己的一套重寫
 	*/

	@Override
	protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
		if (this.isLoginRequest(request, response)) {
			if (this.isLoginSubmission(request, response)) {
				if (log.isTraceEnabled()) {
					log.trace("Login submission detected.  Attempting to execute login.");
				}

				return this.executeLogin(request, response);
			} else {
				if (log.isTraceEnabled()) {
					log.trace("Login page view.");
				}

				return true;
			}
		} else {
			if (log.isTraceEnabled()) {
				log.trace("Attempting to access a path which requires authentication.  Forwarding to the Authentication url [" + this.getLoginUrl() + "]");
			}
			ResultDTO retDto = null;
			ErrorType et = ErrorType.getOperationType("ERROR_INT_TOKEN_INVALID");
			retDto = new ResultDTO(et.getResourceKey(), et.getType(), null);
			response.getWriter().write(GsonUtils.toJson(retDto));
			return false;
		}
	}

}


shiro.xml
攔截 api 接口 用 ApiAuthenticationFilter 過濾器

<bean id="statelessAuthenticationFilter" class="路徑.Stateless.StatelessAuthenticationFilter"></bean>

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
	<property name="securityManager" ref="securityManager" />
	<property name="filters">
           <map>
               <entry key="authc" value-ref="formAuthenticationFilter"/>
				<entry key="apifilter" value-ref="apiAuthenticationFilter"/>
           </map>
       </property>
	<property name="filterChainDefinitions">
		<ref bean="shiroFilterChainDefinitions"/>
	</property>
</bean>

<bean id="apiAuthenticationFilter" class="路徑.ApiAuthenticationFilter">
</bean>

<bean name="shiroFilterChainDefinitions" class="java.lang.String">
	<constructor-arg>
		<value>
			${BasePath}/**/api/** = apifilter
		</value>
	</constructor-arg>
</bean>

重寫SimpleMappingExceptionResolver 授權失敗時 返回json格式

重寫springmvc的SimpleMappingExceptionResolver 授權失敗時不是跳轉視圖 而是返回json


/**
 * @author bbq
 * @version 2020-01-02
 */
package com.thinkgem.jeesite.modules.sys.security;

import com.thinkgem.jeesite.common.bean.ResultDTO;
import com.thinkgem.jeesite.common.enums.ErrorType;
import com.thinkgem.jeesite.common.utils.StringUtils;
import com.thinkgem.jeesite.common.utils.baiduface.GsonUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class ApiMappingExceptionResolver extends SimpleMappingExceptionResolver {
    @Override
    protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        String token = request.getParameter("token");
        if(StringUtils.isNotBlank(token)) {
            ResultDTO retDto = null;
            if(ex instanceof org.apache.shiro.authz.UnauthorizedException){
                retDto = new ResultDTO("403", "操作權限不足", null);
            } else {
                retDto = new ResultDTO("500", "系統內部錯誤", null);
            }
            try {
                response.getWriter().write(GsonUtils.toJson(retDto));
            } catch (IOException e) {
                e.printStackTrace();
            }
            return new ModelAndView();
        }
        return super.doResolveException(request, response, handler, ex);
    }

springmvc配置如下 如果不重寫的話默認爲跳轉視圖
spring-mvc.xml

	<bean class="com.thinkgem.jeesite.modules.sys.security.ApiMappingExceptionResolver">
	<property name="exceptionMappings">
		<props>
			<prop key="org.apache.shiro.authz.UnauthorizedException">error/403</prop>
			<prop key="java.lang.Throwable">error/500</prop>
		</props>
		</property>
</bean>

注意:
看一下4.0.8版本的 springmvc 的
org.springframework.web.servlet.DispatcherServlet
源碼
上面返回爲空的ModelAndView對象纔會return null;
網上的教程都是發送完json 後 return null
這樣會被捕獲異常 導致respon json失敗

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    ModelAndView exMv = null;
    Iterator var6 = this.handlerExceptionResolvers.iterator();

    while(var6.hasNext()) {
        HandlerExceptionResolver handlerExceptionResolver = (HandlerExceptionResolver)var6.next();
        exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
        if (exMv != null) {
            break;
        }
    }

    if (exMv != null) {
        if (exMv.isEmpty()) {
            return null;
        } else {
            if (!exMv.hasView()) {
                exMv.setViewName(this.getDefaultViewName(request));
            }

            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
            }

            WebUtils.exposeErrorRequestAttributes(request, ex, this.getServletName());
            return exMv;
        }
    } else {
        throw ex;
    }
}

重寫DefaultWebSessionManager通過request獲取sessionid

由於項目之前參數叫token,不影響舊api,只要登陸接口從生產token改造成獲取sessionid賦值給token就行。

/**
 * 自定義WEB會話管理類
 */
public class SessionManager extends org.apache.shiro.web.session.mgt.DefaultWebSessionManager {

	public SessionManager() {
		super();
	}
	
	@Override
	protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
		// 如果參數中包含“__sid”參數,則使用此sid會話。 例如:http://localhost/project?__sid=xxx&__cookie=true
		String sid = request.getParameter("token");
		if (StringUtils.isNotBlank(sid)) {
			// 是否將sid保存到cookie,瀏覽器模式下使用此參數。
			if (WebUtils.isTrue(request, "__cookie")){
		        HttpServletRequest rq = (HttpServletRequest)request;
		        HttpServletResponse rs = (HttpServletResponse)response;
				Cookie template = getSessionIdCookie();
		        Cookie cookie = new SimpleCookie(template);
				cookie.setValue(sid); cookie.saveTo(rq, rs);
			}
			// 設置當前session狀態
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
                    ShiroHttpServletRequest.URL_SESSION_ID_SOURCE); // session來源與url
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sid);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
        	return sid;
		}else{
			return super.getSessionId(request, response);
		}
	}
	
}

//重新改造你的api登陸和登出接口即可

最後

session與jwt的不同:session認證是保險箱在服務器,密碼在用戶手中,用戶把密碼送到服務器解開自己的保險箱,而jwt則是保險箱放在用戶手中,服務器什麼都不放,當用戶把保險箱送來,服務器摸一摸保險箱,敲打敲打,認爲保險箱是自己家生產的就打開它。
這樣當服務器開分號時,採用session方式就只能幫用戶解鎖在自己分號的保險箱,用戶如果讓a分號打開存在b分號的保險箱,就得順豐快遞從a送到b送過來。而jwt方式每一家分號都能打開任意用戶的保險箱。

由於項目session加入了redis緩存,也可作爲分佈式使用(開啓順豐服務),而 token的生成只是加入一段隨機串加密保存在redis中(分號老闆認不出自家的保險箱,而是把保險箱的生產編碼記在賬本上,每家分號只記錄自己家賣出去的保險箱),這樣就離不開與服務器進行交互,所以這裏的token本質和sessionid一樣,並沒有實現服務器無關性,所以最後沒有采用多realm認證,而是將sessionid賦予token參數,api請求也複用session認證的方式。

如果你想增加一套jwt的shiro認證
看另一篇文
Shiro實現session和無狀態token認證共存

注意:項目之前處理跨域問題是在後端攔截器裏 而shiro是基於過濾器 優先級高於攔截器 所以改造後得在respon json中處理跨域 又或者統一把跨域丟給過濾器處理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章