1. 程式人生 > >springboot1.5.3+redis+shiro1.3.2+springcloud+nginx登入後shiro的subject為null問題解決

springboot1.5.3+redis+shiro1.3.2+springcloud+nginx登入後shiro的subject為null問題解決

一、 問題描述

線上環境:springboot1.5.3+redis3.4-cluster叢集+shiro1.3.2+springcloud+nginx
本地開發:springboot1.5.3+redis3.4-cluster叢集+shiro1.3.2
本地開發時,一切正常。
上線後,登入正常,之後訪問其他功能對shiro許可權認證,產生Subject().isAuthenticated() = false。debug發現根本沒有進入 doGetAuthorizationInfo()。

二、問題分析

redis

1. debug發現線上redis只寫入shiro-session,並沒有許可權cache的寫入。判斷jedis連線redis的問題。
2. 由於專案引入jedis2.9.0.jar,連線時首先採用單節點redis,Redisutils更改為jedisCluster()訪問,shiro-redis也由2.4升級為3.1.0支援redis-cluster(),本地除錯成功後,線上問題依舊。
3. 專案中產品建議採用spring直接操作redis,而不是jedis,甚至放棄本專案架構。理由是別人的專案這樣是成功的。思考良久,認為問題不在這,既然可以寫入和讀取shiro-session,連線不是問題。從解決之後來看,這種堅持是正確的。

shiro

  1. 百度“shiro登入後 isAuthenticated null”,發現其他人也有類似困擾,但沒有給出真正的解決之道,只說後來莫名其妙好了。只好自己動手分析shiro原始碼了,見本部落格的分析文章。
  2. 迫於無奈只好研讀shiro原始碼,還不能debug,因為問題在線上,復現不了。
  3. 網友有說HttpRequestServlet被shiro重新包裝,多執行緒導致獲取不到shiro的subject,我也驗證不了。第六感強烈告訴自己問題不在這,首先shiro專案經過許多考驗,基本的信任還是有的。
  4. 分析shiro原始碼,發現token是cookie讀取的,應該分析cookie是否能用。debug發現也沒問題。

nginx

為了復現問題,儘可能地模擬生產環境,本地開啟nginx,在vue前端和後臺加入nginx作為反向代理。沒問題。

問題似乎走入了死衚衕, 能試的方法都試過了

最後想起還有spring-cloud沒有模擬,麻煩的一比,為了解決問題沒辦法

springCloud

模擬springCloud,主要就是觀察zuul網關了。

問題終於復現了

問題解決

通過分析zuul閘道器日誌,發現zuul篡改了token,當然會導致shiro能登入成功,其後的功能發現沒有許可權,subject.isAuthenticated() = false。
方案一:nginx代理的時候,不再指向zuul,而是直接指向後臺服務地址,zuul的負載均衡功能可通過其他方案替代。
方案二:zuul對請求有過濾模式,在請求前面加入特定的字元,便可繞過。具體請Seach。
方案三:zuul全域性設定:
zuul.sensitive-headers=
或指定路由設定:
zuul.routes..sensitive-headers=
zuul.routes..custom-sensitive-headers=true
因為:

public class PreDecorationFilter extends ZuulFilter {
	...
	public Object run() {
		RequestContext ctx = RequestContext.getCurrentContext();
		final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
		Route route = this.routeLocator.getMatchingRoute(requestURI);
		if (route != null) {
			String location = route.getLocation();
			if (location != null) {
				ctx.put("requestURI", route.getPath());
				ctx.put("proxy", route.getId());
              	 // 處理忽略頭資訊的部分
				if (!route.isCustomSensitiveHeaders()) {
					this.proxyRequestHelper.addIgnoredHeaders(
						this.properties.getSensitiveHeaders()
						.toArray(new String[0]));
				} else {
					this.proxyRequestHelper.addIgnoredHeaders(
						route.getSensitiveHeaders()
						.toArray(new String[0]));
				}
		...
}

/*
從上述原始碼中,可以看到有一段if/else塊,通過呼叫ProxyRequestHelper的addIgnoredHeaders方法來新增需要忽略的資訊到請求上下文中,供後續ROUTE階段的過濾器使用。這裡的if/else塊分別用來處理全域性設定的敏感頭資訊和指定路由設定的敏感頭資訊。
*/

@Data
@ConfigurationProperties("zuul")
public class ZuulProperties {
	private Set<String> sensitiveHeaders = new LinkedHashSet<>(
			Arrays.asList("Cookie", "Set-Cookie", "Authorization"));
	...
}

其實,在另外一個專案中,上傳檔案也被zuul閘道器截取了一部分位元組,導致檔案不完整報錯。網上有解決方案,不在贅述。

總結

本地除錯正常,線上問題多多,從環境入手是沒錯的。
不要輕易懷疑你專案的架構和更換其他技術,產品人員除外。