本文通过代码实例演示如何通过UAA实现微服务之间的安全调用。

uaa: 身份认证服务,同时也作为被调用的资源服务。服务端口9999。

microservice1: 调用uaa的消费者服务,服务端口8081。

1 准备工作

1.1 工程目录

--| appstack
|-- uaa
|-- microservice1

1.2 启动相关组件

为了简单起见,这里都使用容器启动相关组件,需要2个镜像,最好提前下载好。

  • jhipster/jhipster-registry:v4.0.0
  • mysql:5
a, 启动一个Jhipster-Registry
$ docker container run --name registry-app -e JHIPSTER.SECURITY.AUTHENTICATION.JWT.SECRET=dkk20dldkf0209342334 -e SPRING.PROFILES.ACTIVE=dev -d -p 8761:8761 jhipster/jhipster-registry:v4.0.0
b, 启动2个MySql容器。
$ docker container run --name uaa-mysql --privileged -e MYSQL_ROOT_PASSWORD=my-secret-pw -d -p 32900:3306 mysql:5
$ docker container run --name microservice1-mysql --privileged -e MYSQL_ROOT_PASSWORD=my-secret-pw -d -p 32800:3306 mysql:5

1.3 生成微服务工程

3个微服务都是通过Jhipster生成。 工程代码生成完之后,根据上一节启动的组件的实际情况,修改微服务配置文件中Eureka和database相关的配置。

这里使用的Jhipster版本为5.1.0。具体生成和配置详情,可以参考这里

2 核心代码

2.1 uaa源码

在uaa里面新增一个controller类,提供一个GET方法,作为被调用的API。

$ vi com.mycompany.appstack.web.rest.Provider
# 这里提供一个简单的GET API package com.mycompany.appstack.web.rest; import org.springframework.web.bind.annotation.*; /**
* REST controller for managing the current user's account.
*/
@RestController
@RequestMapping("/api")
public class ProviderResource { public ProviderResource () {
} /**
* GET /provider:
*/
@GetMapping("/provider")
public String provider() {
return "Hello, I'm uaa provider.";
} }

2.2 microservice源码

a, 用于服务间调用的FeignClient注解类。

com.mycompany.appstack.config.client.AuthorizedFeignClient

生成的代码中,这个类是默认存在的,不需要修改,除非你要修改这个默认的配置类名。

Class<?>[] configuration() default OAuth2InterceptedFeignConfiguration.class;
b, 将自定义OAuth2拦截器类注册到当前服务中的配置类。

com.mycompany.appstack.client.OAuth2InterceptedFeignConfiguration

生成的代码中,这个类是默认存在的,需要修改如下:

package com.mycompany.appstack.client;

import java.io.IOException;
import org.springframework.context.annotation.Bean;
import feign.RequestInterceptor; public class OAuth2InterceptedFeignConfiguration {
@Bean(name = "serviceFeignClientInterceptor")
public RequestInterceptor getFeignClientInterceptor() throws IOException {
return new ServiceFeignClientInterceptor();
}
}
c, 自定义OAuth2拦截器类。

com.mycompany.appstack.client.ServiceFeignClientInterceptor

这是一个新增的类,内容如下:

package com.mycompany.appstack.client;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.stereotype.Component; import com.mycompany.appstack.security.oauth2.ServiceTokenEndpointClient; import feign.RequestInterceptor;
import feign.RequestTemplate; @Component
public class ServiceFeignClientInterceptor implements RequestInterceptor { private final Logger log = LoggerFactory.getLogger(ServiceFeignClientInterceptor.class); private static final String AUTHORIZATION_HEADER = "Authorization"; private static final String BEARER_TOKEN_TYPE = "Bearer"; @Autowired
private ServiceTokenEndpointClient serviceTokenEndpointClient ; @Override
public void apply(RequestTemplate template) { OAuth2AccessToken oauthToken = serviceTokenEndpointClient .sendClentCredentialsGrant();
if (oauthToken != null) {
template.header(AUTHORIZATION_HEADER, String.format("%s %s", BEARER_TOKEN_TYPE, oauthToken.getValue()));
} }
}
d, 与UAA通讯的客户端接口,增加一个抽象方法。

com.mycompany.appstack.security.oauth2.OAuth2TokenEndpointClient

