web-dev-qa-db-ja.com

公式のSpringセキュリティoauth2の例は、Cookieがクラッシュするため機能しません(認証コードメカニズム)

チュートリアルによると Spring Boot and OAuth2

私は次のプロジェクト構造を持っています:

enter image description here

そして、次のソースコード:

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の結果を解析しようとするために発生します。

enter image description here

このリダイレクトが発生する理由がわかりません。この動作を説明し、修正するのに役立ちますか?

更新

このコードは https://github.com/spring-guides/tut-spring-boot-oauth2 から取得しました

重要:

クライアントアプリケーションを起動した後にのみを再現します。

追伸.

再現方法:

新機能をテストするには、両方のアプリを実行して、ブラウザーでlocalhost:9999/clientにアクセスするだけです。クライアントアプリはローカル認証サーバーにリダイレクトし、ユーザーはFacebookまたはGithubによる認証の通常の選択を行います。それが完了すると、テストクライアントに制御が戻り、ローカルアクセストークンが許可され、認証が完了します(ブラウザに「Hello」メッセージが表示されます)。 GithubまたはFacebookで既に認証されている場合、リモート認証に気付かないこともあります

回答:

https://stackoverflow.com/a/50349078/26743

29
gstackoverflow

最後に問題を発見しました。 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

P.S.2

最後に、プルリクエストがマージされました: https://github.com/spring-guides/tut-spring-boot-oauth2/pull/81

6
gstackoverflow

更新:2018年5月15日

すでに解決策を見つけているように、JSESSIONIDが上書きされるために問題が発生します

Session ID replaced

更新: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;
}

そして、アプリケーションを確認してください、それは動作します

OAuth Success

元の回答

そのため、リポジトリから間違ったプロジェクトを実行しているという問題。実行しているプロジェクトは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は収集されません。それがエラーが表示される理由です

Error

ユーザーをログインしたときに、ユーザーの詳細を収集しなかったためです。そのため、詳細にアクセスすると、そこにはありません。したがって、エラーが発生します

正しいmanualフォルダーを実行すると、動作します

Working

15
Tarun Lalwani

投稿に2つのクエリがあります。

ONE-

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

これは、おそらくバグがある間違ったプロジェクト(つまり、auth-server)を実行していたために発生していました。リポジトリには、バグのない他の同様のプロジェクトも含まれています。プロジェクトを実行する場合manualまたはgithubこのエラーは表示されません。これらのプロジェクトでは、JavaScriptコードは認証後にサーバーから返されるデータを正しく処理しています。

TWO-

/user2ステータスコードの応答:

これが起こっている理由を理解するために、このアプリケーションのセキュリティ構成を見てみましょう。

エンドポイント"/""/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-servermanual、およびgithubはすべて似ています(同じ機能、つまりfacebookとgithubによる認証を提供します)。 auth-server projetのindex.htmlにはバグが1つあります。交換するこのバグを修正する場合

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

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

また、正常に実行されます。 3つのプロジェクトはすべて同じ出力を提供します。

6
vsoni