此前没有接触过OAuth2的相关知识,现在要实现一个Demo,使用Oauth2 的微服务相互调用的 设计与编写。

 

目标

有授权服务器 A,资源服务器 B,资源服务器 C

用户调用资源服务器B 接口,资源服务器通过oauth2 协议调用资源服务器C 接口得到结果返回用户。

 

忙活了一个多星期,原理和过程也没深入完全了解清楚,不过功能都实现了,在此进行记录。

由于不是边做边记录,中间一些原理上的尝试过程记不得了,所以下边主要是结果导向,是用postman模拟登进来的用户进行操作,主要是弄明白客户端模式下,资源服务器之间的认证通信。

 

前置说明

网上很多都是引用了spring-cloud-starter-security这个包,但是我用的是spring-cloud-starter-oauth2这个包,没用前者,具体有啥区别暂时也没去深究,不过实现功能用后者就够了。

 

要注意的是,spring-cloud-starter-oauth2这个包只要在pom文件里声明了,即使不写任何相关代码,项目就已经有oauth2的相关安全配置了,全部按默认自动配的,所以后续要写代码自己配置相关信息。可以随便写个简单的接口,前后都访问一下就知道了。

 

pom里还需要声明其他的包,就不罗列说明了,需要啥配啥即可,都是常见的。

 

Oauth2协议是啥,就不多说了,它是一个标准,一手资料如下,有耐心的看的话会发现上边写的很清楚了。

https://tools.ietf.org/html/rfc6749

如果看不惯英文的,可以看阮老师的文章。

http://www.ruanyifeng.com/blog/2019/04/oauth_design.html

对于spring给予的实现,也有可以借鉴查看的。

https://spring.io/guides/topicals/spring-security-architecture/

 

项目大致结果预览

在这个项目中,一共建立了三个模块,如下所示。

 

auth-server是授权服务器(端口9000),provider1(端口9001)和provider2(端口9002)是两个资源服务器

本次实现为两个资源服务器可以互相调接口,使用的模式是oauth2中的客户端模式

结果的操作流程,很简单,两步:

1./oauth/token,向授权服务器调这个接口,声明grant_type为client_credentials,同时在Authorization中使用Basic Auth配置好本资源服务器的client_id和client_secret,密码用明文,请求为post请求,结果会返回一个token,此处用postman模拟操作如下所示。(现实中这个操作是服务器后台自己做的,用户并不知晓)

2.用这个token去调用资源服务器开放的接口 。下图是用postman模拟的操作,用Authorization中的Bearer Token配置上一步得到的token。9001是资源服务器1,/pro1/1这个接口访问了9002的资源服务器2,即我现在调用的是provider1开放的接口,结果是provider2提供的资源,实现了资源服务器在认证模式下的资源互通,内部是用了Feign。

 

授权服务器的配置

目前只涉及到客户端模式,没有用户信息的相关配置,就配置一个AuthorizationServerConfigurerAdapter即可。

 

看注解和父类就知道是授权服务器的配置。

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {}

里边要重写三个configure

 

第一个是令牌端点的约束

具体的没有深究,这么写就行

@Override
public void configure(AuthorizationServerSecurityConfigurer security){
    security.checkTokenAccess("permitAll()").allowFormAuthenticationForClients();
}

第二个是令牌的存储配置

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
    endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory));
}

如果没有自己配置redis存储token的话,框架默认提供的支持是存在内存中的。

如果要自己实现token的存储,就像上边的一样,直接在yml中配置redis的信息就好就好,框架会帮你自动装载的。

当时,不在yml中配置也能写代码实现。

上边的redisConnectionFactory就还在本类中直接定义就好

@Autowired
private RedisConnectionFactory redisConnectionFactory;

yml中直接配置就行,token的redis存储就已经实现了,如下所示。

spring:
  redis:
    host: localhost
    port: 6379
    database: 0

第三个是客户端的配置

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

授权服务器是要知道客户端信息的,否则你随便一个客户端都能让我授权吗?

客户端信息,简单的,可以用InMemory的方式,用代码写死。

不过一般而言,都是持久化的,存在数据库中,用上图的方法即可。

也不用写啥代码,配置直接在yml中写好就行,如下所示

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/xxxx?characterEncoding=utf8
    username: xxxx
    password: xxxx

上图参数里边的方法是需要自己定义的,主要就是配置一些数据库的信息。

private ClientDetailsService jdbcClientDetailsService(){
    JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
    jdbcClientDetailsService.setPasswordEncoder(passwordEncoder);
    return jdbcClientDetailsService;
}

dataSource直接在本类声明就行,配置了这个就会根据yml中的数据配置去进行数据库相关的工作

