在这篇文章中,我们将了解如何使用 Spring Boot 2 和 Spring Security 5 OAuth2 来实现集中授权的授权服务器以及如何通过 GUI 对其进行管理,还将提供资源服务器演示以及 github 下的整个项目。

很多示例涵盖了基于早期版本的 Spring boot 2 和 Spring Security 5 使用内存令牌实现 Oauth2,因此想法是使用 MySql 数据库作为令牌存储。

为了深入探讨这个主题,我们必须:

  • 配置 Spring 安全。
  • 配置数据库。
  • 创建授权服务器。
  • 创建资源服务器。
  • 使用 curl 客户端使用访问令牌获取安全资源。

什么是 Oauth 2?

OAuth 2.0 是行业标准授权协议。 OAuth 2.0 取代了 2006 年创建的原始 OAuth 协议上所做的工作。OAuth 2.0 注重客户端开发人员的简单性,同时为 Web 应用程序、桌面应用程序、移动电话和客厅设备提供特定的授权流程。

该规范及其扩展正在 IETF OAuth 工作组内开发。

Oauth 2 角色

OAuth2 定义了 4 个角色:

  • 资源所有者:通常是您自己。

  • 资源服务器:托管受保护数据的服务器(例如 Google 托管您的个人资料和个人信息)。

  • 客户端:请求访问资源服务器的应用程序(网站、Javascript 应用程序或移动应用程序……)。

  • 授权服务器:向客户端颁发访问令牌的服务器。该令牌将用于客户端请求资源服务器。该服务器可以与资源服务器相同(相同的物理服务器和相同的应用程序),而且经常是这种情况。

下图说明了角色流程:

img

授权类型

OAuth 2 为不同的用例提供了多种“授权类型”。定义的授权类型型有:

  • 授权码:授权码授予是使用您的 Facebook 或 Google 帐户登录应用程序的功能。
  • 密码:旨在用于基于用户代理的客户端。其次,授权服务器不会像授权代码授予那样返回授权代码来交换访问令牌,而是返回访问令牌。
  • 客户端凭据:客户端可以仅使用其客户端凭据(或其他支持的身份验证方式)请求访问令牌,当客户端请求访问其下的受保护资源控制权,或先前已被其他资源所有者控制的与授权服务器安排。
  • 隐式授权:隐式授权是一种简化的授权代码流,针对使用 JavaScript 等脚本语言在浏览器中实现的客户端进行了优化。在隐式流程中,而不是向客户端发出授权代码,直接向客户端颁发访问令牌。

示范

让我们动手吧

业务层

为简单起见,我们的主要业务应用程序将是使用一个实体的产品 API,我们的访问规则将是:

  • PRODUCT_CREATE
  • PRODUCT_UPDATE
  • PRODUCT_DISPLAY
  • PRODUCT_ADMIN

OAuth2 客户端设置

要设置 Oauth 2 客户端,我们需要创建下表 [有关更多详细信息,请参阅链接]

我们将调用像“product_api”这样的资源服务器 对于该服务器,我们定义一个客户端,称为:

  • 读-写-客户端(授权授权类型:读、写)
1
2
3
INSERT INTO OAUTH_CLIENT_DETAILS(CLIENT_ID, RESOURCE_IDS, CLIENT_SECRET, SCOPE, AUTHORIZED_GRANT_TYPES, AUTHORITIES, ACCESS_TOKEN_VALIDITY, REFRESH_TOKEN_VALIDITY)
VALUES ('read-write-client', 'product-api','$2a$10$BurTWIy5NTF9GJJH4magz.9Bd4bBurWYG8tmXxeQh1vs7r/wnCFG2','read,write', 'client_credentials', 'ROLE_PRODUCT_ADMIN', 10800, 2592000);
#password [hashed with BCCrypt] :user

权限和用户设置

Spring Security 附带两个有用的接口:

下面的脚本将加载所有权限和凭据(用户):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
INSERT INTO authority  VALUES(1,'ROLE_OAUTH_ADMIN');
INSERT INTO authority VALUES(2,'ROLE_ADMIN_PRODUCT');
INSERT INTO authority VALUES(3,'ROLE_RESOURCE_ADMIN');

INSERT INTO credentials VALUES(1,b'1','oauth_admin','$2a$10$BurTWIy5NTF9GJJH4magz.9Bd4bBurWYG8tmXxeQh1vs7r/wnCFG2','0');
INSERT INTO credentials VALUES(2,b'1','resource_admin','$2a$10$BurTWIy5NTF9GJJH4magz.9Bd4bBurWYG8tmXxeQh1vs7r/wnCFG2','0');
INSERT INTO credentials  VALUES(3,b'1','user','$2a$10$BurTWIy5NTF9GJJH4magz.9Bd4bBurWYG8tmXxeQh1vs7r/wnCFG2','0');

