「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

  • 时间:2018-09-04 21:55 作者:ServiceComb 来源:ServiceComb 阅读:75
  • 扫一扫,手机访问
摘要:在今年的LC3大会上,ServiceComb展台所展现的demo视频“30分钟开发雏形CRM应使用”引起了参会者的广泛关注,大家纷纷对其背后的技术体现出浓厚的兴趣。本文将从房地产企业的用户管理管理场景入手,用领域驱动设计,深入技术细节,详解如何快速开发落地一个微服务化的用户管理系统。牛刀小试打开浏览
「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

在今年的LC3大会上,ServiceComb展台所展现的demo视频“30分钟开发雏形CRM应使用”引起了参会者的广泛关注,大家纷纷对其背后的技术体现出浓厚的兴趣。本文将从房地产企业的用户管理管理场景入手,用领域驱动设计,深入技术细节,详解如何快速开发落地一个微服务化的用户管理系统。

牛刀小试

打开浏览器,输入地址http://start.servicecomb.io/打开SERVICECOMB SPRING INITIALIZR,修改Project Metadata中的Group,Artifact和ServiceComb Parameters中的ServiceCenter Address,Governance等,点击GenerateProject,解压生成下载的demo.zip。

「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

运行它也很简单,用IDE打开项目,DEBUG -> Application.java,或者在命令行:

稍等微服务启动就绪,打开浏览器输入http://localhost:9080/hello验证一下:

「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

是不是非常轻松呢?

脚手架

在建筑领域,脚手架是施工现场为方便工人操作并处理垂直和水平运输而搭设的各种支架以及平台。

「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

在软件开发领域,它引申为预提供少量基础框架代码加速开发过程,避免从零开始构建项目。使用户只要要依据需求场景选择合适的脚手架,而后填充定制的业务逻辑就可,不必再去解决少量基础功能,例如数据库连接、日志实现、RPC传输等。

微服务框架一般都会提供脚手架功能,例如Spring,提供了SPRING INITIALIZR;ServiceComb基于SPRING INITIALIZR,提供了更具优势的特性:

  1. 生成的项目除了在POM中自动增加必要的依赖,还会提供Producer和Consumer示例代码(Hello World);
  2. 会进一步提供Edge ServerAuthcation Server等更贴近业务的脚手架项目,让使用户能快速构建体系完整的微服务系统。

那么什么叫一个完整的微服务系统呢?我们可以拿一个具体的场景做例子,会更有感觉:

场景:地产CRM

您运营着一家房地产开发商,销售房产,迫切需要一套销售系统,考虑到微服务的优势,您决定用微服务的方式构建系统;主要的业务流程也非常简单:使用户前来购买购买产品(房产),首先需要登记使用户信息,并缴纳肯定数量的定金,待交易当日,筛选心仪的产品(房产),支付尾款,完成交易。

1、用DDD指导地产CRM系统的设计

微服务系统的设计方面,领域驱动设计(Domain-Driven Design,DDD)是一个从业务出发的好选择,它由Eric Evans提出,是一种全新的系统设计和建模方法,这里的模型指的就是领域模型(DomainModel)。领域模型通过聚合(Aggregate)组织在一起,聚合间有显著的业务边界,这些边界将领域划分为一个个限界上下文(Bounded Context)。Martin Fowler对它们都有详细的解读 [1]。

理论概念都搞清楚了,那么怎样来找模型和聚合呢?一个非常流行的方法就是Event Storming [2],它是由AlbertoBrandolini发明,经历了DDD社区和很多团队的实践,也是一种非常有参加感的团队活动:

「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

上图就是我们对地产CRM这个场景用Event Storming探究的结果,现在我们能够将限界上下文清晰的梳理出来:

「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

提醒:Event Storming是一项非常有创造性的活动,也是一个持续探讨和反复改进的过程,不同的团队关注的核心域(Core Domain)不同,得到的最终结果也会有差异。我们的目的是为了演示完整的微服务系统构建的过程,并不涉及商业核心竞争力方面的讨论,因而没有Core Domain和Sub Domain之类的偏重。

2、将分析成果转化为方案域设计

当我们完成所有的限界上下文的识别后,可以直接将它们落地为微服务:

「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

  1. 使用户服务:提供使用户信息管理服务,这里保存这使用户的账号和密码,负责登录和认证;
  2. 产品(房产)服务:提供产品管理服务,保存着房产的信息诸如价格、能否已售出等信息;
  3. 支付服务:提供交易时支付服务,模拟对接银行支付定金,以及购房时支付尾款;

