博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JHipster技术栈定制 - 基于UAA的微服务之间安全调用
阅读量:5948 次
发布时间:2019-06-19

本文共 10818 字,大约阅读时间需要 36 分钟。

本文通过代码实例演示如何通过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 APIpackage 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;@Componentpublic 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
formParams = new LinkedMultiValueMap<>(); formParams.set("grant_type", "client_credentials"); addAuthentication(reqHeaders, formParams); HttpEntity
> entity = new HttpEntity<>(formParams, reqHeaders); log.debug("contacting OAuth2 token endpoint to authenticate internal service."); ResponseEntity
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. */@Componentpublic 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
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;@Componentpublic 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个微服务已经注册成功。

1428428-20181025190456237-1777512620.png

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

http://localhost:8081/test/servicecall

可以看到uaa返回的结果:

1428428-20181025190505996-943763877.png

说明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

可以看到返回错误信息:

1428428-20181025190516794-1451623757.png

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

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

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

参考

转载于:https://www.cnblogs.com/yorkwu/p/9851946.html

你可能感兴趣的文章
Codeforces Round #249 (Div. 2) B. Pasha Maximizes
查看>>
MyEclipse 2015 Stable 2.0破解方法
查看>>
【Android游戏开发十一】手把手让你爱上Android sdk自带“9妹”(9patch 工具),让Android游戏开发更方便!...
查看>>
【查找算法】基于存储的查找算法(哈希查找)
查看>>
2012-04-12 工具箱中添加自定义控件的方法
查看>>
【分享】采用CMD/WMI获取共享文件夹——及2种方式的性能对比
查看>>
bootstrap学习笔记<二>(标题,段落样式)
查看>>
云平台的微服务架构实践
查看>>
Java语法学习2
查看>>
为什么要使用云盘?
查看>>
CRC冗余校验算法&&MTU
查看>>
【随笔】Java基础内容复习——最忌中断
查看>>
和项目组研究计算几何
查看>>
JavaWeb网上图书商城完整项目--day02-10.提交注册表单功能之页面实现
查看>>
47 _ 循环队列程序演示.swf
查看>>
django 学习-16 Django会话Cookie
查看>>
Tomcat组件梳理--Server
查看>>
记录一下这次web实训的两个网站
查看>>
POJ-1830 开关问题 高斯消元
查看>>
HDU-4366 Successor 线段树+预处理
查看>>