生成的代码中,这个类是默认存在的,需要增加如下方法:

    /**
* Send a client grant to the token endpoint.
*
* @return
*/
OAuth2AccessToken sendClentCredentialsGrant();
e, d的适配器类,增加对应的实现方法。

com.company.appstack.security.oauth2.OAuth2TokenEndpointClientAdapter

生成的代码中,这个类是默认存在的,需要增加如下方法:

   /**
* Sends a credentials grant to the token endpoint.
*
* @return the access token.
*/
@Override
public OAuth2AccessToken sendClentCredentialsGrant() {
HttpHeaders reqHeaders = new HttpHeaders();
reqHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> formParams = new LinkedMultiValueMap<>();
formParams.set("grant_type", "client_credentials");
addAuthentication(reqHeaders, formParams);
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(formParams, reqHeaders);
log.debug("contacting OAuth2 token endpoint to authenticate internal service.");
ResponseEntity<OAuth2AccessToken> responseEntity = restTemplate.postForEntity(getTokenEndpoint(), entity,
OAuth2AccessToken.class);
if (responseEntity.getStatusCode() != HttpStatus.OK) {
log.debug("failed to authenticate user with OAuth2 token endpoint, status: {}",
responseEntity.getStatusCodeValue());
throw new HttpClientErrorException(responseEntity.getStatusCode());
}
OAuth2AccessToken accessToken = responseEntity.getBody();
return accessToken;
} protected String getJhipsterClientSecret() {
String clientSecret = jHipsterProperties.getSecurity().getClientAuthorization().getClientSecret();
if (clientSecret == null) {
throw new InvalidClientException("no client-secret configured in application properties");
}
return clientSecret;
} protected String getJhipsterClientId() {
String clientId = jHipsterProperties.getSecurity().getClientAuthorization().getClientId();
if (clientId == null) {
throw new InvalidClientException("no client-id configured in application properties");
}
return clientId;
}
f, e的实现类,增加对应的实现方法。

com.mycompany.appstack.security.oauth2.ServiceTokenEndpointClient

这是一个新增的类,内容如下:

package com.mycompany.appstack.security.oauth2;

import com.mycompany.appstack.config.oauth2.OAuth2Properties;
import io.github.jhipster.config.JHipsterProperties;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.StandardCharsets; /**
* Client talking to UAA's token endpoint to do different OAuth2 grants.
*/
@Component
public class ServiceTokenEndpointClient extends OAuth2TokenEndpointClientAdapter implements OAuth2TokenEndpointClient { public ServiceTokenEndpointClient(@Qualifier("loadBalancedRestTemplate") RestTemplate restTemplate,
JHipsterProperties jHipsterProperties, OAuth2Properties oAuth2Properties) {
super(restTemplate, jHipsterProperties, oAuth2Properties);
} @Override
protected void addAuthentication(HttpHeaders reqHeaders, MultiValueMap<String, String> formParams) {
reqHeaders.add("Authorization", getAuthorizationHeader());
} /**
* @return a Basic authorization header to be used to talk to UAA.
*/
protected String getAuthorizationHeader() {
String clientId = getJhipsterClientId();
String clientSecret = getJhipsterClientSecret();
String authorization = clientId + ":" + clientSecret;
return "Basic " + Base64Utils.encodeToString(authorization.getBytes(StandardCharsets.UTF_8));
} }
g, 调用uaa服务的Feign客户端类

com.mycompany.appstack.client.feign.BaseUaaAuthFeignClient

这是一个新增的类,内容如下:


package com.mycompany.appstack.client.feign; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import com.mycompany.appstack.client.AuthorizedFeignClient; @AuthorizedFeignClient(name = "uaa", fallback = CallUaaAuthFeignClientHystrix.class)
public interface CallUaaAuthFeignClient { @RequestMapping(value = "/api/provider", method = RequestMethod.GET)
String callProvider();
}
h, g类的断路器类

com.mycompany.appstack.client.feign.CallUaaAuthFeignClientHystrix

这是一个新增的类,内容如下:

package com.mycompany.appstack.client.feign;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; @Component
public class CallUaaAuthFeignClientHystrix implements CallUaaAuthFeignClient {
private final Logger log = LoggerFactory.getLogger(this.getClass()); @Override
public String callProvider() {
log.error("调用uaa provider接口失败!");
return "调用uaa provider接口失败!";
} }