因为完成一笔交易是一个复杂的流程,与这三个微服务都有关联,因而我们引入了一个复合服务——交易服务:

「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

  • 交易服务:提供产品交易服务。它通过编排调使用将整个交易流程串起来,交易服务中有两个流程:
  • 定金支付

Step1:通过使用户服务验证使用户身份;

Step2:通过支付服务请求银行扣款,添加定金账号内的定金;

  • 购房交易

Step1:通过使用户服务验证使用户身份;

Step2:通过资源服务确定使用户希望购买的资源(房产)尚未售出;

Step3:通过资源服务标记目标资源(房产)已售出;

Step4:通过支付服务请求扣减定金账号内的定金,以及银行扣剩下的尾款;

最后两个步骤需要保证事务一致性,其中Step4包含两个扣款操作。

之后,我们引入Edge服务提供统一入口:

「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

  • Edge服务:很多时候也被称为API网关(API Gateway),负责集中认证、动态路由等等;

提醒:Edge服务需要依赖服务注册-发现机制,因而同时导入了ServiceCenter。

最后还需要提供UI:

「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

  • 前台UI(同样以微服务方式提供):使用户交互界面;

至此,DDD设计地产CRM的工作就结束了。

快速实现用户关系管理系统的使用户服务

1、使用户微服务并不简单

使用户微服务是所有系统中不可或者缺的部分,它承载了认证和受权等核心功能——无论是登录一个网站、还是打开一个APP,当涉及到需要身份识别后才能够执行的操作,都需要使用户微服务把关。例如观看视频网站上的视频,匿名使用户会插播广告,假如希望屏蔽广告,则需要登录并购买VIP会员,登录即是身份认证的过程,而VIP屏蔽广告即是受权的过程。

认证

认证不仅仅是一次性验证使用户名和密码的过程,还需要能反复用认证的结果,确保后继所有操作都是合法的,这就涉及到“有状态”,但HTTP是一个无状态协议,如何能够将登录成功后的认证信息与后继的请求关联起来呢?

我们非常熟习的做法是用Session或者Cookie:

  • Session存储在服务端,因而具有良好的防篡改能力,但弊端是使服务有状态,微服务系统中,同一个微服务会依据系统压力的大小弹性伸缩出多个运行实例负载均衡,跨实例访问会状态丢失。
  • Cookie存储在用户端,它正好与Session相反,优势是服务不必保持状态,但弊端是用户比较容易的篡改Cookie信息,例如修改过期时间以逃避验证,而且浏览器对Cookie也有较多限制。

那么,如何兼顾这两方面的需求呢?Token就是一个比较好的处理方案。

Token中文翻译为令牌,它将登录认证后的信息签名后返回,服务端不保存,用户端请求的时候将认证的完整信息附带上提供给服务端验签,签名可以保证信息不被篡改。理解了理解Token的原理,自然要关注Token的格式,JWT就是这样一个基于JSON的开放标准RFC-7519 [3]。

JWT (Java Web Token)规范

简而言之JWT规范由三部分构成:

1、Header: 公告Token的类型也就是JWT,以及加密算法,例如:

{

"typ":"JWT",

"alg":"HS256"

}

2、Playload:存放有效信息,既包含标准签发者、使用户、签发时间、过期时间,唯一标识等信息;也可以存放使用户自己设置的公告信息,例如权限控制相关的内容,例如:

{

"sub":"1234567890",

"name":"YangYongZheng",

"iat":1516239022

}

3、Signature:签名信息,包含Header和Playload的原始信息(Base64编码过)以及签名过后的信息。

提醒:JWT IO提供了在线编码和解码工具 [4]。

受权

受权的本意是指将完成某项工作所必需的权力授给下属人员,在软件系统中往往引申为使人或者角色具有访问特定资源或者更改行为的许可。例如之前提到的VIP屏蔽广告,即是视频网站允许播放终端在特定的帐号登录后跳过广告播放环节(行为)的许可。

受权系统比较常见的做法有ACL和RBAC:

  • ACL:ACL全称Access Control List,它是以受控资源为核心,每一个受控资源,都有一个权限控制列表记录哪些使用户或者角色对这项资源执行具体操作(也被称为受权点)的权限设置,例如查询(可见)、修改、删除等等。Windows中的文件系统安全即是一个经典的ACL实现案例:
