最近看到一个有趣的开源项目pig,主要的技术点在认证授权中心,spring security oauth,zuul网关实现,Elastic-Job定时任务,趁着刚刚入门微服务,赶快写个博客分析一下。此篇文章主要用于个人备忘。如果有不对,请批评。😭
由于每个模块篇幅较长,且部分内容和前文有重叠,干货和图片较少,阅读时使用旁边的导航功能体验较佳。😉
想要解锁更多新姿势?请访问https://blog.tengshe789.tech/
说明
本篇文章是对基于spring boot
1.5的pig 1
版本做的分析,不是收费的pigx 2
版本。
开源项目地址
配置中心:https://gitee.com/cqzqxq_lxh/pig-config
冷冷官方地址
https://pig4cloud.com/zh-cn/index.html
体验地址
http://pigx.pig4cloud.com/#/wel/index
项目启动顺序
请确保启动顺序(要先启动认证中心,再启动网关)
- eureka
- config
- auth
- gateway
- upms
认证中心
老规矩,自上到下看代码,先从接口层看起
请求rest接口
1 |
|
接口层有三个接口路径,第一个应该没用,剩下两个是校验用户信息的/user
和清除Redis中 accesstoken 与refreshtoken的/removeToken
框架配置
框架配置
下面这段代码时配置各种spring security
配置,包括登陆界面url是"/authentication/require"
啦。如果不使用默认的弹出框而使用自己的页面,表单的action是"/authentication/form"
啦。使用自己定义的过滤规则啦。禁用csrf
啦(自行搜索csrf,jwt验证不需要防跨域,但是需要使用xss过滤)。使用手机登陆配置啦。
1 | 1) (SecurityProperties.ACCESS_OVERRIDE_ORDER - |
校验用户信息
读配置类和接口层,我们知道了,总的逻辑大概就是用户登陆了以后,使用spring security框架的认证来获取权限。
我们一步一步看,边猜想边来。接口处有"ftl/login"
,这大概就是使用freemarker模板,login信息携带的token
会传到用户信息校验url"/user"
上,可作者直接使用Authentication
返回一个getPrincipal()
,就没了,根本没看见自定义的代码,这是怎么回事呢?
原来,作者使用spring security
框架,使用框架来实现校验信息。
打卡config
包下的PigAuthorizationConfig
,我们来一探究竟。
使用spring security 实现 授权服务器
注明,阅读此处模块需要OAUTH基础,https://blog.tengshe789.tech/2018/12/02/%E6%84%9F%E6%80%A7%E8%AE%A4%E8%AF%86jwt/#more
这里简单提一下,spring security oauth
里有两个概念,授权服务器和资源服务器。
授权服务器是根据授权许可给访问的客户端发放access token
令牌的,提供认证、授权服务;
资源服务器需要验证这个access token
,客户端才能访问对应服务。
客户详细信息服务配置
ClientDetailsServiceConfigurer
(AuthorizationServerConfigurer
的一个回调配置项) 能够使用内存或者JDBC来实现客户端详情服务(ClientDetailsService),Spring Security OAuth2
的配置方法是编写@Configuration
类继承AuthorizationServerConfigurerAdapter
,然后重写void configure(ClientDetailsServiceConfigurer clients)
方法
下面代码主要逻辑是,使用spring security
框架封装的简单sql连接器,查询客户端的详细信息👇
1 |
|
相关的sql语句如下,由于耦合度较大,我将sql声明语句改了一改,方面阅读:
1 | /** |
相关数据库信息如下:
授权服务器端点配置器
endpoints
参数是什么?所有获取令牌的请求都将会在Spring MVC controller endpoints
中进行处理
1 |
|
token增强器(自定义token信息中携带的信息)
有时候需要额外的信息加到token返回中,这部分也可以自定义,此时我们可以自定义一个TokenEnhancer
,来自定义生成token携带的信息。TokenEnhancer
接口提供一个 enhance(OAuth2AccessToken var1, OAuth2Authentication var2)
方法,用于对token信息的添加,信息来源于OAuth2Authentication
。
作者将生成的accessToken
中,加上了自己的名字,加上了userId
1 |
|
JWT转换器(自定义token信息中添加的信息)
JWT中,需要在token中携带额外的信息,这样可以在服务之间共享部分用户信息,spring security默认在JWT的token中加入了user_name,如果我们需要额外的信息,需要自定义这部分内容。
JwtAccessTokenConverter
是使用JWT
替换默认的Token的转换器,而token令牌默认是有签名的,且资源服务器需要验证这个签名。此处的加密及验签包括两种方式:
对称加密
非对称加密(公钥密钥)
对称加密需要授权服务器和资源服务器存储同一key值,而非对称加密可使用密钥加密,暴露公钥给资源服务器验签
1 |
|
redis与token
使用鉴权的endpoint
将加上自己名字的token
放入redis
,redis连接器用的srping data redis
框架
1 | /** |
授权服务器安全配置器
1 |
|
自定义实现的手机号 认证服务
接口层
先看接口层,这里和pig-upms-service
联动,给了三个路径,用户使用手机号码登陆可通过三个路径发送请求
1 | "pig-upms-service", fallback = UserServiceFallbackImpl.class) (name = |
配置类
重写SecurityConfigurerAdapter
的方法,通过http请求,找出有关手机号的token,用token找出相关用户的信息,已Authentication
方式保存。拿到信息后,使用过滤器验证
1 |
|
手机号登录校验逻辑MobileAuthenticationProvider
在spring security
中,AuthenticationManage
管理一系列的AuthenticationProvider
,
而每一个Provider
都会通UserDetailsService
和UserDetail
来返回一个
以MobileAuthenticationToken
实现的带用户以及权限的Authentication
此处逻辑是,通过UserService
查找已有用户的手机号码,生成对应的UserDetails
,使用UserDetails生成手机验证Authentication
1 |
|
手机号登录令牌类MobileAuthenticationToken
MobileAuthenticationToken
继承AbstractAuthenticationToken
实现Authentication
所以当在页面中输入手机之后首先会进入到MobileAuthenticationToken
验证(Authentication),
然后生成的Authentication
会被交由我上面说的AuthenticationManager
来进行管理
1 | public class MobileAuthenticationToken extends AbstractAuthenticationToken { |
手机号登录验证filter
判断http请求是否是post,不是则返回错误。
根据request请求拿到moblie信息,使用moblie信息返回手机号码登陆成功的oauth token。
1 |
|
手机登陆成功的处理器MobileLoginSuccessHandler
这个处理器可以返回手机号登录成功的oauth token
,但是要将oauth token
传输出去必须配合上面的手机号登录验证filter
逻辑都在注释中
1 |
|
其他配置
redis集群
挺好的模板,收藏一下
1 | public class PigRedisTokenStore implements TokenStore { |
服务网关模块
网关主体在包pig\pig-gateway\src\main\java\com\github\pig\gateway
下
作者使用了Zuul做为网关,它Netflix开源的微服务网关,可以和Eureka,Ribbon,Hystrix等组件配合使用。
Zuul组件的核心是一系列的过滤器,这些过滤器可以完成以下功能:
身份认证和安全: 识别每一个资源的验证要求,并拒绝那些不符的请求
审查与监控:
动态路由:动态将请求路由到不同后端集群
压力测试:逐渐增加指向集群的流量,以了解性能
负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求
静态响应处理:边缘位置进行响应,避免转发到内部集群
多区域弹性:跨域AWS Region进行请求路由,旨在实现ELB(ElasticLoad Balancing)使用多样化
多种功能的过滤器过滤器
Zuul组件的核心是一系列的过滤器,我们先从过滤器下手。
网关统一异常过滤器
1 |
|
作者以原生zuul过滤器为基础加了日志配置,优先级为+1,数字越大优先级越低。
XSS过滤器
1 | public class XssSecurityFilter extends OncePerRequestFilter { |
重写springMVC里面的的确保在一次请求只通过一次filter的类OncePerRequestFilter
,添加一条https://gitee.com/renrenio/renren-fast的工具类`XssHttpServletRequestWrapper`为过滤链条。
1 |
|
密码过滤器DecodePasswordFilter
此过滤器优先级为+2.每当一个请求不是请求/oauth/token
或者/mobile/token
这个地址时,都会解析使用aes解码器password
。
1 |
|
校验码过滤器ValidateCodeFilter
逻辑作者都写在注释中了,此处使用了redis做为服务端验证码的缓存
1 | ** |
灰度发布
灰度发布,已经不是一个很新的概念了.一个产品,如果需要快速迭代开发上线,又要保证质量,保证刚上线的系统,一旦出现问题那么可以很快的控制影响面,就需要设计一套灰度发布系统.
灰度发布系统的作用在于,可以根据自己的配置,来将用户的流量导到新上线的系统上,来快速验证新的功能修改,而一旦出问题,也可以马上的恢复,简单的说,就是一套A/BTest系统.
初始化
下面是灰度路由初始化类:
1 |
|
灰度发布有关过滤器AccessFilter
首先重写filterOrder()
方法,使这个过滤器在在RateLimitPreFilter
之前运行,不会出现空指针问题。此处优先级FORM_BODY_WRAPPER_FILTER_ORDER-1
.
1 |
|
核心方法在run()上,首先受到request请求,拿到他的版本约束信息,然后根据选择添加token
路由微服务断言处理器MetadataCanaryRuleHandler
自定义ribbon
路由规则匹配多版本请求,实现灰度发布。复合判断server所在区域的性能和server的可用性选择server,即,使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个zone的运行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用于过滤掉连接数过多的Server。
此处逻辑是
- eureka metadata (主机名,IP地址,端口号,状态页健康检查等信息,或者通过配置文件自定义元数据)存在版本定义时候进行判断
- 不存在 metadata 直接返回true
1 |
|
动态路由
配置
1 | public class DynamicRouteLocator extends DiscoveryClientRouteLocator { |
网关日志处理
代码注释已经将逻辑写的很清楚了
1 | 4j |
多维度限流
限流降级处理器ZuulRateLimiterErrorHandler
重写zuul中默认的限流处理器DefaultRateLimiterErrorHandler
,使之记录日志内容
1 |
|
与spring security oAuth方法整合单点登陆
授权拒绝处理器 PigAccessDeniedHandler
重写Srping security oAuth
提供单点登录验证拒绝OAuth2AccessDeniedHandler
接口,使用R包装失败信息到PigDeniedException
1 |
|
菜单管理
MenuService
1 | "pig-upms-service", fallback = MenuServiceFallbackImpl.class) (name = |
使用feign连接pig系统的菜单微服务
菜单权限
1 | "permissionService") ( |
网关总结
pig这个系统是个很好的框架,本次体验的是pig的zuul网关模块,此模块与feign,ribbon,spring security,Eurasia进行整合,完成或部分完成了动态路由,灰度发布,菜单权限管理,服务限流,网关日志处理,非常值得学习!
UPMs权限管理系统模块
百度了一下,UPMS是User Permissions Management System,通用用户权限管理系统
数据库设计
部门表
部门关系表
字典表
1 | /** |
日志表
1 |
|
菜单权限表
角色表
角色与部门对应关系
略
角色与菜单权限对应关系
略
用户表
1 | /** |
动态路由配置表
业务逻辑
全是基于mybatis plus的CRUD,有点多。大部分干这行的都懂,我就不详细展开了。
验证码
创建
ValidateCodeController
可以找到创建验证码相关代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21/**
* 创建验证码
*
* @param request request
* @throws Exception
*/
"/{randomStr}") (SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX +
public void createCode(@PathVariable String randomStr, HttpServletRequest request, HttpServletResponse response)
throws Exception {
Assert.isBlank(randomStr, "机器码不能为空");
response.setHeader("Cache-Control", "no-store, no-cache");
response.setContentType("image/jpeg");
//生成文字验证码
String text = producer.createText();
//生成图片验证码
BufferedImage image = producer.createImage(text);
userService.saveImageCode(randomStr, text);
ServletOutputStream out = response.getOutputStream();
ImageIO.write(image, "JPEG", out);
IOUtils.closeQuietly(out);
}
其中的 producer
是使用Kaptcha
,下面是配置类
1 |
|
发送手机验证码
大体逻辑为,先查询验证码redis缓存,没有缓存则说明验证码缓存没有失效,返回错误。
查到没有验证码,则根据手机号码从数据库获得用户信息,生成一个4位的验证码,使用rabbbitmq
队列把短信验证码保存到队列,同时加上手机验证码的redis缓存
1 | /** |
树形节点工具栏
1 | public class TreeUtil { |
生成avue模板类
1 | public class PigResourcesGenerator { |
velocity模板
1 | package $!{package.Controller}; |
缓存
在部分实现类中,我们看到了作者使用了spring cache
相关的注解。现在我们回忆一下相关缓存注解的含义:
@Cacheable
:用来定义缓存的。常用到是value,key;分别用来指明缓存的名称和方法中参数,对于value你也可以使用cacheName,在查看源代码是我们可以看到:两者是指的同一个东西。
@CacheEvict
:用来清理缓存。常用有cacheNames,allEntries(默认值false);分别代表了要清除的缓存名称和是否全部清除(true代表全部清除)。
@CachePut
:用来更新缓存,用它来注解的方法都会被执行,执行完后结果被添加到缓存中。该方法不能和@Cacheable同时在同一个方法上使用。
后台跑批定时任务模块
Elastic-Job
是ddframe中dd-job的作业模块中分离出来的分布式弹性作业框架。去掉了和dd-job中的监控和ddframe接入规范部分。该项目基于成熟的开源产品Quartz和Zookeeper及其客户端Curator进行二次开发。主要功能如下:
- 定时任务: 基于成熟的定时任务作业框架Quartz cron表达式执行定时任务。
- 作业注册中心: 基于Zookeeper和其客户端Curator实现的全局作业注册控制中心。用于注册,控制和协调分布式作业执行。
- 作业分片: 将一个任务分片成为多个小任务项在多服务器上同时执行。
- 弹性扩容缩容: 运行中的作业服务器崩溃,或新增加n台作业服务器,作业框架将在下次作业执行前重新分片,不影响当前作业执行。
- 支持多种作业执行模式: 支持OneOff,Perpetual和SequencePerpetual三种作业模式。
- 失效转移: 运行中的作业服务器崩溃不会导致重新分片,只会在下次作业启动时分片。启用失效转移功能可以在本次作业执行过程中,监测其他作业服务器空闲,抓取未完成的孤儿分片项执行。
- 运行时状态收集: 监控作业运行时状态,统计最近一段时间处理的数据成功和失败数量,记录作业上次运行开始时间,结束时间和下次运行时间。
- 作业停止,恢复和禁用:用于操作作业启停,并可以禁止某作业运行(上线时常用)。
- 被错过执行的作业重触发:自动记录错过执行的作业,并在上次作业完成后自动触发。可参考Quartz的misfire。
- 多线程快速处理数据:使用多线程处理抓取到的数据,提升吞吐量。
- 幂等性:重复作业任务项判定,不重复执行已运行的作业任务项。由于开启幂等性需要监听作业运行状态,对瞬时反复运行的作业对性能有较大影响。
- 容错处理:作业服务器与Zookeeper服务器通信失败则立即停止作业运行,防止作业注册中心将失效的分片分项配给其他作业服务器,而当前作业服务器仍在执行任务,导致重复执行。
- Spring支持:支持spring容器,自定义命名空间,支持占位符。
- 运维平台:提供运维界面,可以管理作业和注册中心。
配置
作者直接使用了开源项目的配置,我顺着他的pom文件找到了这家的github,地址如下
https://github.com/xjzrc/elastic-job-lite-spring-boot-starter
工作流作业配置
1 | "0 0 0/1 * * ? ", shardingTotalCount = 3, shardingItemParameters = "0=Beijing,1=Shanghai,2=Guangzhou") (cron = |
测试代码
1 | 4j |
开源版对这个支持有限,等到拿到收费版我在做分析。
消息中心
这里的消息中心主要是集成了钉钉服务和阿里大鱼短息服务
钉钉
配置
钉钉是相当简单了,只需要一个webhook
信息就够了。
webhook
是一种web回调或者http的push API,是向APP或者其他应用提供实时信息的一种方式。Webhook在数据产生时立即发送数据,也就是你能实时收到数据。这一种不同于典型的API,需要用了实时性需要足够快的轮询。这无论是对生产还是对消费者都是高效的,唯一的缺点是初始建立困难。Webhook有时也被称为反向API,因为他提供了API规则,你需要设计要使用的API。Webhook将向你的应用发起http请求,典型的是post请求,应用程序由请求驱动。
1 |
|
消息模板
1 | /** |
监听
使用队列时时监听
1 | 4j |
发送
使用队列发送
1 | 4j |
阿里大鱼短息服务
配置
1 |
|
监听
1 | 4j |
发送
不错的模板
1 | 4j |
资源认证服务器 (单点登陆功能)
由于作者在认证中心使用了spring security oauth框架,所以需要在微服务的客户端实现一个资源认证服务器,来完成SSO需求。
配置
暴露监控信息
1 |
|
接口
1 | 2Sso |
监控模块
springboot admin配置
RemindingNotifier
会在应用上线或宕掉的时候发送提醒,也就是把notifications
发送给其他的notifier
,notifier的实现很有意思,不深究了,从类关系可以知道,我们可以以这么几种方式发送notifications:Pagerduty、Hipchat 、Slack 、Mail、 Reminder
1 |
|
短信服务下线通知
继承AbstractStatusChangeNotifier
,将短信服务注册到spring boot admin
中。
1 | 4j |
zipkin 链路追踪
由于zipkin是侵入式,因此这部分组件没有代码,只有相关依赖。下面分享一下作者的yaml
DB
1 | server: |
ELK
1 | server: |
续1s时间
全片结束,觉得我写的不错?想要了解更多精彩新姿势?赶快打开我的👉个人博客 👈吧!
谢谢你那么可爱,还一直关注着我~❤😝