30 votes

L'exemple officiel de sécurité Spring oauth2 ne fonctionne pas en raison du conflit des cookies (mécanisme de code d'autorisation)

Selon le tutoriel de Printemps de Démarrage et OAuth2

J'ai structure de projet suivante:

enter image description here

Et le code source suivant:

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="stackoverflow.com/"/>
    <link rel="stylesheet" type="text/css"
          href="stackoverflow.com/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="stackoverflow.com/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

Mais quand j'ouvre le navigateur et essayez de frapper http://localhost:8080

Dans le navigateur de la console, je vois:

(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)

dans le code:

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

Cela arrive parce qu' /user réponse avec 302 code d'état, et js de rappel essayer d'analyser le résultat de l' localhost:8080:

enter image description here

Je ne comprends pas pourquoi cette redirection se passe. Pouvez-vous expliquer ce comportement et aider à résoudre ce problème?

Mise à JOUR

J'ai pris ce code à partir de https://github.com/spring-guides/tut-spring-boot-oauth2

important:

Il reproduit seulement après que j'ai commencer à l'application client.

P. S.

Comment reproduire:

Pour tester les nouvelles fonctionnalités, il vous suffit d'exécuter deux applications et visite localhost:9999/client dans votre navigateur. L'application client sera redirigé vers le local Serveur d'Autorisation, ce qui permet à l'utilisateur d'habitude le choix de l'authentification avec Facebook ou Github. Une fois que c'est contrôle complet revient au client de test, le jeton d'accès est accordée et l'authentification est terminée (vous devriez voir un "Bonjour" message dans votre navigateur). Si vous êtes déjà authentifié avec Github ou Facebook, vous ne pouvez pas le même avis, l'authentification à distance

RÉPONSE:

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

16voto

Tarun Lalwani Points 75641

Mise À Jour: 15 Mai 2018

Comme vous l'avez déjà trouvé la solution, le problème se produit en raison de la JSESSIONID sera écrasé

Session ID replaced

Mise À Jour Le: 10-May-2018

Votre persistance à la 3e bounty a enfin payé. J'ai commencé à creuser dans ce qui était différent entre les deux exemples que vous avez eu dans le repo

Si vous regardez l' manual pensions et /user cartographie

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

Comme vous pouvez le voir vous retournez l' principal ici, vous obtenez plus de détails à partir du même objet. Maintenant, dans votre code que vous exécutez à partir d' auth-server le dossier

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

Comme vous pouvez le voir, vous ne retourna l' name dans la /user cartographie et votre logique de l'INTERFACE utilisateur exécute ci-dessous

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

Donc, la réponse json retourné à partir de /user api devrait avoir userAuthentication.details.name par l'INTERFACE utilisateur n'a pas que des détails. Maintenant, si j'ai mis à jour la méthode comme ci-dessous dans le même projet

@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;
}

Et puis vérifier l'application, elle fonctionne

OAuth Success

Réponse Originale À Cette Question

Donc, la question que vous exécutez mal de projet de l'opération. Le projet que vous êtes en cours d'exécution est auth-server qui est pour le lancement de votre propre oauth serveur. Le projet que vous devez exécuter est à l'intérieur d' manual le dossier.

Maintenant, si vous regardez le code ci-dessous

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;

Et le code que vous exécuter a

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;
}

Dans votre courant de l' userdetails de la facebook à ne pas avoir recueillies. C'est pourquoi vous obtenez une erreur

Error

Parce que quand vous vous êtes connecté, l'utilisateur, vous n'avez pas de recueillir ses détails de l'utilisateur. Ainsi, lorsque vous accédez aux détails, ce n'est pas là. Et par conséquent, vous obtenez un message d'erreur

Si vous exécutez le corriger manual le dossier, il fonctionne

Working

6voto

vsoni Points 2095

Je vois deux requêtes dans votre post.

UN

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

Ce qui se passait parce que vous étiez peut-être l'exécution d'un projet erroné (c'est à dire auth-serveur) qui a un bug. Les pensions de titres contient d'autres projets similaires aussi sans bug. Si vous exécutez le projet de manuel ou github cette erreur n'apparaîtra pas. Dans ces projets, le code javascript est de gérer correctement les données renvoyées par le serveur après l'authentification.

DEUX

/user réponse avec 302 code d'état:

Pour comprendre pourquoi ce qui se passe permet de voir la configuration de la sécurité de cette application.

La fin des points de "/", "/login**" et "/logout" sont accessibles à tous. Tous les autres points de fin, y compris "/user" nécessite une authentification parce que vous avez utilisé

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

De sorte que toute demande qui n'est pas authentifié sera redirigé vers l'authentification du point d'entrée c'est à dire "/", demandant à l'utilisateur pour l'authentification. Il ne dépend pas de votre application client est démarré ou non. Tant que la demande n'est pas authentifié, il sera redirigé vers "/". Ce pourquoi le printemps est un contrôleur de répondre avec le statut 302. Une fois que vous avez authentifié avec facebook ou github, des demandes ultérieures "/user" point de fin répondra succès avec 200.

ET À LA PROCHAINE

Le point de terminaison "/me" dans votre application est sécurisé comme une ressource sécurisée avec @EnableResourceServer. Depuis ResourceServerConfiguration a une priorité plus élevée (commandé 3 par défaut) que WebSecurityConfigurerAdapter(100 par défaut, de toute façon il a déjà commandé explicitement inférieur à 3 avec @Afin d'annotation dans le code) afin ResourceServerConfiguration s'appliquera à ce point de terminaison. Cela signifie que si la demande n'est pas authentifié, alors il ne sera pas redirigé vers l'authentification du point d'entrée, ce sera plutôt de renvoyer une réponse 401. Une fois que vous êtes authentifié, il va de réponse de succès avec 200.

Espérons que cela permettra de clarifier toutes vos questions.

Mise à JOUR- pour répondre à votre question

Le référentiel lien que vous avez fourni dans votre post contient de nombreux projets. Les projets auth-serveur, manuel et github sont tous semblables (fournir la même fonctionnalité à savoir l'authentification avec facebook et github). Juste l' index.html dans auth-serveur de projet a un bug. Si vous corriger ce bug qui est de remplacer

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

avec

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

il permettra également de s'exécuter correctement. Toutes les trois projets donnera le même résultat.

6voto

gstackoverflow Points 1993

Enfin j'ai trouvé le problème. Je vois ce comportement en raison du fait de cookies affrontent pour le client et le serveur si vous démarrez les applications sur localhost.

Il se produit en raison du fait de l'utilisation incorrecte de la propriété pour le contexte.

Pour fixer application dont vous avez besoin pour remplacer:

server:
  context-path: /client

avec

server:  
  servlet:
    context-path: /client

P. S.

J'ai créé de problème sur github:

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

et faites la demande d'extraction:

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

P. S. 2

Enfin mon pull request a fusionné: https://github.com/spring-guides/tut-spring-boot-oauth2/pull/81

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X