官方Spring安全性oauth2示例不起作用(授权代码机制)

根据教程Spring Boot和OAuth2

我有以下项目结构:

在此处输入图像描述

以下源代码:

SocialApplication.class:

@SpringBootApplication @RestController @EnableOAuth2Client @EnableAuthorizationServer @Order(200) public class SocialApplication extends WebSecurityConfigurerAdapter { @Autowired OAuth2ClientContext oauth2ClientContext; @RequestMapping({ "/user", "/me" }) public Map user(Principal principal) { Map map = new LinkedHashMap(); map.put("name", principal.getName()); return map; } @Override protected void configure(HttpSecurity http) throws Exception { // @formatter:off http.antMatcher("/**").authorizeRequests().antMatchers("/", "/login**", "/webjars/**").permitAll().anyRequest() .authenticated().and().exceptionHandling() .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/")).and().logout() .logoutSuccessUrl("/").permitAll().and().csrf() .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and() .addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class); // @formatter:on } @Configuration @EnableResourceServer protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { // @formatter:off http.antMatcher("/me").authorizeRequests().anyRequest().authenticated(); // @formatter:on } } public static void main(String[] args) { SpringApplication.run(SocialApplication.class, args); } @Bean public FilterRegistrationBean oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(filter); registration.setOrder(-100); return registration; } @Bean @ConfigurationProperties("github") public ClientResources github() { return new ClientResources(); } @Bean @ConfigurationProperties("facebook") public ClientResources facebook() { return new ClientResources(); } private Filter ssoFilter() { CompositeFilter filter = new CompositeFilter(); List filters = new ArrayList(); filters.add(ssoFilter(facebook(), "https://stackoverflow.com/login/facebook")); filters.add(ssoFilter(github(), "/login/github")); filter.setFilters(filters); return filter; } private Filter ssoFilter(ClientResources client, String path) { OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter( path); OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext); filter.setRestTemplate(template); UserInfoTokenServices tokenServices = new UserInfoTokenServices( client.getResource().getUserInfoUri(), client.getClient().getClientId()); tokenServices.setRestTemplate(template); filter.setTokenServices(new UserInfoTokenServices( client.getResource().getUserInfoUri(), client.getClient().getClientId())); return filter; } } class ClientResources { @NestedConfigurationProperty private AuthorizationCodeResourceDetails client = new AuthorizationCodeResourceDetails(); @NestedConfigurationProperty private ResourceServerProperties resource = new ResourceServerProperties(); public AuthorizationCodeResourceDetails getClient() { return client; } public ResourceServerProperties getResource() { return resource; } } 

index.html的:

