10年 Java程序员,硬核人生!勇往直前,永不退缩!

欢迎围观我的git:https://github.com/R1310328554/spring_security_learn 寻找志同道合的有志于研究技术的朋友,关注本人微信公众号: 觉醒的码农,或Q群 165874185

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

问题现象

启用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框架的命名, 极其的规范、考究!初看非常懵, 其实 看似复杂, 却非常合理! 越看越轻松, 看懂后 恍然大悟、 醍醐灌顶, 拍手称赞, 膜拜得五体投地 !!不过缺点 就是概念太多太细了!!

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

 

posted on 2022-07-12 19:47  CanntBelieve  阅读(13769)  评论(3编辑  收藏  举报