• oauth2 问题 Full authentication is required to access this resource 探索


    问题现象

    启用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 {

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

    整个过程下来,人其实很累,不过很有收获。我发现就是spring框架的命名, 极其的规范、考究!初看非常懵, 其实 看似复杂, 却非常合理! 越看越轻松, 看懂后 恍然大悟、 醍醐灌顶, 拍手称赞, 膜拜得五体投地 !!不过缺点 就是概念太多太细了!!

    而且代码量那么大, 如不学会跳读, 那么肯定坚持不下去的, 肯定看不完,容易失去耐心。

  • 相关阅读:
    Linux Socket函数close() 与 shutdown()区别
    Android Performance Patterns S01E03
    Android Performance Patterns S01E02
    Android Performance Patterns S01E01
    Java类初始化顺序
    原子性,可见性,有序性
    UML类图
    Linux 五种IO模型
    Linux学习笔记(一)
    线程的生命周期-java
  • 原文地址:https://www.cnblogs.com/FlyAway2013/p/16471446.html
Copyright © 2020-2023  润新知