天天看點

oauth2 問題 Full authentication is required to access this resource 探索

問題現象

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

oauth2 問題 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浏覽器試試?

oauth2 問題 Full authentication is required to access this resource 探索

結果發現其實是一樣的。

源碼調試

關鍵字是:

WWW-Authenticate: Bearer realm="oauth2-resource", error="unauthorized", error_description="Full authentication is required to access this resource"

通過關鍵字跟蹤源碼調試半天發現是這裡問題: 

oauth2 問題 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)

oauth2 問題 Full authentication is required to access this resource 探索

這裡,預設是 never ,也就是說,就即使登入了, 也不會建立。 這個可怎麼辦啊,

 我想在 我的AuthServerConfig#configure( AuthorizationServerSecurityConfigurer)方法裡面嘗試配置

.and() .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)

結果發現 參數的 AuthorizationServerSecurityConfigurer 還沒有初始化, 沒有設定 HttpSecurity, and 方法無法執行。

試試給我的 WebSecurityConfigurerAdapter 的configure(HttpSecurity http) 方法設定一下

oauth2 問題 Full authentication is required to access this resource 探索

 發現不起作用!

問題解決

發現原因

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, 那麼? 會覆寫嗎?其實是會的,他們有一個順序。