SpringCloud-oauth2探索实现授权服务器和资源服务器的分离
本人此前没有接触过OAuth2的相关知识,现在要实现一个Demo,使用Oauth2 的微服务相互调用的 设计与编写。目标是:有授权服务器 A,资源服务器 B,资源服务器 C用户调用资源服务器B 接口,资源服务器通过oauth2 协议调用资源服务器C 接口得到结果并返回用户。忙活了一个多星期,原理和过程也没深入完全了解清楚,不过功能都实现了,在此进行记录。网上很多都是引用了spring-cloud-
此前没有接触过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. 如果有时间和毅力,建议经常看看英文版的一手资料。从实际问题出发在网上搜答案,也行,不过有点捕风捉影,需要尝试鉴别。
更多推荐
所有评论(0)