Java 官方的Spring安全oauth2示例没有';Cookie冲突导致无法工作(授权码机制)
根据教程 我有以下项目结构: 和以下源代码: 社交应用程序。类别:Java 官方的Spring安全oauth2示例没有';Cookie冲突导致无法工作(授权码机制),java,spring-security,oauth-2.0,spring-oauth2,Java,Spring Security,Oauth 2.0,Spring Oauth2,根据教程 我有以下项目结构: 和以下源代码: 社交应用程序。类别: @SpringBootApplication @RestController @EnableOAuth2Client @EnableAuthorizationServer @Order(200) public class SocialApplication extends WebSecurityConfigurerAdapter { @Autowired OAuth2ClientContext oauth2C
@SpringBootApplication
@RestController
@EnableOAuth2Client
@EnableAuthorizationServer
@Order(200)
public class SocialApplication extends WebSecurityConfigurerAdapter {
@Autowired
OAuth2ClientContext oauth2ClientContext;
@RequestMapping({ "/user", "/me" })
public Map<String, String> user(Principal principal) {
Map<String, String> 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<OAuth2ClientContextFilter> oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
FilterRegistrationBean<OAuth2ClientContextFilter> registration = new FilterRegistrationBean<OAuth2ClientContextFilter>();
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<Filter> filters = new ArrayList<>();
filters.add(ssoFilter(facebook(), "/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;
}
}
但当我打开浏览器并尝试点击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.<anonymous> (jquery.js:8598)
发生这种情况是因为带有302状态代码和js回调的/user
响应试图解析localhost:8080的结果:
我不明白为什么会发生这种重定向。你能解释一下这种行为并帮助解决它吗
更新
我从你那里得到了这个密码
重要:
只有在启动客户端应用程序后,它才会复制。
附笔。
如何复制:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>Demo</title>
<meta name="description" content=""/>
<meta name="viewport" content="width=device-width"/>
<base href="/"/>
<link rel="stylesheet" type="text/css"
href="/webjars/bootstrap/css/bootstrap.min.css"/>
<script type="text/javascript" src="/webjars/jquery/jquery.min.js"></script>
<script type="text/javascript"
src="/webjars/bootstrap/js/bootstrap.min.js"></script>
</head>
<body>
<h1>Login</h1>
<div class="container unauthenticated">
With Facebook: <a href="/login/facebook">click here</a>
</div>
<div class="container authenticated" style="display: none">
Logged in as: <span id="user"></span>
<div>
<button onClick="logout()" class="btn btn-primary">Logout</button>
</div>
</div>
<script type="text/javascript"
src="/webjars/js-cookie/js.cookie.js"></script>
<script type="text/javascript">
$.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 i.e. 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;
}
</script>
</body>
</html>
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
要测试新功能,您只需运行应用程序并访问
浏览器中的localhost:9999/客户端。客户端应用程序将重定向到
本地授权服务器,然后为用户提供通常的
选择使用Facebook或Github进行身份验证。一旦是
完全控制返回到测试客户端,本地访问令牌为
已授予,身份验证已完成(您应该看到“Hello”
在浏览器中显示消息)。如果您已经通过Github的身份验证
或者Facebook,您甚至可能没有注意到远程身份验证
答复:
更新日期:2018年5月15日
正如您已经找到的解决方案一样,出现问题的原因是JSESSIONID
被覆盖
更新日期:2018年5月10日
你对第三份赏金的坚持终于得到了回报。我开始深入研究回购协议中两个例子的不同之处
如果查看手册
repo和/user
映射
@RequestMapping("/user")
public Principal user(Principal principal) {
return principal;
}
正如您在此处返回的主体
所示,您可以从同一对象获得更多详细信息。现在,在您从auth server
文件夹运行的代码中
@RequestMapping({ "/user", "/me" })
public Map<String, String> user(Principal principal) {
Map<String, String> map = new LinkedHashMap<>();
map.put("name", principal.getName());
return map;
}
因此,从/user
api返回的json响应预期具有userAuthentication.details.name
,但用户界面不具有该细节。现在如果我在同一个项目中更新了下面的方法
@RequestMapping({"/user", "/me"})
public Map<String, Object> user(Principal principal) {
Map<String, Object> map = new LinkedHashMap<>();
map.put("name", principal.getName());
OAuth2Authentication user = (OAuth2Authentication) principal;
map.put("userAuthentication", new HashMap<String, Object>(){{
put("details", user.getUserAuthentication().getDetails());
}});
return map;
}
您运行的实际代码
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
中的userdetails
不会被收集。这就是你看到错误的原因
因为当您登录用户时,您没有收集其用户详细信息。因此,当您访问详细信息时,它并不存在。因此你会得到一个错误
如果您运行正确的手动
文件夹,它会工作
我在你的帖子中看到两个问题
1-
(index):44 Uncaught TypeError: Cannot read property 'details' of undefined
发生这种情况是因为您可能运行了一个错误的项目(即auth server),该项目有一个bug。回购协议包含其他类似项目,也没有缺陷。如果运行项目手动或github,则不会出现此错误。在这些项目中,javascript代码正确地处理服务器在身份验证后返回的数据
2-
(index):44 Uncaught TypeError: Cannot read property 'details' of undefined
/user
响应302状态码:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>Demo</title>
<meta name="description" content=""/>
<meta name="viewport" content="width=device-width"/>
<base href="/"/>
<link rel="stylesheet" type="text/css"
href="/webjars/bootstrap/css/bootstrap.min.css"/>
<script type="text/javascript" src="/webjars/jquery/jquery.min.js"></script>
<script type="text/javascript"
src="/webjars/bootstrap/js/bootstrap.min.js"></script>
</head>
<body>
<h1>Login</h1>
<div class="container unauthenticated">
With Facebook: <a href="/login/facebook">click here</a>
</div>
<div class="container authenticated" style="display: none">
Logged in as: <span id="user"></span>
<div>
<button onClick="logout()" class="btn btn-primary">Logout</button>
</div>
</div>
<script type="text/javascript"
src="/webjars/js-cookie/js.cookie.js"></script>
<script type="text/javascript">
$.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 i.e. 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;
}
</script>
</body>
</html>
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
为了理解为什么会发生这种情况,让我们看看这个应用程序的安全配置
所有人都可以访问端点“/”
、“/login**”
和“/logout”
。
所有其他端点(包括“/user”
)都需要身份验证,因为您使用了
.anyRequest().authenticated().and().exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/"))
因此,任何未经身份验证的请求都将重定向到身份验证入口点,即,“/”
,请求用户进行身份验证。它不取决于客户端应用程序是否启动。只要请求没有经过身份验证,它就会被重定向到“/”
。这就是弹簧控制器响应状态302的原因。一旦您使用facebook或github进行身份验证,对“/user”
端点的后续请求将以200作为成功响应
和下一步-
(index):44 Uncaught TypeError: Cannot read property 'details' of undefined
应用程序中的端点“/me”
通过@EnableResourceServer
作为安全资源进行保护。由于ResourceServerConfiguration
具有比WebSecurityConfigureAdapter
更高的优先级(默认顺序为3)(默认为100,无论如何,它已在代码中以@Order注释的方式显式排序低于3),因此ResourceServerConfiguration将应用于此端点。这意味着,如果请求未经过身份验证,那么它将不会被重定向到身份验证入口点,而是返回一个响应401。一旦您通过身份验证,它将以200响应成功
希望这能澄清你所有的问题
更新-回答您的问题
您在文章中提供的存储库链接包含许多项目。项目auth server、manual和github都是类似的(提供相同的功能,即通过facebook和github进行身份验证)。只有auth serverprojet中的index.html
有一个bug。如果您纠正了此错误,则需要更换
$("#user").html(data.userAuthentication.details.name);
与
它也会运行良好。这三个项目的输出都是一样的。我终于找到了问题所在。我看到这种行为是因为如果您在本地主机上同时启动两个应用程序,客户端和服务器的cookie会发生冲突
发生这种情况的原因是上下文使用了错误的属性
因此,要修复应用程序,您需要替换:
server:
context-path: /client
与
附笔。
我在github上创建了一个问题:
并提出拉动请求:
P.S.2
最后,我的拉取请求被合并:
您能否创建一个最小的回购协议以进行复制?提供一个fix@Tarun拉尔瓦尼,你可以在这里找到:你有空参加一个聚会吗
server:
servlet:
context-path: /client