      Demo         

Login

With Facebook: click here
Logged in as:
$.ajaxSetup({ beforeSend: function (xhr, settings) { if (settings.type == 'POST' || settings.type == 'PUT' || settings.type == 'DELETE') { if (!(/^http:.*/.test(settings.url) || /^https:.*/ .test(settings.url))) { // Only send the token to relative URLs ie locally. xhr.setRequestHeader("X-XSRF-TOKEN", Cookies.get('XSRF-TOKEN')); } } } }); $.get("/user", function (data) { $("#user").html(data.userAuthentication.details.name); $(".unauthenticated").hide(); $(".authenticated").show(); }); var logout = function () { $.post("/logout", function () { $("#user").html(''); $(".unauthenticated").show(); $(".authenticated").hide(); }); return true; }

application.yml:

 server: port: 8080 security: oauth2: client: client-id: acme client-secret: acmesecret scope: read,write auto-approve-scopes: '.*' facebook: client: clientId: 233668646673605 clientSecret: 33b17e044ee6a4fa383f46ec6e28ea1d accessTokenUri: https://graph.facebook.com/oauth/access_token userAuthorizationUri: https://www.facebook.com/dialog/oauth tokenName: oauth_token authenticationScheme: query clientAuthenticationScheme: form resource: userInfoUri: https://graph.facebook.com/me github: client: clientId: bd1c0a783ccdd1c9b9e4 clientSecret: 1a9030fbca47a5b2c28e92f19050bb77824b5ad1 accessTokenUri: https://github.com/login/oauth/access_token userAuthorizationUri: https://github.com/login/oauth/authorize clientAuthenticationScheme: form resource: userInfoUri: https://api.github.com/user logging: level: org.springframework.security: DEBUG 

但是当我打开浏览器并尝试点击http://localhost:8080

在浏览器控制台中我看到:

 (index):44 Uncaught TypeError: Cannot read property 'details' of undefined at Object.success ((index):44) at j (jquery.js:3073) at Object.fireWith [as resolveWith] (jquery.js:3185) at x (jquery.js:8251) at XMLHttpRequest. (jquery.js:8598) 

在代码中:

 $.get("/user", function (data) { $("#user").html(data.userAuthentication.details.name); $(".unauthenticated").hide(); $(".authenticated").show(); }); 

这是因为/user响应302状态代码和js回调尝试解析localhost:8080结果localhost:8080

在此处输入图像描述

我不明白为什么会发生这种重定向。 你能解释一下这种行为并帮助修复它吗?

UPDATE

我从https://github.com/spring-guides/tut-spring-boot-oauth2获取此代码

重要:

它只在我启动客户端应用程序后重现

PS

如何重现:

要测试新function,您只需运行这两个应用程序,然后在浏览器中访问localhost:9999 / client。 客户端应用程序将重定向到本地授权服务器,然后通过Facebook或Github为用户提供通常的身份validation选择。 完成后,控制权返回测试客户端,授予本地访问令牌并完成身份validation(您应在浏览器中看到“Hello”消息)。 如果您已经使用Github或Facebook进行了身份validation,则可能甚至没有注意到远程身份validation

更新:2018年5月15日

正如您已经找到解决方案一样,问题发生是因为JSESSIONID被覆盖了

会话ID已被替换

更新:2018年5月10日

那么你对第三次赏金的坚持终于得到了回报。 我开始深入研究回购中两个例子之间的不同之处

如果你看一下manual repo和/user映射

 @RequestMapping("/user") public Principal user(Principal principal) { return principal; } 

正如您所看到的那样,您将在此处返回principal ,您可以从同一对象获得更多详细信息。 现在在您从auth-server文件夹运行的代码中

 @RequestMapping({ "/user", "/me" }) public Map user(Principal principal) { Map map = new LinkedHashMap<>(); map.put("name", principal.getName()); return map; } 

如您所见,您只在/user映射中返回了name ,并且您的UI逻辑在下面运行

 $.get("/user", function(data) { $("#user").html(data.userAuthentication.details.name); $(".unauthenticated").hide(); $(".authenticated").show(); }); 

因此,UI /user api返回的json响应期望具有userAuthentication.details.name ,但没有该详细信息。 现在,如果我在同一个项目中更新了如下所示的方法

 @RequestMapping({"/user", "/me"}) public Map user(Principal principal) { Map map = new LinkedHashMap<>(); map.put("name", principal.getName()); OAuth2Authentication user = (OAuth2Authentication) principal; map.put("userAuthentication", new HashMap(){{ put("details", user.getUserAuthentication().getDetails()); }}); return map; } 

然后检查应用程序,它的工作原理

OAuth成功

原始答案

所以你从repo运行错误项目的问题。 您运行的项目是auth-server ,用于启动您自己的oauth服务器。 您需要运行的项目位于manual文件夹中。

现在,如果你看下面的代码

 OAuth2ClientAuthenticationProcessingFilter facebookFilter = new OAuth2ClientAuthenticationProcessingFilter( "/login/facebook"); OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(facebook(), oauth2ClientContext); facebookFilter.setRestTemplate(facebookTemplate); UserInfoTokenServices tokenServices = new UserInfoTokenServices(facebookResource().getUserInfoUri(), facebook().getClientId()); tokenServices.setRestTemplate(facebookTemplate); facebookFilter.setTokenServices( new UserInfoTokenServices(facebookResource().getUserInfoUri(), facebook().getClientId())); return facebookFilter; 

而你运行的实际代码有

 private Filter ssoFilter(ClientResources client, String path) { OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter( path); OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext); filter.setRestTemplate(template); UserInfoTokenServices tokenServices = new UserInfoTokenServices( client.getResource().getUserInfoUri(), client.getClient().getClientId()); tokenServices.setRestTemplate(template); filter.setTokenServices(tokenServices); return filter; } 

在您当前,来自facebook不会被收集。 这就是你看到错误的原因

错误

因为当您登录用户时,您没有收集其用户详细信息。 因此,当您访问详细信息时,它不在那里。 因此你得到一个错误

如果您运行正确的manual文件夹,它的工作原理

加工

我在你的post中看到两个查询。

一-

 (index):44 Uncaught TypeError: Cannot read property 'details' of undefined 

发生这种情况是因为您可能正在运行一个有错误的错误项目(即auth-server)。 回购包含其他类似项目也没有错误。 如果您运行项目手册github,则不会出现此错误。 在这些项目中,javascript代码正确处理服务器在身份validation后返回的数据。

二-

/user 响应302状态代码:

要了解发生这种情况的原因,请查看此应用程序的安全配置。

所有人都可以访问结束点"/""/login**""/logout" 。 包括"/user"在内的所有其他端点都需要进行身份validation,因为您已经使用过

 .anyRequest().authenticated().and().exceptionHandling() .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/")) 

因此,任何未经过身份validation的请求都将重定向到身份validation入口点,即"/" ,要求用户进行身份validation。 它不取决于您的客户端应用程序是否已启动。 只要请求未经过身份validation,它就会被重定向到"/" 。 这就是弹簧控制器响应状态302的原因。一旦您使用facebookgithub进行了身份validation,对"/user"端点的后续请求将以200响应成功。

和下一个 –

应用程序中的端点"/me"使用@EnableResourceServer作为安全资源进行@EnableResourceServer 。 由于ResourceServerConfiguration具有比WebSecurityConfigurerAdapter更高的优先级(默认排序为3)(默认为100,无论如何它已经在代码中使用@Order注释明确地低于3排序),因此ResourceServerConfiguration将应用于此端点。 这意味着如果请求未经过身份validation,则不会将其重定向到身份validation入口点,而是返回响应401 。 一旦通过身份validation,它将以200响应成功。

希望这能澄清你的所有问题。

更新 – 回答你的问题

您在post中提供的存储库链接包含许多项目。 auth-servermanualgithub项目都很相似(提供相同的function,即使用facebook和github进行身份validation)。 只是auth-server projet中的index.html有一个错误。 如果你纠正了这个被替换的bug

 $("#user").html(data.userAuthentication.details.name); 

 $("#user").html(data.name); 

它也会运行良好。 所有这三个项目都会提供相同的输出。

最后我发现了问题。 我看到这种行为是由于如果你在localhost上启动这两个应用程序,cookie会发生客户端和服务器冲突的事实。

这是因为上下文使用了错误的属性。

因此,要修复应用程序,您需要替换:

 server: context-path: /client 

 server: servlet: context-path: /client 

PS

我在github上创建了一个问题:

https://github.com/spring-guides/tut-spring-boot-oauth2/issues/80

并提出了拉取请求:

https://github.com/spring-guides/tut-spring-boot-oauth2/pull/81