「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

  • RBAC:RBAC全称Role Based Access Control,与ACL相比,它以角色为核心,权限落地在角色上,不为特定使用户受权。它的优势是大幅简化了使用户与权限的管理,在受控对象不多或者控制粒度要求不高(例如接口访问控制)的场景下非常适使用。
「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

因为微服务系统的权限控制主要是接口访问控制上,并且多采使用使用户组方式组织使用户,因而RBAC是比较流行的做法。

2、实现使用户微服务

第一步:创立微服务项目

用SERVICECOMBSPRING INITIALIZR创立使用户微服务,创立完毕后用IDEA或者Eclipse打开项目,我们删掉HelloImpl和HelloConsumer,之后增加自己的实现。

第二步:用MySQL持久化使用户信息

使用户微服务需要持久化使用户信息,我们用MySQL数据库,ORM用SpringData JPA:

引入依赖


mysql
mysql-connector-java


org.springframework.boot
spring-boot-starter-data-jpa

定义存储User信息的UserEntity实体

@Entity
@Table(name ="T_User")
public class UserEntity{
@Id
private String name;
private String password;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public String getPassword(){
return password;
}
public void setPassword(String password){
this.password = password;
}
public UserEntity(){
}
public UserEntity(String name, String password){
this.name = name;
this.password = password;
}
}

在CodeFist模式下,Spring Data JPA会在数据库中自动创立T_User表与此实体映射。

实现UserEntity实体的Repository

我们继承JPA的PagingAndSortingRepository来实现ORM操作

@Repository
public interface UserRepository extends PagingAndSortingRepository{
UserEntity findByName(String name);
}
配置数据库连接
在项目的resources目录下新添加application.properties文件,写入数据库连接信息:
spring.datasource.url=jdbc:mysql://localhost:3306/user_db?useSSL=false
spring.datasource.username=root
spring.datasource.password=pwd
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

提醒:关于Spring Data JPA的更多资料请参见这篇文档 [5],为了能够简化依赖的引入我们实际上用的是Spring Boot JPA Starter,详细的例子请参见这篇文档 [6]。

第三步:实现JWT认证

定义JWT接口

public interface TokenStore{
String generate(String userName);
booleanvalidate(String token);
}

generate使用于生成Token,validate使用于验证Token能否正确。

实现TokenStore

我们用jjwt [7]提供的JWT实现,创立JwtTokenStore类,继承TokenStore接口,并重写方法:

@Component
@Component
public class JwtTokenStore implements TokenStore{
private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenStore.class);
private final String secretKey;
private final int secondsToExpire;
public JwtTokenStore(){
this.secretKey ="someSecretKeyForAuthentication";
this.secondsToExpire =60*60*24;
}
public JwtTokenStore(String secretKey,int secondsToExpire){
this.secretKey = secretKey;
this.secondsToExpire = secondsToExpire;
}
@Override
public String generate(String userName){
return Jwts.builder().setSubject(userName)
.setExpiration(Date.from(ZonedDateTime.now().plusSeconds(secondsToExpire).toInstant()))
.signWith(HS512, secretKey).compact();
}
@Override
public boolean validate(String token){
try{
return StringUtils.isNotEmpty(Jwts.parser()
.setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject());
}catch(JwtException| IllegalArgumentException e){
LOGGER.info("validateToken token : "+ token +" failed", e);
}
returnfalse;
}
}

第四步:实现使用户服务

定义UserService接口

public interface UserService{
ResponseEntitylogon(UserDTO user);
ResponseEntitylogin(UserDTO user);
}

logon使用于新使用户注册,login使用于使用户登录验证,UserDTO使用于参数传递:

public class UserDTO{
private String name;
private String password;
public String getName(){
return name;
}
public String getPassword(){
return password;
}
public UserDTO(){
}
public UserDTO(String name, String password){
this.name = name;
this.password = password;
}
}

实现并发布UserService

创立UserServiceImpl,继承UserService接口:

@RestSchema(schemaId ="user")
@RequestMapping(path ="/")
public class UserServiceImpl implements UserService{
private final UserRepository repository;
private final TokenStore tokenStore;
@Autowired
public UserServiceImpl(UserRepository repository, TokenStoretokenStore){
this.repository = repository;
this.tokenStore = tokenStore;
}
@Override
@PostMapping(path ="logon")
public ResponseEntitylogon(@RequestBody UserDTOuser){
if(validateUser(user)){
UserEntity dbUser = repository.findByName(user.getName());
if(dbUser == null){
UserEntity entity =new UserEntity(user.getName(), user.getPassword());
repository.save(entity);
return new ResponseEntity<>(true, HttpStatus.OK);
}
throw new InvocationException(BAD_REQUEST,"user namehad exist");
}
throw new InvocationException(BAD_REQUEST,"incorrectuser");
}
@Override
@PostMapping(path ="login")
public ResponseEntitylogin(@RequestBody UserDTOuser){
if(validateUser(user)){
UserEntity dbUser = repository.findByName(user.getName());
if(dbUser != null){
if(dbUser.getPassword().equals(user.getPassword())){
String token = tokenStore.generate(user.getName());
HttpHeaders headers =generateAuthenticationHeaders(token);
//addauthentication header
return new ResponseEntity<>(true, headers, HttpStatus.OK);
}
throw new InvocationException(BAD_REQUEST,"wrongpassword");
}
throw new InvocationException(BAD_REQUEST,"user namenot exist");
}
throw new InvocationException(BAD_REQUEST,"incorrectuser");
}
private boolean validateUser(UserDTO user){
return user != null &amp;&amp; StringUtils.isNotEmpty(user.getName())&amp;&amp; StringUtils.isNotEmpty(user.getPassword());
}
private HttpHeaders generateAuthenticationHeaders(String token){
HttpHeaders headers =newHttpHeaders();
headers.add(AUTHORIZATION, token);
return headers;
}
}

登录成功后,会从TokenStore生成Token,并将其写入Key为AUTHORIZATION的Header。

因为我们允许任何使用户注册和登录,所以目前还没有受权的需求,经过上面四步,具备基本注册和登录功能的使用户微服务就构建好了。

3、验证明现的使用户服务

启动使用户微服务,我们先注册一个账号:

「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

显示注册成功,现在我们用这个账号登录:

「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

返回登录成功,Response中已经包含了AUTHORIZATIONHeader,后继的所有请求都需要用这个Token值进行合法认证。

至此,实现用户关系管理系统的使用户服务工作就结束了,现在我们会将目光转移到Edge服务,通过Edge服务作为微服务调使用的统一入口,在它之上构建统一认证,应对海量级调使用的挑战。

开发高性能边缘服务

1、什么是边缘服务(Edge Service)

边缘服务也是一个微服务,微服务化系统通常用边缘服务(Edge Service)作为所有其它微服务的统一入口,因而它也常常会被称为APIGateway,用边缘服务的好处有如下几点:

  • 动态路由:动态配置URL地址与微服务之间的对应关系,便于扩展,以及实现版本灰度发布等;
  • 统一认证:在入口处进行访问认证,避免需要在所有的微服务中都承载重复的认证机制;
  • 集中监控:与统一认证相似,在边缘服务对入口调使用进行监控,容易统计流量信息。

2、边缘服务的作使用和原理

我们先来看不用边缘服务,UI直接调使用使用户服务的场景:

「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

可以看出这种调使用方式,UI缺乏肯定的灵活性,表现在:

  • UI的实现绑定了Chassis的编程语言Java,无法用PHP等其它前台技术开发;
  • UI访问微服务的路径无法动态配置,假如作为后台的微服务系统发生调整,则UI很可能需要修改;
  • UI很容易混入复合(编排)调使用的逻辑,使得结构变得复杂难以维护。

我们再看引入边缘服务后,UI如何通过边缘服务调使用使用户服务:

「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

Edge服务将在9090端口上接受http rest调使用,我们设计了下面的转发规则:

http://{edge-host-name}:9090/{ServiceComb微服务Name}/{服务路径&amp;参数}

使用户微服务名(service_description.name)是user-service,因而login调使用URL:cse://user-service/login可以通过http://{edge-host-name}:9090/user-service/login 访问。

如此一来,微服务名成为了路径的一部分,http协议的hostname和port将固定指向Edge服务保持不变,灵活性大大添加了。

到此我们还可以再做一点点改进,引入一个自己设置配置edge.routing-short-path.{简称},映射微服务名:

edge:
routing-short-path:
user: user-service

上面的配置代表:http://{edge-host-name}:9090/user/login 等效于:http://{edge-host-name}:9090/user-service/login,如此一来:

  1. URL能够更加简洁;
  2. 当微服务名发生变化,只要要调整对应的配置,不需要更改前台UI路径代码。

