前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Jhipster技术栈定制 - 基于UAA的微服务之间安全调用

Jhipster技术栈定制 - 基于UAA的微服务之间安全调用

作者头像
羽客
修改2018-12-20 17:12:25
3.2K1
修改2018-12-20 17:12:25
举报
文章被收录于专栏:云原生架构实践

本文通过代码实例演示如何通过UAA实现微服务之间的安全调用。 uaa: 身份认证服务,同时也作为被调用的资源服务。服务端口9999。 microservice1: 调用uaa的消费者服务,服务端口8081。

1 准备工作

1.1 工程目录

代码语言:txt
复制
--| appstack
  |-- uaa
  |-- microservice1

1.2 启动相关组件

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

  • jhipster/jhipster-registry:v4.0.0
  • mysql:5
a, 启动一个Jhipster-Registry
代码语言:txt
复制
$ 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容器。
代码语言:txt
复制
$ 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。具体生成和配置详情,可以参考<a href="https://www.cnblogs.com/yorkwu/p/9330665.html" target="\_blank">这里</a>

2 核心代码

2.1 uaa源码

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

代码语言:txt
复制
$ 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

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

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

com.mycompany.appstack.client.OAuth2InterceptedFeignConfiguration

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

代码语言:txt
复制
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

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

代码语言:txt
复制
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

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

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

com.company.appstack.security.oauth2.OAuth2TokenEndpointClientAdapter

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

代码语言:txt
复制
   /**
	 * 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

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

代码语言:txt
复制
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

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

代码语言:txt
复制
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

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

代码语言:txt
复制
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
代码语言:txt
复制
# 防止第一次初始化restTemplate时超时
hystrix:
    share-security-context: true
    command:
        default:
            execution:
                isolation:
                    thread:
                        timeoutInMilliseconds: 10000
application-dev.yml
代码语言:txt
复制
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。

代码语言:txt
复制
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中的代码:

代码语言:txt
复制
@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错误:

代码语言:txt
复制
org.springframework.web.client.HttpClientErrorException: 401 Unauthorized

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

参考

完整源码

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-10-25 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 准备工作
    • 1.1 工程目录
      • 1.2 启动相关组件
        • 1.3 生成微服务工程
        • 2 核心代码
          • 2.1 uaa源码
            • 2.2 microservice源码
              • 2.3 microservice1配置文件
              • 3 测试效果
                • 3.1 通过UAA获取安全令牌的访问
                  • 3.2 没有通过UAA获取安全令牌的访问
                  • 参考
                  相关产品与服务
                  容器服务
                  腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档