Extracting Principal and Authorities using Spring Security OAuth – 使用Spring Security OAuth提取委托人和授权人

最后修改: 2018年 8月 10日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll illustrate how to create an application that delegates user authentication to a third party, as well as to a custom authorization server, using Spring Boot and Spring Security OAuth.

在本教程中,我们将说明如何使用Spring Boot和Spring Security OAuth创建一个将用户认证委托给第三方以及自定义授权服务器的应用程序。

Also, we’ll demonstrate how to extract both Principal and Authorities using Spring’s PrincipalExtractor and AuthoritiesExtractor interfaces.

此外,我们将演示如何使用Spring的PrincipalExtractorAuthoritiesExtractor接口抽取

For an introduction to Spring Security OAuth2 please refer to these articles.

关于Spring Security OAuth2的介绍,请参考这些文章。

2. Maven Dependencies

2.Maven的依赖性

To get started, we need to add the spring-security-oauth2-autoconfigure dependency to our pom.xml:

为了开始工作,我们需要将spring-security-oauth2-autoconfigure依赖性添加到我们的pom.xml

<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.6.8</version>
</dependency>

3. OAuth Authentication Using Github

3.使用Github的OAuth认证

Next, let’s create the security configuration of our application:

接下来,让我们创建我们应用程序的安全配置。

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.antMatcher("/**")
            .authorizeRequests()
            .antMatchers("/login**")
            .permitAll()
            .anyRequest()
            .authenticated()
            .and()
            .formLogin()
            .disable()
            .oauth2Login();
        return http.build();
    }
}

In short, we’re saying that anyone can access the /login endpoint and that all other endpoints will require user authentication.

简而言之,我们是说任何人都可以访问/login端点,所有其他端点都需要用户认证。

Adding below properties for at least one client will enable the Oauth2ClientAutoConfiguration class, which sets up all the necessary beans.

为至少一个客户端添加以下属性将启用Oauth2ClientAutoConfiguration,该类设置了所有必要的bean。

spring.security.oauth2.client.registration.github.client-id=89a7c4facbb3434d599d
spring.security.oauth2.client.registration.github.client-secret= 
    9b3b08e4a340bd20e866787e4645b54f73d74b6a
spring.security.oauth2.client.registration.github.scope=read:user,user:email

spring.security.oauth2.client.provider.github.token-uri=
    https://github.com/login/oauth/access_token
spring.security.oauth2.client.provider.github.authorization-uri=
    https://github.com/login/oauth/authorize
spring.security.oauth2.client.provider.github.user-info-uri=https://api.github.com/user

Instead of dealing with user account management, we’re delegating it to a third party – in this case, Github – thus enabling us to focus on the logic of our application.

我们不再处理用户账户管理,而是将其委托给第三方–在这种情况下是Github–从而使我们能够专注于我们的应用程序的逻辑。

4. Extracting Principal and Authorities

4.提取委托人和授权人

When acting as an OAuth client and authenticating users through a third party there are three steps we need to consider:

当作为OAuth客户端并通过第三方认证用户时,有三个步骤是我们需要考虑的。

  1. User authentication – the user authenticates with the third party
  2. User authorization – follows authentication, it’s when the user allows our application to perform certain operations on their behalf; this is where scopes come in
  3. Fetch user data – use the OAuth token we’ve obtained to retrieve user’s data

Once we retrieve the user’s data, Spring is able to automatically create the user’s Principal and Authorities.

一旦我们检索到用户的数据,Spring就能够自动创建用户的PrincipalAuthorities

While that may be acceptable, more often than not we find ourselves in a scenario where we want to have complete control over them.

虽然这可能是可以接受的,但更多的时候,我们发现自己处于一种想要完全控制它们的场景中。

To do so, Spring gives us two interfaces we can use to override its default behavior:

为此,Spring给了我们两个接口,我们可以用来覆盖其默认行为

  • PrincipalExtractor – Interface we can use to provide our custom logic to extract the Principal
  • AuthoritiesExtractor – Similar to PrincipalExtractor, but it’s used to customize Authorities extraction instead

By default, Spring provides two components – FixedPrincipalExtractor and FixedAuthoritiesExtractor  that implement these interfaces and have a pre-defined strategy to create them for us.

默认情况下,Spring提供了两个组件–FixedPrincipalExtractorFixedAuthoritiesExtractor,它们实现了这些接口并有预定的策略来为我们创建它们。

4.1. Customizing Github’s Authentication

4.1.定制Github的认证

In our case, we’re aware of how Github’s user data looks like and what we can use to tailor them according to our needs.

在我们的案例中,我们知道Github的用户数据看起来,以及我们可以用什么来根据我们的需要对它们进行调整。

As such, to override Spring’s default components we just need to create two Beans that also implement these interfaces.

因此,要覆盖Spring的默认组件,我们只需要创建两个同样实现这些接口的Beans

For our application’s Principal we’re simply going to use the user’s Github username:

对于我们应用程序的Principal,我们将简单地使用用户的Github用户名。

public class GithubPrincipalExtractor 
  implements PrincipalExtractor {

    @Override
    public Object extractPrincipal(Map<String, Object> map) {
        return map.get("login");
    }
}

Depending on our user’s Github subscription – free, or otherwise – we’ll give them a GITHUB_USER_SUBSCRIBED, or a GITHUB_USER_FREE authority:

