チュートリアルによると Spring Boot and OAuth2
私は次のプロジェクト構造を持っています:
そして、次のソースコード:
SocialApplication.class:
@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;
}
}
index.html:
<!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>
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.<anonymous> (jquery.js:8598)
コード内:
$.get("/user", function (data) {
$("#user").html(data.userAuthentication.details.name);
$(".unauthenticated").hide();
$(".authenticated").show();
});
302ステータスコードとjsコールバックを含む/user
応答がlocalhost:8080
の結果を解析しようとするために発生します。
このリダイレクトが発生する理由がわかりません。この動作を説明し、修正するのに役立ちますか?
このコードは https://github.com/spring-guides/tut-spring-boot-oauth2 から取得しました
クライアントアプリケーションを起動した後にのみを再現します。
再現方法:
新機能をテストするには、両方のアプリを実行して、ブラウザーでlocalhost:9999/clientにアクセスするだけです。クライアントアプリはローカル認証サーバーにリダイレクトし、ユーザーはFacebookまたはGithubによる認証の通常の選択を行います。それが完了すると、テストクライアントに制御が戻り、ローカルアクセストークンが許可され、認証が完了します(ブラウザに「Hello」メッセージが表示されます)。 GithubまたはFacebookで既に認証されている場合、リモート認証に気付かないこともあります
最後に問題を発見しました。 localhostで両方のアプリケーションを起動すると、クライアントとサーバーでCookieが衝突するため、この動作が見られます。
コンテキストのプロパティが間違って使用されているために発生します。
したがって、アプリケーションを修正するには、次のものを交換する必要があります。
server:
context-path: /client
と
server:
servlet:
context-path: /client
私はgithubで問題を作成しました:
https://github.com/spring-guides/tut-spring-boot-oauth2/issues/8
そしてプルリクエストを行いました:
https://github.com/spring-guides/tut-spring-boot-oauth2/pull/81
最後に、プルリクエストがマージされました: https://github.com/spring-guides/tut-spring-boot-oauth2/pull/81
更新:2018年5月15日
すでに解決策を見つけているように、JSESSIONID
が上書きされるために問題が発生します
更新:2018年5月10日
さて、3番目の賞金でのあなたの粘り強さはついに報われました。私はあなたがレポで持っていた2つの例の違いを掘り始めました
manual
リポジトリと/user
マッピングを見ると
@RequestMapping("/user")
public Principal user(Principal principal) {
return principal;
}
ここで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
マッピングでname
のみを返し、UIロジックは以下で実行されます
$.get("/user", function(data) {
$("#user").html(data.userAuthentication.details.name);
$(".unauthenticated").hide();
$(".authenticated").show();
});
そのため、UIによって/user
apiから返されるjson応答には、UIによって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;
}
そして、アプリケーションを確認してください、それは動作します
元の回答
そのため、リポジトリから間違ったプロジェクトを実行しているという問題。実行しているプロジェクトは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;
}
現在のuserdetails
からのfacebook
は収集されません。それがエラーが表示される理由です
ユーザーをログインしたときに、ユーザーの詳細を収集しなかったためです。そのため、詳細にアクセスすると、そこにはありません。したがって、エラーが発生します
正しいmanual
フォルダーを実行すると、動作します
投稿に2つのクエリがあります。
ONE-
(index):44 Uncaught TypeError: Cannot read property 'details' of undefined
これは、おそらくバグがある間違ったプロジェクト(つまり、auth-server)を実行していたために発生していました。リポジトリには、バグのない他の同様のプロジェクトも含まれています。プロジェクトを実行する場合manualまたはgithubこのエラーは表示されません。これらのプロジェクトでは、JavaScriptコードは認証後にサーバーから返されるデータを正しく処理しています。
TWO-
/user
2ステータスコードの応答:
これが起こっている理由を理解するために、このアプリケーションのセキュリティ構成を見てみましょう。
エンドポイント"/"
、"/login**"
、および"/logout"
はすべてにアクセス可能です。 "/user"
を含む他のすべてのエンドポイントでは、
.anyRequest().authenticated().and().exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/"))
したがって、認証されていないリクエストは認証エントリポイント、つまり"/"
にリダイレクトされ、ユーザーに認証を求めます。クライアントアプリケーションが起動しているかどうかに依存しません。要求が認証されない限り、"/"
にリダイレクトされます。これが、スプリングコントローラーがステータス302で応答している理由です。facebookまたはgithubのいずれかで認証されると、"/user"
エンドポイントへの後続のリクエストは成功で応答します。 200。
および次へ-
アプリケーションのエンドポイント"/me"
は、@EnableResourceServer
で保護されたリソースとして保護されます。 ResourceServerConfiguration
は、WebSecurityConfigurerAdapter
(デフォルトでは100、コードの@Orderアノテーションで明示的に3未満に既に順序付けられている)よりも優先順位が高いため(デフォルトでは3の順序)、ResourceServerConfigurationがこれに適用されます終点。つまり、要求が認証されていない場合、それはではない認証エントリポイントにリダイレクトされ、むしろ応答401を返します。認証されると、成功で200が返されます。
これですべての質問が明確になることを願っています。
PDATE-質問に答える
投稿で提供したリポジトリリンクには、多くのプロジェクトが含まれています。プロジェクトauth-server、manual、およびgithubはすべて似ています(同じ機能、つまりfacebookとgithubによる認証を提供します)。 auth-server projetのindex.html
にはバグが1つあります。交換するこのバグを修正する場合
$("#user").html(data.userAuthentication.details.name);
と
$("#user").html(data.name);
また、正常に実行されます。 3つのプロジェクトはすべて同じ出力を提供します。