zoukankan      html  css  js  c++  java
  • JHipster技术栈定制

    本文通过代码实例演示如何通过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。

    参考

    完整源码

  • 相关阅读:
    C++ 函数模板&类模板详解
    C++ const修饰不同类型的用法
    C++ 引用Lib和Dll的方法总结
    C#查询本机所有打印机
    C#如何设置桌面背景
    C#使用Aspose.Words把 word转成图片
    查看IP占用
    查看IP占用
    C# Dictionary判断Key是否存在
    C# 只有小时和分钟的两个时间进行对比
  • 原文地址:https://www.cnblogs.com/yorkwu/p/9851946.html
Copyright © 2011-2022 走看看