根据我们用户的Github订阅–免费的,或者其他–我们会给他们一个GITHUB_USER_SUBSCRIBED,或者一个GITHUB_USER_FREE权限。

public class GithubAuthoritiesExtractor 
  implements AuthoritiesExtractor {
    List<GrantedAuthority> GITHUB_FREE_AUTHORITIES
     = AuthorityUtils.commaSeparatedStringToAuthorityList(
     "GITHUB_USER,GITHUB_USER_FREE");
    List<GrantedAuthority> GITHUB_SUBSCRIBED_AUTHORITIES 
     = AuthorityUtils.commaSeparatedStringToAuthorityList(
     "GITHUB_USER,GITHUB_USER_SUBSCRIBED");

    @Override
    public List<GrantedAuthority> extractAuthorities
      (Map<String, Object> map) {
 
        if (Objects.nonNull(map.get("plan"))) {
            if (!((LinkedHashMap) map.get("plan"))
              .get("name")
              .equals("free")) {
                return GITHUB_SUBSCRIBED_AUTHORITIES;
            }
        }
        return GITHUB_FREE_AUTHORITIES;
    }
}

Then, we also need to create beans using these classes:

然后,我们还需要使用这些类来创建bean。

@Configuration
public class SecurityConfig {
    
    // ...

    @Bean
    public PrincipalExtractor githubPrincipalExtractor() {
        return new GithubPrincipalExtractor();
    }

    @Bean
    public AuthoritiesExtractor githubAuthoritiesExtractor() {
        return new GithubAuthoritiesExtractor();
    }
}

4.2. Using a Custom Authorization Server

4.2.使用自定义授权服务器

We can also use our own Authorization Server for our users – instead of relying on a third party.

我们也可以为我们的用户使用我们自己的授权服务器–而不是依赖第三方。

Despite the authorization server we decide to use, the components we need to customize both Principal and Authorities remain the same: a PrincipalExtractor and an AuthoritiesExtractor.

尽管我们决定使用哪种授权服务器,我们需要定制PrincipalAuthorities的组件仍然是一样的:一个PrincipalExtractor和一个AuthoritiesExtractor

We just need to be aware of the data returned by the user-info-uri endpoint and use it as we see fit.

我们只需要注意由user-info-uri端点返回的数据并按我们认为合适的方式使用它。

Let’s change our application to authenticate our users using the authorization server described in this article:

让我们改变我们的应用程序,使用本文中描述的授权服务器来验证我们的用户。

spring.security.oauth2.client.registration.baeldung.client-id=SampleClientId
spring.security.oauth2.client.registration.baeldung.client-secret=secret

spring.security.oauth2.client.provider.baeldung.token-uri=http://localhost:8081/auth/oauth/token
spring.security.oauth2.client.provider.baeldung.authorization-uri=
    http://localhost:8081/auth/oauth/authorize
spring.security.oauth2.client.provider.baeldung.user-info-uri=http://localhost:8081/auth/user/me

Now that we’re pointing to our authorization server we need to create both extractors; in this case, our PrincipalExtractor is going to extract the Principal from the Map using the name key:

现在我们已经指向了我们的授权服务器,我们需要创建两个提取器;在这种情况下,我们的PrincipalExtractor将使用name键从Map中提取Principal>。

public class BaeldungPrincipalExtractor 
  implements PrincipalExtractor {

    @Override
    public Object extractPrincipal(Map<String, Object> map) {
        return map.get("name");
    }
}

As for authorities, our Authorization Server is already placing them in its user-info-uri‘s data.

至于授权,我们的授权服务器已经将它们放在其user-info-uri的数据中。

As such, we’re going to extract and enrich them:

因此,我们要对它们进行提取和充实。

public class BaeldungAuthoritiesExtractor 
  implements AuthoritiesExtractor {

    @Override
    public List<GrantedAuthority> extractAuthorities
      (Map<String, Object> map) {
        return AuthorityUtils
          .commaSeparatedStringToAuthorityList(asAuthorities(map));
    }

    private String asAuthorities(Map<String, Object> map) {
        List<String> authorities = new ArrayList<>();
        authorities.add("BAELDUNG_USER");
        List<LinkedHashMap<String, String>> authz = 
          (List<LinkedHashMap<String, String>>) map.get("authorities");
        for (LinkedHashMap<String, String> entry : authz) {
            authorities.add(entry.get("authority"));
        }
        return String.join(",", authorities);
    }
}

Then we’ll add the beans to our SecurityConfig class:

然后我们将这些Bean添加到我们的SecurityConfig类中。

@Configuration
public class SecurityConfig {

    // ...

    @Bean
    public PrincipalExtractor baeldungPrincipalExtractor() {
        return new BaeldungPrincipalExtractor();
    }

    @Bean
    public AuthoritiesExtractor baeldungAuthoritiesExtractor() {
        return new BaeldungAuthoritiesExtractor();
    }
}

5. Conclusion

5.总结

In this article, we’ve implemented an application that delegates user authentication to a third party, as well as to a custom authorization server, and demonstrated how to customize both Principal and Authorities.

在这篇文章中,我们实现了一个将用户认证委托给第三方以及自定义授权服务器的应用,并演示了如何定制PrincipalAuthorities

As usual, the implementation of this example can be found over on Github.

像往常一样,这个例子的实现可以在Github上找到over

When running locally, you can run and test the application at localhost:8082

在本地运行时,你可以在localhost:8082运行和测试应用程序。