INSERT INTO credentials_authorities VALUES (1, 1);
INSERT INTO credentials_authorities VALUES (2, 3);
INSERT INTO credentials_authorities VALUES (3, 2);
#Password : user

API 层

对于演示,基于 Spring Boot 开发了 RESTful 应用程序并公开以下端点:

img

Spring 安全配置

我们必须提供 UserDetailsService 接口的实现,以便获取用户凭据和权限,如下所示

img

为了向应用程序提供安全性,我们将使用 @EnableWebSecurity 注解和 WebSecurityConfigurerAdapter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@EnableWebSecurity
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public UserDetailsService userDetailsServiceBean() throws Exception {
        return new JdbcUserDetails();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/webjars/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/login","/logout.do").permitAll()
                .antMatchers("/**").authenticated()
                .and()
                .formLogin()
                .loginProcessingUrl("/login.do")
                .usernameParameter("username")
                .passwordParameter("password")
                .loginPage("/login")
                .and()
                .logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout.do"))
                .and()
                .userDetailsService(userDetailsServiceBean());
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsServiceBean())
        .passwordEncoder(passwordEncoder());
    }
}

OAuth2 配置

要设置 Oauth 2,需要实现两个组件

  • Authorization Server 授权服务器
  • Resource Server 资源服务器

授权服务器

授权服务器负责验证用户身份并提供令牌,使用@EnableAuthorizationServer 注解启用授权服务器配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource oauthDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public JdbcClientDetailsService clientDetailsService() {
        return new JdbcClientDetailsService(oauthDataSource());
    }

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(oauthDataSource());
    }

    @Bean
    public ApprovalStore approvalStore() {
        return new JdbcApprovalStore(oauthDataSource());
    }

    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new JdbcAuthorizationCodeServices(oauthDataSource());
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {

    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .approvalStore(approvalStore())
                .authorizationCodeServices(authorizationCodeServices())
                .tokenStore(tokenStore());
    }
}

成功配置运行授权服务器后,您将获得登录页面来管理授权服务器

img

使用 oauth_admin/user 作为用户名/密码访问 Oauth2 仪表板,您可以在其中创建服务器客户端

img

资源服务器

资源服务器托管受 OAuth2 令牌保护的资源(基本上是我们的产品 API)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@EnableResourceServer
@Configuration
public class ResourcesServerConfiguration  extends ResourceServerConfigurerAdapter {

    @Bean
    @ConfigurationProperties(prefix="spring.datasource")
    public DataSource ouathDataSource(){return DataSourceBuilder.create().build();}

    @Override
    public void configure(ResourceServerSecurityConfigurer resources)throws Exception{
        TokenStore tokenStore=new JdbcTokenStore(ouathDataSource());
        resources.resourceId("product_api").tokenStore(tokenStore);
    }
    @Override

    public void configure(HttpSecurity http) throws Exception{
        http
                .authorizeRequests()
                .antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')")
                .antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')")
                .and()
                .headers().addHeaderWriter((request, response) -> {
            			response.addHeader("Access-Control-Allow-Origin", "*");
                  if (request.getMethod().equals("OPTIONS")) {
                      response.setHeader("Access-Control-Allow-Methods", request.getHeader("Access-Control-Request-Method"));
                      response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
                  }
        });
    }
}

使用以下 Curl 客户端测试产品列表端点:

1
2
3
4
#!/bin/sh
TOKEN=`curl -s -u curl_client:user -X POST localhost:8081/oauth/token\?grant_type=client_credentials | egrep -o ‘[a-f0–9-]{20,}`
echo "Got token for curl client as :$TOKEN"
curl localhost:8083/product/products -H "Authorization: Bearer $TOKEN"

运行 Curl 客户端 bash 脚本后得到响应:

1
2
3
$ ./client.sh
Got token for curl client as : 3be01519–0cab-4049-b87d-617c48bda502
[{“version”:0,”name”:”product_1",”available”:false},{“version”:0,”name”:”product_2",”available”:true}]

从 github 上查看整个代码:https://github.com/Akourtiim/oauth2-spring-boot-2.0.2.git

参考:

原文链接:Oauth 2 Centralized Authorization with Spring Boot 2.0.2 and Spring Security 5 and JDBC token store