2.3 microservice1配置文件

application.yml
# 防止第一次初始化restTemplate时超时
hystrix:
share-security-context: true
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 10000
application-dev.yml
jhipster:
security:
client-authorization:
access-token-uri: http://uaa/oauth/token // 从uaa获取token的uri
token-service-id: uaa
client-id: internal // 和uaa的对应配置文件项保持一致
client-secret: internal // 和uaa的对应配置文件项保持一致

3 测试效果

3.1 通过UAA获取安全令牌的访问

a, 在microservice1中新增一个controller类

这个类提供一个测试API,我们通过浏览器访问这个API,间接调用CallUaaAuthFeignClient。

package com.mycompany.appstack.web.rest;

import com.mycompany.appstack.client.feign.CallUaaAuthFeignClient;
import com.mycompany.appstack.service.RoleService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; /**
* REST controller for Test AuthFeignClient.
*/
@RestController
@RequestMapping("/test")
public class CallUaaResource { private final Logger log = LoggerFactory.getLogger(CallUaaResource.class); @Autowired
private CallUaaAuthFeignClient callUaaAuthFeignClient; public CallUaaResource(RoleService roleService) { } /**
* GET /servicecall :
*
* @return
*/
@GetMapping("/servicecall")
public String getProvider() {
log.debug("REST request to get provider from uaa.");
return callUaaAuthFeignClient.callProvider();
} }
b, 编译运行uaa,microservice1

如果一切正常,会看到Jhipster-Registry的Web UI中2个微服务已经注册成功。

c, 浏览器访问microservice1的测试API

http://localhost:8081/test/servicecall

可以看到uaa返回的结果:

说明microservice1从uaa获取token之后,成功访问了uaa的一个受限访问的API。

3.2 没有通过UAA获取安全令牌的访问

a, 注释掉从uaa获取安全令牌的代码

注释掉ServiceFeignClientInterceptor中的代码:

@Override
public void apply(RequestTemplate template) {
//OAuth2AccessToken oauthToken = uaaTokenEndpointServiceClient.sendClentCredentialsGrant();
//if (oauthToken != null) {
//template.header(AUTHORIZATION_HEADER, String.format("%s %s", BEARER_TOKEN_TYPE, oauthToken.getValue()));
//} }
b, 重新编译运行microservice1
c, 浏览器访问microservice1的测试API

http://localhost:8081/test/servicecall

可以看到返回错误信息:

查看microservice1的日志,报401错误:

org.springframework.web.client.HttpClientErrorException: 401 Unauthorized

说明microservice没有从uaa获取token,所以无法访问uaa的受限访问的API。

参考

完整源码

JHipster技术栈定制 - 基于UAA的微服务之间安全调用的更多相关文章

  1. SpringCloud实战 | 第五篇:SpringCloud整合OpenFeign实现微服务之间的调用

    一. 前言 微服务实战系列是基于开源微服务项目 有来商城youlai-mall 版本升级为背景来开展的,本篇则是讲述SpringCloud整合OpenFeign实现微服务之间的相互调用,有兴趣的朋友可 ...

  2. JHipster技术栈定制 - JHipster Registry消息总线配置

    本文说明了如何定制化JHipster-Registry,增加消息总线功能. 实现的效果就是修改配置中心的文件后,通过消息队列主动推送给微服务而无需重启微服务,实现配置内容热加载. 1 整体规划 1.1 ...

  3. 微服务之间的调用(Ribbon与Feign)

    来源:https://blog.csdn.net/jrn1012/article/details/77837658 使用Eureka作为服务注册中心,在服务启动后,各个微服务会将自己注册到Eureka ...

  4. JHipster技术栈定制 - JHipster Registry配置信息加密

    本文说明了如何开启和使用JHipster-Registry的加解密功能. 1 整体规划 1.1 名词说明 名词 说明 备注 对称加密 最快速.最简单的一种加密方式,加密(encryption)与解密( ...

  5. JHipster技术栈理解 - UAA原理分析

    本文简要分析了UAA的认证机制和部分源码功能. UAA全称User Account and Authentication. 相关源码都是通过Jhipster生成,包括UAA,Gateway,Ident ...

  6. 基于 Docker 的微服务架构实践

    本文来自作者 未闻 在 GitChat 分享的{基于 Docker 的微服务架构实践} 前言 基于 Docker 的容器技术是在2015年的时候开始接触的,两年多的时间,作为一名 Docker 的 D ...

  7. iUAP云运维平台v3.0全面支持基于K8s的微服务架构

    什么是微服务架构? 微服务(MicroServices)架构是当前互联网业界的一个技术热点,业内各公司也都纷纷开展微服务化体系建设.微服务架构的本质,是用一些功能比较明确.业务比较精练的服务去解决更大 ...

  8. 用友iuap云运维平台支持基于K8s的微服务架构

    什么是微服务架构? 微服务(MicroServices)架构是当前互联网业界的一个技术热点,业内各公司也都纷纷开展微服务化体系建设.微服务架构的本质,是用一些功能比较明确.业务比较精练的服务去解决更大 ...

  9. spring cloud实战与思考(二) 微服务之间通过fiegn上传一组文件(上)

    需求场景: 微服务之间调用接口一次性上传多个文件. 上传文件的同时附带其他参数. 多个文件能有效的区分开,以便进行不同处理. Spring cloud的微服务之间接口调用使用Feign.原装的Feig ...

随机推荐

  1. String补充

    String类不可变和可变字符序列区别_字符串比较 String类对象代表不可变的Unicode字符序列,因此我们可以将String对象称为“不可变对象”.也就是指对象内部成员变量的值无法再改变, p ...

  2. Python中面向对象的概念(科普)

    面向对象(OOP)基本概念 面向对象编程 —— Object Oriented Programming 简写 OOP 目标 了解 面向对象 基本概念 01. 面向对象基本概念 我们之前学习的编程方式就 ...

  3. python高级-动态特性(20)

    一.动态语⾔的定义 动态语言是在运行时确定数据类型的语言.变量使用之前不需要类型声明,通常变量的类型是被赋值的那个值的类型.现在比较热门的动态语言有:Python.PHP.JavaScript.Obj ...

  4. 在Mac上使用vs-code快速上手c语言学习(入门文,老鸟退散)

    天下事,合久必分.分久必合,你肯定想不到当你逃离到Mac平台这么多年之后,有一天你会再用微软的产品来写代码 :) 其实微软的产品虽然用户体验总是做不到最好,但整体上的确拉低了行业的进入门槛,对于编程也 ...

  5. Config非对称加解密

    对称加密和非对称加密区别1. 对称加密对称加密指的就是加密和解密使用同一个秘钥,所以叫做对称加密.对称加密只有一个秘钥,作为私钥. 常见的对称加密算法:DES,AES,3DES等等. 2. 非对称加密 ...

  6. 客户端缓存机制 - Cookie详解

    Cookie 作者:Stanley 罗昊 [转载请注明出处和署名,谢谢!] Cookie不是内置对象,所以用的时候需要new出来,Cookie是由服务端产生的,再发送给客户端保存,它不是内置对象,却是 ...

  7. RabbitMQ消息队列(六)-消息任务分发与消息ACK确认机制(.Net Core版)

    在前面一章介绍了在.Net Core中如何使用RabbitMQ,至此入门的的部分就完成了,我们内心中一定还有很多疑问:如果多个消费者消费同一个队列怎么办?如果这几个消费者分任务的权重不同怎么办?怎么把 ...

  8. 【Java基础】【16List集合】

    16.01_集合框架(去除ArrayList中重复字符串元素方式)(掌握) A:案例演示 需求:ArrayList去除集合中字符串的重复值(字符串的内容相同) 思路:创建新集合方式 /** * A:案 ...

  9. jaeger 使用初探

    导读:有一天我们接到这样一条客诉“你们的收银软件最近特别慢,严重影响我们的收银效率,再不解决我们就不用了”,我相信大家应该都遇到过这种问题,即使现在没遇到,将来一定会遇到的,那遇到了怎么办呢?就这个话 ...

  10. WPF中Datagrid控件添加行号

    /// <summary> /// 导入Excel文件按钮 /// </summary> /// <param name="sender">&l ...