3、实现边缘服务

第一步:引入Edge Core依赖


org.apache.servicecomb
edge-core

Copy

第二步:编写调度器Dispatcher

Edge服务的核心就是调度器Dispatcher,ServiceComb Edge Core中的Dispatcher基于高性能的Vertx Reactive,轻松应对百万量级API请求的挑战;只要要继承AbstractEdgeDispatcher笼统类,增加对应的逻辑就可:

public class EdgeDispatcher extends AbstractEdgeDispatcher{
private static final Logger LOGGER = LoggerFactory.getLogger(EdgeDispatcher.class);
//此Dispatcher的优先级,Order级越小,路由策略优先级越高
public int getOrder(){
return 10000;
}
//初始化Dispatcher的路由策略
public void init(Router router){
//捕获 {ServiceComb微服务Name}/{服务路径&amp;参数} 的URL
String regex ="/([^\\\\/]+)/(.*)";
router.routeWithRegex(regex).handler(CookieHandler.create());
router.routeWithRegex(regex).handler(createBodyHandler());
router.routeWithRegex(regex).failureHandler(this::onFailure).handler(this::onRequest);
}
//解决请求,请注意
private void onRequest(RoutingContext context){
Map pathParams = context.pathParams();
//从匹配的param0拿到{ServiceComb微服务Name}
final String service = pathParams.get("param0");
//从匹配的param1拿到{服务路径&amp;参数}
String path ="/"+ pathParams.get("param1");
//还记得我们之前说的做出一点点改进吗?引入一个自己设置配置edge.routing-short-path.{简称},映射微服务名;假如简称没有配置,那么就认为直接是微服务的名
final String serviceName = DynamicPropertyFactory.getInstance()
.getStringProperty("edge.routing-short-path."+ service, service).get();
//创立一个Edge转发
EdgeInvocation edgeInvocation =new EdgeInvocation();
//允许接受任意版本的微服务实例作为Provider,未来我们会用此(设置版本)能力实现灰度发布
edgeInvocation.setVersionRule(DefinitionConst.VERSION_RULE_ALL);
edgeInvocation.init(serviceName, context, path, httpServerFilters);
edgeInvocation.edgeInvoke();
}
}

第三步:加载调度器Dispatcher

ServiceComb Edge用SPI(ServiceProvider Interface)的方式加载已经编写好的调度器Dispatcher,在resources目录下创立META-INF.services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher配置文件,写入上一步EdgeDispatcher的类全名:

{EdgeDispatcher的包名}.EdgeDispatcher

第四步:配置microservice.yaml

边缘服务本身也是一个微服务,同样需要配置microservice.yaml:

APPLICATION_ID: scaffold
service_description:
name: edge-service
version: 0.0.1
servicecomb:
service:
registry:
#配置ServiceCenter使得Edge能够发现其余微服务
address: http://127.0.0.1:30100
#配置Rest Endpoint
rest:
address: 0.0.0.0:9090
#自己设置的简称机制配置(这是我们自行扩展实现的)
edge:
routing-short-path:
user: user-service

提醒:

  1. 除了配置Rest Endpoint,我们也支持配置Highway Endpoint,但Highway Endpoint只支持ServiceComb开发的微服务调使用;
  2. microservice.yaml中没有配置Handler,Edge支持所有Consumer端Handler,不支持Producer端Handler,调使用链原理如下:
「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

4、验证边缘服务

启动使用户微服务和Edge服务,用Postman [8]注册一个使用户:

「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

成功,现在我们用新注册的使用户名ldg登录:

「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

同样成功,并在Response中已经包含了正确的AUTHORIZATIONHeader。

5、性能比拼

ServiceComb JavaChassis也支持集成Netflix Zuul作为网关服务,我们做了一次性能比较,用ServiceComb Edge作为网关吞吐能力大幅优于Netflix Zuul,性能测试项目源代码在这里 [9]。

扩展边缘服务支持统一认证

1、设计思路

正如前面提到的,统一认证的目的是在Edge入口处进行访问认证,避免需要在所有的微服务中都承载重复的认证机制,因而:

  1. 我们先要将认证功能作为一个独立的Procuder发布出来,使Edge服务能够随时认证Token,我们将其命名为AuthenticationService,放在使用户服务中;
  2. 将无需认证的访问请求识别出来,包括:
「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

除此之外其余业务请求都需要做Token认证;

  1. Edge服务转发访问请求之前,对需要认证的请求先做统一认证,认证通过之后才转发,我们用HttpServerFilter扩展这个能力:
「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

统一认证流程时序图为:

「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

2、实现统一认证

第一步:发布认证服务

定义AuthenticationService

public interface AuthenticationService{
String validate(String token);
}
Copy

实现并发布AuthenticationService

@RestSchema(schemaId ="authentication")
@RequestMapping(path ="/")
public class AuthenticationServiceImpl implements AuthenticationService{
private final TokenStore tokenStore;
@Autowired
publicAuthenticationServiceImpl(TokenStore tokenStore){
this.tokenStore = tokenStore;
}
@Override
@GetMapping(path ="validate")
public String validate(String token){
String userName = tokenStore.validate(token);
if(userName == null){
throw new InvocationException(BAD_REQUEST,"incorrecttoken");
}
return userName;
}
}

第二步:实现统一认证AuthenticationFilter

public class AuthenticationFilter implements HttpServerFilter{
private final RestTemplate template =RestTemplateBuilder.create();
private static final String USER_SERVICE_NAME ="user-service";
public static final String EDGE_AUTHENTICATION_NAME ="edge-authentication-name";
private static final SetNOT_REQUIRED_VERIFICATION_USER_SERVICE_METHODS =new HashSet<>(
Arrays.asList("login","logon","validate"));
@Override
public int getOrder(){
return0;
}
@Override
public Response afterReceiveRequest(Invocation invocation,HttpServletRequestEx httpServletRequestEx){
if(isInvocationNeedValidate(invocation.getMicroserviceName(), invocation.getOperationName())){
String token = httpServletRequestEx.getHeader(AUTHORIZATION);
if(StringUtils.isNotEmpty(token)){
String userName = template
.getForObject("cse://"+ USER_SERVICE_NAME +"/validate?token={token}", String.class, token);
if(StringUtils.isNotEmpty(userName)){
//Add header
invocation.getContext().put(EDGE_AUTHENTICATION_NAME, userName);
}else{
return Response
.failResp(new InvocationException(Status.UNAUTHORIZED,"authentication failed, invalid token"));
}
}else{
return Response.failResp(
new InvocationException(Status.UNAUTHORIZED,"authenticationfailed, missing AUTHORIZATION header"));
}
}
return null;
}
private boolean isInvocationNeedValidate(String serviceName, String operationPath){
if(USER_SERVICE_NAME.equals(serviceName)){
for(String method :NOT_REQUIRED_VERIFICATION_USER_SERVICE_METHODS){
if(operationPath.startsWith(method)){
return false;
}
}
}
return true;
}
}
Copy

别忘了通过SPI机制加载它,在resources\META-INF\services目录中创立org.apache.servicecomb.common.rest.filter.HttpServerFilter文件:

org.apache.servicecomb.scaffold.edge.filter.AuthenticationFilter

第三步:在使用户微服务中添加修改密码的功能使用于验证

现有的login和logon都无需认证,因而我们在使用户微服务中添加需要认证的修改密码的功能使用于验证统一认证。

在UserService中增加修改密码

public interface UserService{
ResponseEntitylogon(UserDTO user);
ResponseEntitylogin(UserDTO user);
//需要认证的修改密码功能
ResponseEntitychangePassword(UserUpdateDTO userUpdate);
}

在UserServiceImpl中实现修改密码

@Override
@PostMapping(path ="changePassword")
public ResponseEntitychangePassword(@RequestBody UserUpdateDTO userUpdate){
if(validateUserUpdate(userUpdate)){
UserEntity dbUser = repository.findByName(userUpdate.getName());
if(dbUser != null){
if(dbUser.getPassword().equals(userUpdate.getOldPassword())){
dbUser.setPassword(userUpdate.getNewPassword());
repository.save(dbUser);
return newResponseEntity<>(true, HttpStatus.OK);
}
throw new InvocationException(BAD_REQUEST,"wrongpassword");
}
throw new InvocationException(BAD_REQUEST,"user namenot exist");
}
throw new InvocationException(BAD_REQUEST,"incorrectuser");
}

3、验证明现的统一认证

确认AuthenticationFilter在Edge服务中成功加载

在Edge服务的启动日志中能够找到:

2018-07-13 14:38:48,756 [INFO] 1.org.apache.servicecomb.scaffold.edge.filter.AuthenticationFilter.org.apache.servicecomb.foundation.common.utils.SPIServiceUtils.loadSortedService(SPIServiceUtils.java:79)

使用户登录

用zhengyangyong登录:

「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

拿到的Token值为:eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ6aGVuZ3lhbmd5b25nIiwiZXhwIjoxNTMwNjA4OTczfQ.90teWUNbypPZvds_SD7Kus_y7wLc4b6VzC_aIVg8sLItKxwQ0g4V9BDU665PlqQY5KM-mnk8y0R6ENL1T8YVFg

不带Authorization Header请求changePassword

「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

返回的失败信息是:authenticationfailed, missing AUTHORIZATION header

用错误的Token请求changePassword

「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

返回的失败信息是:authenticationfailed : InvocationException: code=400;msg=CommonExceptionData[message=incorrect token]

用正确的Token请求changePassword

「良心推荐」用户管理系统ServiceComb微服务化实战-PartI

修改密码成功。

这里可能有疑问,用zhengyangyong登录后,是可以通过这个Token修改其余使用户例如lidagang的密码的,这是由于我们目前构建的validate仅检查Token的有效性,而不做权限检查,基于RBAC的角色权限管理系统将会在未来构建。

提醒:

  1. AuthenticationFilter的完整代码 [10];
  2. HttpServerFilter的详情 [11]。

小结

本文详细详情了如何用http://start.servicecomb.io脚手架快速构建微服务项目、用领域驱动设计(Domain-Driven Design,DDD)设计地产CRM系统、用Edge Service构建统一认证边缘服务等内容。至此,一个地产用户关系管理系统的骨架已经初步搭建起来,剩下的板块,我们将在接下来的文章里详细详情

参考介绍链接地址

[1] https://martinfowler.com/tags/domain driven design.html

[2] https://en.wikipedia.org/wiki/Event_storming

[3] https://tools.ietf.org/html/rfc7519

[4] https://jwt.io/

[5] https://projects.spring.io/spring-data-jpa/

[6] https://spring.io/guides/gs/accessing-data-jpa/

[7] https://github.com/jwtk/jjwt

[8] https://www.getpostman.com/

[9] https://github.com/zhengyangyong/gateway-perf

[10] https://github.com/zhengyangyong/scaffold/blob/master/edge-service/src/main/java/org/apache/servicecomb/scaffold/edge/filter/AuthenticationFilter.java

[11] https://github.com/apache/incubator-servicecomb-docs/blob/master/java-chassis-reference/zh_CN/general-development/http-filter.md

ServiceComb相关资料

官方网站:

http://servicecomb.incubator.apache.org/

加入社区:

https://servicecomb.incubator.apache.org/cn/docs/join_the_community

JIRA:

https://issues.apache.org/jira/browse/SCB

ServiceComb Java-Chassis:

https://github.com/apache/incubator-servicecomb-java-chassis

ServiceComb Saga:

https://github.com/apache/incubator-servicecomb-saga

ServiceComb Service-Center:

https://github.com/apache/incubator-servicecomb-service-center

点击左下角“理解更多”,给SeriveComb加个Star

  • 全部评论(0)
最新发布的资讯信息
【系统环境|服务器应用】用python写一个简单的词法分析器(2018-12-17 22:49)
【系统环境|服务器应用】Requirejs 和 AngularJS 中依赖注入(2018-12-17 22:49)
【系统环境|服务器应用】Excel 数据验证常用以及自己设置用法(2018-12-17 22:48)
【系统环境|服务器应用】WordPress 站点数据手动备份(2018-12-17 22:48)
【系统环境|服务器应用】20 个常用的 Excel 快捷键,瞬间提升工作效率(2018-12-17 22:48)
【系统环境|服务器应用】Java这些多线程基础知识你会吗?(2018-12-17 22:48)
【系统环境|服务器应用】vue中$refs的用法及作用详解(2018-12-17 22:47)
【系统环境|服务器应用】【轻知识】跑grpc的一个demo——Building APIs with GRPC, PHP, and Golang(2018-12-17 22:47)
【系统环境|服务器应用】Golang学习笔记之包管理工具(govendor)(2018-12-17 22:47)
【系统环境|服务器应用】阿里云sn1ne实例和sn2ne实例比较(2018-12-17 22:47)
手机二维码手机访问领取大礼包
返回顶部