@Autowired
private DataSource dataSource;

passwordEncoder是客户端密码的加密方式,这个必须配置,具体配置看具体数据库密码的加密格式,或者看自己喜好

这里我是注入了,需要在配置里配一个Bean(任意一个有@Configuration的类中)

@Bean
public PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
}

说下encoder, 这个BCryptPasswordEncoder是推荐的加密方式。

当然这个encoder也可以自己实现,有的时候要用已经存在的数据库,可能里边的加密方式是不加盐的md5,可能要自己实现一个encoder,实现如下接口,重写里边的encode和matches方法即可。

org.springframework.security.crypto.password.PasswordEncoder

 

至于表结构的话,可以去JdbcClientDetailsService这个类中看一下,里边有具体的sql语句。

另外可以看里边的一个方法loadClientByClientId

具体的数据装配规则,即如何用数据库返回的数据封装一个ClientDetails对象,可以在这个类中看一个mapRow这个方法,看完大致就应该知道是怎么一回事儿了。

 

网上很多博客会让配置WebSecurityConfigurerAdapter这个类,这个类主要是配置用户的信息,客户端模式下,不用配也能跑起来。

授权码模式肯定需要配置,在此先不做记录。

 

资源服务器的配置

资源服务器几乎不需要任何的配置

启动类上有如下的注解就行

@SpringBootApplication
@EnableFeignClients
@EnableResourceServer
public class Application {}

 

@EnableResourceServer配置此服务器是资源服务器。

@EnableFeignClients是声明该服务器可以调用其他服务器的接口,这个只在消费端配置即可,提供端不需要配置。

直接粘贴代码展示吧

消费端端口9001,服务端端口9002

@GetMapping("/pro1/1")
public String test1(){
    return apiClient.api1();
}

这是消费端开放的一个接口

里边的apiClient注入一下即可,具体编写如下

@FeignClient(name = "provider2", url = "http://localhost:9002", configuration = FeignConfig.class)
public interface ApiClient {

    @GetMapping("/pro2/2")
    String api1();

}

这里就说明了这个要访问另外一个服务器了,我调用9001的/pro1/1接口,返回的数据就是9002的/pro2/2接口返回的数据,即实现了服务消费端之间的调用。

注解里的name貌似是随便写的,但是不写不行,url是肯定要有的,configuration是需要自己写的,否则都在授权认证体系下了,消费端想调就调提供端了?

配置也没啥难的,就是一个拦截器吧。

@Configuration
public class FeignConfig implements RequestInterceptor{
    @Override
    public void apply(RequestTemplate requestTemplate) {
        SecurityContext securityContext = SecurityContextHolder.getContext();
        Authentication authentication = securityContext.getAuthentication();
        OAuth2AuthenticationDetails details =  (OAuth2AuthenticationDetails) authentication.getDetails();
        requestTemplate.header("Authorization", String.format("%s %s", details.getTokenType(), details.getTokenValue()));
    }
}

我也是照网上说的写的,主要就是配置token。

最后还差了一个配置,就是资源服务器作为客户端的信息,不配置的话,授权服务器怎么认识资源服务器呢?

也很简单,框架都帮你自动做好了,在yml配置里边写就行,当然,自己用代码配置也行。

下边这个应该是最少的配置了,少一个都不行。

security:
  oauth2:
    client:
      client-id: provider1
      client-secret: provider1pass
      access-token-uri: http://localhost:9000/oauth/token
      grant-type: client_credentials
      scope: write read
    resource:
      token-info-uri: http://localhost:9000/oauth/check_token

client是资源服务器作为消费端的配置信息。

client-id和client-secret自不用多说,客户端的id和密码,要注意这里的密码是明文的,授权服务器存的是加密的。

access-token-uri是向授权服务器请求token的接口,即我去哪拿token。

grant-type和scope自不用说。

 

resource是资源服务器作为服务端的配置信息

token-info-uri是校验token的,即有人拿了一个token请求我的服务,我怎么知道这个token对不对,需要去授权服务器校验一下。

 

后话

1. 技术都是堆出来的,一开始实现的简单,后边慢慢扩展加功能。

2. 当今技术上也不需要我等普通程序员去造什么轮子了,可在没有接触的领域中,轮子长啥样,去哪拿,怎么拼都不知道。所以很多东西你花时间弄出来了,但到一定节点回过头看看,其实也没啥东西,就是一个弄过没弄过而已。

3. 如果有时间和毅力,建议经常看看英文版的一手资料。从实际问题出发在网上搜答案,也行,不过有点捕风捉影,需要尝试鉴别。

 

 

 

 

 

 

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