問題現象
啟用oauth2後,正常的oauth2 登入都是沒有問題的,但是我想 form登入呢? 其實也是支援的,不過我開始是沒搞明白,一直出現問題 Full authentication is required to access this resource, 幾天都搞不定,茶不思飯不想...

單獨使用spring security是ok 的,是以感覺是 加了 oauth2 導緻的,說實在話,oauth2的源碼看過,但沒有完全搞懂。後面F12 檢視請求資訊, 發現:
總體資訊:
Request URL: http://192.168.1.103:8081/auth/
Request Method: GET
Status Code: 401
Remote Address: 192.168.1.103:8081
響應頭:
Referrer Policy: strict-origin-when-cross-origin
Access-Control-Allow-Headers: x-requested-with, authorization
Access-Control-Allow-Methods: *
Access-Control-Allow-Origin: *
Access-Control-Max-Age: 3600
Cache-Control: no-store
Content-Type: application/xhtml+xml
Date: Mon, 11 Jul 2022 23:12:46 GMT
Pragma: no-cache
Transfer-Encoding: chunked
WWW-Authenticate: Bearer realm="oauth2-resource", error="unauthorized", error_description="Full authentication is required to access this resource"
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
請求頭:
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh-TW;q=0.9,zh;q=0.8,zh-HK;q=0.7,en;q=0.6
Cache-Control: max-age=0
Connection: keep-alive
Cookie: JSESSIONID=EAFF5C06541EE532098D58B1D5D097A1; JSESSIONID=D06EF1CF20C1D3413BD8DA26D2279A5E
Host: 192.168.1.103:8081
Referer: http://192.168.1.103:8081/auth/myLogin
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
發現響應狀态碼是 401。後面又注意到 響應頭包含有WWW-Authenticate 這個意味着什麼? 檢視了響應體, 竟然沒有内容, why ?
觀察發現請求頭、參數并不包含 Authentication: Bearer xxx token,
Authentication 是不是我之前使用 basic 登入的殘留? 使用ff浏覽器試試?
結果發現其實是一樣的。
源碼調試
關鍵字是:
WWW-Authenticate: Bearer realm="oauth2-resource", error="unauthorized", error_description="Full authentication is required to access this resource"
通過關鍵字跟蹤源碼調試半天發現是這裡問題:
其實裡面是觸發了 AccessDeniedException,
不隻是 http://192.168.1.103:8081/auth/ , 任何url,比如 http://192.168.1.103:8081/auth/123456789 都是 401
unauthorized 表明已經認證, 但是沒有授權吧; 确實如此,登入就是認證,但是權限呢?并沒有發放出來..
對于 resource Server, 裡面的所有通路都被看做是資源, 就通路url 就是通路資源, 是需要授權的。。 ———— 這一點在哪裡有配置呢? OAuth2AuthenticationManager !
—— AuthorizationServer 期望就是每次通路都攜帶一個 Authentication: Bearer xxx token, 但是沒有發現。 這裡的邏輯是 OAuth2AuthenticationProcessingFilter#doFilter
BearerTokenExtractor#extract/#extractToken,首先是從header 裡面擷取,然後是從 request param擷取
最後到達 ExceptionTranslationFilter#handleSpringSecurityException:185 的 sendStartAuthentication 方法, 也就是建立了一個InsufficientAuthenticationException, 然後
最後是:OAuth2AuthenticationEntryPoint ,然後, AbstractOAuth2SecurityExceptionHandler#doHandle , OAuth2AuthenticationEntryPoint#enhanceResponse
我确實已經登入了, 但是卻發現 principal: anonymousUser, sessionId 是正确的 446631399D1901E4CF3E50EF5AB94EBB
isAllowSessionCreation() = false
測試 request.isRequestedSessionIdValid() 結果 true
原因其實是
AuthorizationServerSecurityConfiguration#configure(HttpSecurity)
這裡,預設是 never ,也就是說,就即使登入了, 也不會建立。 這個可怎麼辦啊,
我想在 我的AuthServerConfig#configure( AuthorizationServerSecurityConfigurer)方法裡面嘗試配置
.and() .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
結果發現 參數的 AuthorizationServerSecurityConfigurer 還沒有初始化, 沒有設定 HttpSecurity, and 方法無法執行。
試試給我的 WebSecurityConfigurerAdapter 的configure(HttpSecurity http) 方法設定一下
發現不起作用!
問題解決
發現原因
protected void configure(HttpSecurity http) throws Exception {
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.authorizeRequests()
.anyRequest().authenticated();
}
原因就是配置 初始的authorizeRequests.antMatchers 必須要包括所有的配置受formLogin 保護的資源端點,否則就會走 oauth2 認證;oauth2 會讀取請求頭或請求參數裡面的Authentication: Bearer xxx token,讀取不到就直接 deny, 傳回401.
是以呢,問題解決方案就是受formLogin 保護的資源端點, 不配置且不放行則 401; 當然,oauth2的端點不需要配置在這裡,否則畫蛇添足導緻oauth2登入不正常!
protected void configure(HttpSecurity http) throws Exception {
http
.headers().frameOptions().disable()
.and()
.csrf().disable()
// 配置受formLogin 保護的資源端點, 不配置且不放行則 401; 當然,oauth2的端點不需要配置在這裡,否則畫蛇添足導緻oauth2登入不正常!
.requestMatchers()
.antMatchers("/myLogin","/doLogin", "/oauth/authorize"
, "/protected/**", "/mustLogin/**", "/securedPage*"
, "/myLogout*" , "/logout?logout*" // login?logout 也需要保護起來,否則401 —— 這樣也不行 todo
// 首頁也最好保護起來,否則..
, "/", "/index", "/tourist*", "/a*")// 這裡antMatchers必須要包括/doLogin, 否則永遠都是登入頁面
.and()
.authorizeRequests()
//antMatchers這裡 "/user/me"不能放行,如果放行,則不能擷取到Principal參數 —— 錯錯錯,再次測試發現 這裡 "/user/me"是否放行 都不要緊; 不知道哪裡搞錯了
.antMatchers("/tourist","/myLogin", "/logout?logout*", "/doLogin","/user/me123", "/oauth/authorize")
.permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/myLogin")
// 它的作用是什麼? 僅僅是一個通知作用吧..不對! 測試發現,隻有配置了loginPage,那麼就一定需要配置loginProcessingUrl, 而且需要比對!
.loginProcessingUrl("/doLogin")
.defaultSuccessUrl("/index", false)
.permitAll()
// .and()
// .authorizeRequests()
// .anyRequest().authenticated() // 不能加這行,
// 否則:一直401 <oauth><error_description>Full authentication is required to access this resource</error_description><error>unauthorized</error></oauth>
.and()
.logout()
// 設定logoutUrl之後,再通路/logout會出現401(如果不放行), 或者404
// 測試發現, /myLogout、 /logout 兩個端點都可以登出成功,why? 按理說隻有一個; 測試發現如果antMatchers 發現/logout,則隻有logoutUrl可以登出,而且通路 /logout不會登出,而是404
// 測試發現有時候/myLogout 并沒真正的登出,而是401,why? 原因是logoutUrl需要受保護
// 這裡需要 保護起來, 否則也是 401, Full authentication is required to access this resource
.logoutUrl("/myLogout")
// defaultTarget must start with '/' or with 'http(s)'
.logoutSuccessUrl("/myLogoutSuccessUrl")
.permitAll()
// .logoutSuccessHandler(tigerLogoutSuccessHandler) //url和Handler隻能配置一個
// .deleteCookies("JSESSIONID")//清除cook鍵值
.and()
// 這裡的sessionManagement 并不能影響到AuthorizationServer, 因為..
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
;
}
那為什麼這樣配置之後就ok了呢?因為requestMatchers 必須要包括了某端點才會對她進行認證、校驗,否則就不管它, 就會被其他的HttpSecurity 捕獲到,進而引發意外問題 。
多個 HttpSecurity
其實調試過程中是發現有3個 HttpSecurity,分别對應3個AuthorizationServerConfigurerAdapter,分别來自自定義的MySecurityConfiguration,及 ResourceServerConfiguration、AuthorizationServerSecurityConfiguration 他們各司其職,都有其作用,非常合理。
但是每個 HttpSecurity 可以有自己的一系列的配置, 包括 session管理, 比如 oauth2 就不需要 session, 是以政策是 never 。
如果有多個 HttpSecurity, 那麼? 會覆寫嗎?其實是會的,他們有一個順序。