天天看点

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, 那么? 会覆盖吗?其实是会的,他们有一个顺序。