问题现象
启用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框架的命名, 极其的规范、考究!初看非常懵, 其实 看似复杂, 却非常合理! 越看越轻松, 看懂后 恍然大悟、 醍醐灌顶, 拍手称赞, 膜拜得五体投地 !!不过缺点 就是概念太多太细了!!
而且代码量那么大, 如不学会跳读, 那么肯定坚持不下去的, 肯定看不完,容易失去耐心。