来源:JAVA日知录 DailyMart是服务一个ToC的在线购物商城,目前仅支持通过浏览器访问。中何在商城中的实现所有操作都需要用户先登录。为了实现这一需求,多端我们可以采用以下技术方案: 详细交互流程如下图1所示: 这种架构在初期可以满足业务的发展需求。然而,随着业务的扩展,我们需要考虑到现在大部分用户使用手机进行购物的情况。因此,DailyMart也需要支持手机端访问。但与浏览器不同,手机端的源码下载认证机制可能会有所不同。 例如,浏览器端的Token有效期通常设定为1小时,而手机端的Token有效期通常设置为7天或更长。此外,浏览器端的Token采用JWT这种去中心化的认证机制,而手机端的Token采用中心化的认证机制,需要调用手机端服务进行登录认证。 同时,为了扩展业务,其他一些第三方应用可能也需要调用DailyMart的后端服务来获取数据,对于第三方的应用一般采用appId + appSecret的方式进行认证,同时需要对接口参数进行签名防止出现篡改和重放。(此方案在前文中有详细说明,可以通过链接跳转访问查看。) 现在的问题是,如何在原有架构的基础上满足这三种不同形式的认证需求呢? 要解决这个问题,最关键在于如何判断请求的来源,是香港云服务器来自浏览器端的请求、手机端的请求还是第三方的请求? 我们可以通过请求路径进行区分,对于不同端的请求使用不同的路径进行标识,可以做如下约定: 手机端请求,需要在请求路径上带有/ph/ 浏览器请求,需要在请求路径上带有/pd/ 第三方请求,需要在路径请求上带有/pt/ ... 最终规定接口的完整请求路径为:/服务名/api/来源标识/接口路径/,如:http://localhost:9090/customer-service/api/pd/customer/info 这样在SpringCloud Gateway网关先获取请求的路径,再根据请求的路径判断请求来源,最后根据请求来源实现不同的认证方案。 解决这个问题的关键在于如何判断请求的来源,即是来自浏览器端、手机端还是第三方应用? 我们可以通过请求路径进行区分,对于不同端的请求使用不同的路径进行标识。例如: 最终,我们规定接口的完整请求路径为:/服务名/api/来源标识/接口路径/,例如:http://localhost:9090/customer-service/api/pd/customer/info 这样,在SpringCloud Gateway网关中,我们需要创建一个过滤器,首先获取请求的路径,然后根据请求的路径判断请求来源,最后根据请求来源实现不同的认证方案。 有了解决方案,我们就很容易完成代码实现了。 为了满足多端认证的需求,在网关服务中我们可以抽取一个公共的认证接口ApiAuthenticator,具体的认证逻辑由具体实现类实现。 在上面的类图中,ProtectedApiAuthenticator用于实现第三方的认证逻辑,DefaultApiAuthenticator用于实现浏览器端的认证逻辑。 在网关过滤器ApiAuthenticatorFilter中,我们首先根据请求路径获取请求来源,然后根据请求来源找到对应的实现类。 4j { { URI uri = exchange.getRequest().getURI(); String rawPath = uri.getRawPath(); (handleExcludeUrl(rawPath)) { chain.filter(exchange); } ApiAuthenticator apiAuthenticator = getApiAuthenticator(rawPath); AuthenticatorResult authenticatorResult = apiAuthenticator.auth(exchange); (!authenticatorResult.isResult()) { HttpServerErrorException( HttpStatus.METHOD_NOT_ALLOWED, authenticatorResult.getMessage())); } chain.filter(exchange); } / * 确定认证策略 rawPath 请求路径 { ); ) { ]; (parameter) { ProtectedApiAuthenticator(); PrivateApiAuthenticator(); PublicApiAuthenticator(); DefaultApiAuthenticator(); + parameter); }; } DefaultApiAuthenticator(); } } 以下是浏览器端的认证逻辑,它会验证JWT token的有效性。如果token失效,则直接返回错误提示给用户,引导其重新登录。 4j { { ServerHttpRequest request = exchange.getRequest(); HttpHeaders httpHeaders = request.getHeaders(); String token = httpHeaders.getFirst(HttpHeaders.AUTHORIZATION); (Objects.nonNull(token)) { { String subjectFromJWT = JwtUtil.getSubjectFromJWT(token); , token, subjectFromJWT); mutateNewHeader(exchange, subjectFromJWT); ); (ParseException | JOSEException e) { ); ); } } ); } } 本文提出了一种灵活、可扩展的方案,以满足 DailyMart 在业务发展过程中的多端认证需求。通过使用请求路径区分不同端的请求来源,并在 SpringCloud Gateway 网关中实现相应的过滤器进行认证,方案具有灵活性、可扩展性和可维护性。同时,本文涉及的代码都已经上传至Github,感兴趣的可以通过文末方式获取。概述
多端认证需求
解决方案
代码实现
小结