本文介绍了如何使用OAuth2保护2个Spring Boot微服务之间的通信?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在学习有关使用基本身份验证和OAuth2 JWT令牌身份验证来保护微服务的信息.我使用基本身份验证实现了它,现在我想在OAuth2身份验证中对其进行转换.

这是使用基本身份验证来保护这两个微服务之间的通信的实现.

微服务1-REST API

  @Configuration@盖特公共类DemoApiConfiguration {@Value("$ {demo.api.credentials.username}")私有字符串用户名;@Value("$ {demo.api.credentials.password}")私有字符串密码;} 

SecurityConfigurer类:

  @Configuration@RequiredArgsConstructor公共类SecurityConfigurer扩展了WebSecurityConfigurerAdapter {私有最终的DemoApiConfiguration apiConfig;@Override受保护的void configure(HttpSecurity http)抛出异常{http.csrf().disable().authorizeRequests().anyRequest().authenticated().和().httpBasic();}@豆角,扁豆public UserDetailsS​​ervice userDetailsS​​ervice(PasswordEncoder passwordEncoder){UserDetails theUser = User.withUsername(apiConfig.getUsername()).password(passwordEncoder.encode(apiConfig.getPassword())).roles("USER").build();InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();userDetailsManager.createUser(theUser);返回userDetailsManager;}@豆角,扁豆public PasswordEncoder passwordEncoder(){返回新的BCryptPasswordEncoder();}} 

控制器类:

  @RestController@RequestMapping("/rest/api/v1")公共类HomeController {@GetMapping("/products")公共字符串home(){返回这些是产品!";}} 

application.yml:

  demo:api:证书:用户名:$ {demo_api_username:john}密码:$ {demo_api_password:test} 

微服务2-REST使用者

  @Configuration@盖特公共类DemoApiConfiguration {@Value("$ {demo.api.credentials.username}")私有字符串用户名;@Value("$ {demo.api.credentials.password}")私有字符串密码;@Value("$ {demo.api.credentials.basePath}")私有String basePath;} 

WebConfigurer类:

  @Configuration@RequiredArgsConstructor公共类WebConfigurer {私有最终的DemoApiConfiguration apiConfig;@豆角,扁豆公共ApiClient restTemplate(){RestTemplate restTemplate =新的RestTemplate();ApiClient apiClient = new ApiClient(restTemplate);apiClient.setBasePath(apiConfig.getBasePath());返回apiClient;}公共字符串getAuthorization(){return(!StringUtils.isEmpty(apiConfig.getUsername())&&!StringUtils.isEmpty(apiConfig.getPassword()))吗?基本"+ Base64Utils.encodeToString((apiConfig.getUsername()+:"+ apiConfig.getPassword()).getBytes()):空值;}} 

ApiClient类:

  @Getter@RequiredArgsConstructor@ slf4j公共类ApiClient {私有静态最终字符串AUTHORIZATION_HEADER ="Authorization";私有最终RestTemplate restTemplate;私有String basePath;公共ApiClient setBasePath(String basePath){this.basePath = basePath;返回这个;}公开字串invokeApi(字串路径,字串凭证){UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(basePath).path(path);RequestEntity.BodyBuilder requestBuilder =RequestEntity.method(HttpMethod.GET,builder.build().toUri());requestBuilder.contentType(MediaType.APPLICATION_JSON);requestBuilder.header(AUTHORIZATION_HEADER,凭证);RequestEntity< Object>requestEntity = requestBuilder.body(null);返回restTemplate.exchange(requestEntity,String.class).getBody();}} 

ConsumeController类:

  @RestController@RequiredArgsConstructor公共类ConsumeController {私有静态最终字符串PATH ="/rest/api/v1/products";私有最终WebConfigurer webConfigurer;私有的最终ApiClient apiClient;@GetMapping(value ="/products-client")公共字符串getProductList(){返回apiClient.invokeApi(PATH,webConfigurer.getAuthorization());}} 

application.yml:

 服务器:端口:8090演示:api:证书:用户名:$ {demo_api_username:john}密码:$ {demo_api_password:test}basePath:$ {demo_api_path:http://localhost:8080} 

因此,第一个微服务是REST API,第二个微服务是REST使用者,并且使用基本身份验证来保护通信.

现在,我想使用OAuth2实施,我想问你如何使用OAuth2保护通信?因此,我想添加另一个端点,例如"/access-token",客户端首先将在该端点使用用户名和密码进行请求,并获得一个jwt令牌.之后,将请求"/products".使用此jwt令牌的具有Authorization标头的端点.您能帮我做这种实现吗?谢谢!

解决方案

微服务体系结构

理想的方法或通常首选的方法是微服务的API网关模式,但是它可能会根据项目和要求而变化.让我们考虑以下组件

配置服务器:负责管理微服务的配置,我们可能会使用具有与Kafka或RabbitMQ通用的总线接口的Spring Cloud功能来动态更改配置.

API网关:这将是管理针对其他服务的REST请求的常见入口点.我们可以在此处使用负载均衡器管理请求.另外,我们可以从API网关提供用户界面.

身份验证服务(UAA):这应该负责管理用户管理和相关活动.在这里,您将添加 @EnableAuthorizationServer 并扩展 AuthorizationServerConfigurerAdapter

  @Override公共无效configure(ClientDetailsS​​erviceConfigurer客户端)引发异常{int accessTokenValidity = uaaProperties.getWebClientConfiguration().getAccessTokenValidityInSeconds();accessTokenValidity = Math.max(accessTokenValidity,MIN_ACCESS_TOKEN_VALIDITY_SECS);int refreshTokenValidity = uaaProperties.getWebClientConfiguration().getRefreshTokenValidityInSecondsForRememberMe();refreshTokenValidity = Math.max(refreshTokenValidity,accessTokenValidity);/*为了更好的客户端设计,这应该由ClientDetailsS​​ervice(类似于UserDetailsS​​ervice)完成.*/client.inMemory().withClient(uaaProperties.getWebClientConfiguration().getClientId()).secret(passwordEncoder.encode(uaaProperties.getWebClientConfiguration().getSecret())).scopes("openid").autoApprove(真).authorizedGrantTypes(隐式","refresh_token",密码","authorization_code").accessTokenValiditySeconds(accessTokenValidity).refreshTokenValiditySeconds(refreshTokenValidity).和().withClient(applicationProperties.getSecurity().getClientAuthorization().getClientId()).secret(passwordEncoder.encode(applicationProperties.getSecurity().getClientAuthorization().getClientSecret())).scopes(网络应用").authorities("ROLE_GA").autoApprove(真).authorizedGrantTypes("client_credentials").accessTokenValiditySeconds((int)jHipsterProperties.getSecurity().getAuthentication().getJwt().getTokenValidityInSeconds()).refreshTokenValiditySeconds((int)jHipsterProperties.getSecurity().getAuthentication().getJwt().getTokenValidityInSecondsForRememberMe());} 

服务1,服务2 ... 这将是微服务,用于管理业务逻辑和需求,通常称为资源服务器,可以使用 ResourceServerConfigurerAdapter

对其进行配置.


管理访问和刷新令牌

如上所述,API网关是请求的公共入口点.我们可以在API网关中管理登录/注销API.当用户执行登录时,我们可以使用身份验证服务和 org.springframework.security.oauth2.common.OAuth2AccessToken 中的 OAuth2TokenEndpointClient 管理授权授予类型.OAuth2AccessToken sendPasswordGrant(字符串用户名,字符串密码); 和 OAuth2AccessToken sendRefreshGrant(String refreshTokenValue); 方法.

身份验证服务将根据配置和登录用户提供 OAuth2AccessToken .在 OAuth2AccessToken 内部,您将获得 access_token refresh_token OAuth2 expires_in 范围 .

在认证时,将创建两个JWT-访问令牌刷新令牌.刷新令牌将具有更长的有效期.这两个令牌都将写在 cookies 中,以便在随后的每个请求中发送.

在每个REST API调用中,将从HTTP标头中检索令牌.如果访问令牌是未过期 ,请检查用户的特权并相应地允许访问.如果访问令牌已已过期 ,但刷新令牌已有效 ,请重新创建访问令牌并使用新的到期日期刷新令牌,并通过 Cookies

发送回

 /***通过用户名和密码对用户进行身份验证.** @param请求来自客户端的请求.* @param response响应返回到服务器.* @param loginVM包含用户名,密码和记住我的参数.* @将{@link OAuth2AccessToken}作为{@link ResponseEntity}返回.如果成功,将返回{@code OK(200)}.*如果UAA无法验证用户身份,则将返回UAA返回的状态码.*/公共ResponseEntity< OAuth2AccessToken>authenticate(HttpServletRequest请求,HttpServletResponse响应,LoginVM loginVM){尝试 {字符串用户名= loginVM.getUsername();字符串密码= loginVM.getPassword();boolean RememberMe = loginVM.isRememberMe();OAuth2AccessToken accessToken = AuthorizationClient.sendPasswordGrant(用户名,密码);OAuth2Cookies cookie =新的OAuth2Cookies();cookieHelper.createCookies(request,accessToken,RememberMe,Cookies);cookies.addCookiesTo(response);如果(log.isDebugEnabled()){log.debug(成功认证的用户{}",用户名);}返回ResponseEntity.ok(accessToken);} catch(HttpStatusCodeException in4xx){抛出新的UAAException(ErrorConstants.BAD_CREDENTIALS);}catch(ResourceAccessException in5xx){抛出新的UAAException(ErrorConstants.UAA_APPLICATION_IS_NOT_RESPONDING);}}/***尝试使用作为cookie提供的刷新令牌刷新访问令牌.*请注意,浏览器通常并行发送多个请求,这意味着访问令牌*将在多个线程上过期.不过,我们不希望向UAA发送多个请求,*因此,我们需要将结果缓存一定的时间,并同步线程以避免发送*多个并行请求.** @param请求该请求可能包含刷新令牌.* @param response该响应设置新的cookie(如果刷新成功).* @param refreshCookie刷新令牌cookie.不能为null.* @返回包含更新的cookie的新servlet请求,以用于向下游中继.*/公共HttpServletRequest refreshToken(HttpServletRequest请求,HttpServletResponse响应,CookierefreshCookie){//检查非记住我"会话是否已过期如果(cookieHelper.isSessionExpired(refreshCookie)){log.info(会话由于不活动而过期");注销(请求,响应);//注销以清除浏览器中的cookie返回stripTokens(request);//不要在下游包含Cookie}OAuth2Cookies cookie = getCachedCookies(refreshCookie.getValue());已同步(Cookie){//检查是否已有其他线程的结果if(cookies.getAccessTokenCookie()== null){//否,我们是第一位!//发送refresh_token授权给UAA,获取新令牌字符串refreshCookieValue = OAuth2CookieHelper.getRefreshTokenValue(refreshCookie);OAuth2AccessToken accessToken = authorizationClient.sendRefreshGrant(refreshCookieValue);boolean RememberMe = OAuth2CookieHelper.isRememberMe(refreshCookie);cookieHelper.createCookies(request,accessToken,RememberMe,Cookies);//添加Cookie以响应以更新浏览器cookies.addCookiesTo(response);} 别的 {log.debug(重用缓存的refresh_token授权");}//将原始请求中的Cookie替换为新的CookieCookieCollection requestCookies =新的CookieCollection(request.getCookies());requestCookies.add(cookies.getAccessTokenCookie());requestCookies.add(cookies.getRefreshTokenCookie());返回新的CookiesHttpServletRequestWrapper(request,requestCookies.toArray());}} 


微服务之间的安全通信

我们可以使用 FeignClient在服务之间进行通信,并可以通过自定义配置来保护通信.请参见 Class<?> [] configuration()默认OAuth2UserClientFeignConfiguration.class;

此处,我们通过 AuthorizedUserFeignClient 接口增强了默认的 @FeignClient ,该接口由自定义配置组成,为 OAuth2UserClientFeignConfiguration ,其中包括 @Bean/code>用于 UserFeignClientInterceptor ,它使用标头管理自动化

AuthorizedUserFeignClient.java

  @Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@记录@FeignClient公共@interface AuthorizedUserFeignClient {@AliasFor(注释= FeignClient.class,属性=名称")字符串name()默认为"./***伪装客户端的自定义{@code @Configuration}.**可以包含构成客户端的部分的覆盖{@code @Bean}定义,例如{@link* feign.codec.Decoder},{@ link feign.codec.Encoder},{@ link feign.Contract}.** @默认值请参见FeignClientsConfiguration.*/@AliasFor(注释= FeignClient.class,属性=配置")类<?> [] configuration()默认为OAuth2UserClientFeignConfiguration.class;/***绝对URL或可解析的主机名(协议是可选的).*/字符串url()默认为"./***是否应解码404而不是抛出FeignExceptions.*/boolean encode404()默认为false;/***指定的Feign客户端接口的后备类.后备类必须实现接口*带有此注释的注释,它是有效的Spring bean.*/类<?>fallback()默认void.class;/***所有方法级映射将使用的路径前缀.可以与{@code @RibbonClient}一起使用,也可以不一起使用.*/字符串path()默认为".} 

UserFeignClientInterceptor.java

 公共类UserFeignClientInterceptor实现RequestInterceptor {私有静态最终字符串AUTHORIZATION_HEADER ="Authorization";私有静态最终字符串BEARER_TOKEN_TYPE ="Bearer";@Override公共无效申请(RequestTemplate模板){SecurityContext securityContext = SecurityContextHolder.getContext();身份验证身份验证= securityContext.getAuthentication();if(authentication!= null& authentication.getDetails()OAuth2AuthenticationDetails实例){OAuth2AuthenticationDetails details =(OAuth2AuthenticationDetails)authentication.getDetails();template.header(AUTHORIZATION_HEADER,String.format(%s%s",BEARER_TOKEN_TYPE,details.getTokenValue()));}}} 


可能会有所帮助

架构概述

管理身份验证服务

I'm learning about securing microservices with Basic Authentication and OAuth2 JWT Token Authentication. I implemented it using Basic Authentication and now I want to transform it in OAuth2 Authentication.

This is the implementation for securing the communication between these 2 microservices using Basic Auth.

Microservice 1 - REST API

@Configuration
@Getter
public class DemoApiConfiguration {
    @Value("${demo.api.credentials.username}")
    private String username;

    @Value("${demo.api.credentials.password}")
    private String password;
}

SecurityConfigurer class:

@Configuration
@RequiredArgsConstructor
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
    private final DemoApiConfiguration apiConfig;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .authorizeRequests().anyRequest().authenticated()
                .and()
                .httpBasic();
    }

    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {

        UserDetails theUser = User.withUsername(apiConfig.getUsername())
                .password(passwordEncoder.encode(apiConfig.getPassword())).roles("USER").build();

        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
        userDetailsManager.createUser(theUser);

        return userDetailsManager;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Controller class:

@RestController
@RequestMapping("/rest/api/v1")
public class HomeController {

    @GetMapping("/products")
    public String home() {
        return "These are products!";
    }
}

application.yml:

demo:
  api:
    credentials:
      username: ${demo_api_username:john}
      password: ${demo_api_password:test}

Microservice 2 - REST Consumer

@Configuration
@Getter
public class DemoApiConfiguration {
    @Value("${demo.api.credentials.username}")
    private String username;

    @Value("${demo.api.credentials.password}")
    private String password;

    @Value("${demo.api.credentials.basePath}")
    private String basePath;
}

WebConfigurer class:

@Configuration
@RequiredArgsConstructor
public class WebConfigurer {

    private final DemoApiConfiguration apiConfig;

    @Bean
    public ApiClient restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        ApiClient apiClient = new ApiClient(restTemplate);
        apiClient.setBasePath(apiConfig.getBasePath());

        return apiClient;
    }

    public String getAuthorization() {
        return (!StringUtils.isEmpty(apiConfig.getUsername()) &&
                !StringUtils.isEmpty(apiConfig.getPassword())) ?
                "Basic " + Base64Utils.encodeToString((
                        apiConfig.getUsername() + ":" + apiConfig.getPassword())
                        .getBytes()) :
                null;
    }
}

ApiClient class:

@Getter
@RequiredArgsConstructor
@Slf4j
public class ApiClient {

    private static final String AUTHORIZATION_HEADER = "Authorization";
    private final RestTemplate restTemplate;
    private String basePath;

    public ApiClient setBasePath(String basePath) {
        this.basePath = basePath;
        return this;
    }

    public String invokeApi(String path, String credentials) {
        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(basePath).path(path);

        RequestEntity.BodyBuilder requestBuilder =
                RequestEntity.method(HttpMethod.GET, builder.build().toUri());

        requestBuilder.contentType(MediaType.APPLICATION_JSON);
        requestBuilder.header(AUTHORIZATION_HEADER, credentials);

        RequestEntity<Object> requestEntity = requestBuilder.body(null);

        return restTemplate
                .exchange(requestEntity, String.class).getBody();
    }
}

ConsumeController class:

@RestController
@RequiredArgsConstructor
public class ConsumeController {

    private static final String PATH = "/rest/api/v1/products";
    private final WebConfigurer webConfigurer;
    private final ApiClient apiClient;

    @GetMapping(value = "/products-client")
    public String getProductList() {

        return apiClient.invokeApi(PATH, webConfigurer.getAuthorization());
    }
}

application.yml:

server:
  port: 8090

demo:
  api:
    credentials:
      username: ${demo_api_username:john}
      password: ${demo_api_password:test}
      basePath: ${demo_api_path:http://localhost:8080}

So the first microservice is a REST API and the second microservice is a REST consumer and the communication is secured using Basic Auth.

Now I want to implement using OAuth2, and I want to ask you how can I secure the communication using OAuth2? So I want to add another endpoint like "/access-token", and the client first will do a request at this endpoint with username and password and will get a jwt token. After that will do a request for "/products" endpoint with Authorization header using this jwt token. Can you help me to do this kind of implementation? Thank you!

解决方案

Microservice Architecture

The ideal way or commonly preferred way is the API Gateway Pattern for the microservices however it may change according to the projects and requirements. Let's consider the following components

Config Server:Responsible to manage the configurations for the microservices and we may change the configurations dynamically using spring cloud features with a common bus interface with Kafka or RabbitMQ

API Gateway:This will be the common entry point to manage the REST request for other services. We can manage the requests using a load balancer here. Also, we can serve the UI from the API Gateway.

Authentication Service (UAA):This should be responsible for managing the user management and related activity. This is where you will add @EnableAuthorizationServer and extend AuthorizationServerConfigurerAdapter

 @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        int accessTokenValidity = uaaProperties.getWebClientConfiguration().getAccessTokenValidityInSeconds();
        accessTokenValidity = Math.max(accessTokenValidity, MIN_ACCESS_TOKEN_VALIDITY_SECS);
        int refreshTokenValidity = uaaProperties.getWebClientConfiguration().getRefreshTokenValidityInSecondsForRememberMe();
        refreshTokenValidity = Math.max(refreshTokenValidity, accessTokenValidity);
        /*
        For a better client design, this should be done by a ClientDetailsService (similar to UserDetailsService).
         */
        clients.inMemory()
            .withClient(uaaProperties.getWebClientConfiguration().getClientId())
            .secret(passwordEncoder.encode(uaaProperties.getWebClientConfiguration().getSecret()))
            .scopes("openid")
            .autoApprove(true)
            .authorizedGrantTypes("implicit","refresh_token", "password", "authorization_code")
            .accessTokenValiditySeconds(accessTokenValidity)
            .refreshTokenValiditySeconds(refreshTokenValidity)
            .and()
            .withClient(applicationProperties.getSecurity().getClientAuthorization().getClientId())
            .secret(passwordEncoder.encode(applicationProperties.getSecurity().getClientAuthorization().getClientSecret()))
            .scopes("web-app")
            .authorities("ROLE_GA")
            .autoApprove(true)
            .authorizedGrantTypes("client_credentials")
            .accessTokenValiditySeconds((int) jHipsterProperties.getSecurity().getAuthentication().getJwt().getTokenValidityInSeconds())
            .refreshTokenValiditySeconds((int) jHipsterProperties.getSecurity().getAuthentication().getJwt().getTokenValidityInSecondsForRememberMe());
    }

Service 1, Service 2...This will be the microservice to manage the business logic and requirements which is commonly known as Resource Server which can be configured with ResourceServerConfigurerAdapter

Diagram


Managing Access and Refresh Tokens

As mentioned API Gateway is the common entry point for the requests. We can manage the login/logout API in the API Gateway. When the user performs the log in and we can manage the authorization grant type using authentication service and OAuth2TokenEndpointClient from org.springframework.security.oauth2.common.OAuth2AccessToken using OAuth2AccessToken sendPasswordGrant(String username, String password); and OAuth2AccessToken sendRefreshGrant(String refreshTokenValue); methods.

The authentication service will provide the OAuth2AccessToken based on the configurations and login users. Inside OAuth2AccessToken you will get access_token, refresh_token, OAuth2, expires_in, scope.

At the time of authentication, two JWTs will be created - access token and refresh token. Refresh token will have longer validity. Both the tokens will be written in cookies so that they are sent in every subsequent request.

On every REST API call, the tokens will be retrieved from the HTTP header. If the access token is not expired, check the privileges of the user and allow access accordingly. If the access token is expired but the refresh token is valid, recreate new access token and refresh token with new expiry dates and sent back through Cookies

/**
     * Authenticate the user by username and password.
     *
     * @param request  the request coming from the client.
     * @param response the response going back to the server.
     * @param loginVM   the params holding the username, password and rememberMe.
     * @return the {@link OAuth2AccessToken} as a {@link ResponseEntity}. Will return {@code OK (200)}, if successful.
     * If the UAA cannot authenticate the user, the status code returned by UAA will be returned.
     */
    public ResponseEntity<OAuth2AccessToken> authenticate(HttpServletRequest request, HttpServletResponse response,
                                                          LoginVM loginVM) {
        try {
            String username = loginVM.getUsername();
            String password = loginVM.getPassword();
            boolean rememberMe = loginVM.isRememberMe();
            OAuth2AccessToken accessToken = authorizationClient.sendPasswordGrant(username, password);
            OAuth2Cookies cookies = new OAuth2Cookies();
            cookieHelper.createCookies(request, accessToken, rememberMe, cookies);
            cookies.addCookiesTo(response);
            if (log.isDebugEnabled()) {
                log.debug("successfully authenticated user {}", username);
            }
            return ResponseEntity.ok(accessToken);
        } catch (HttpStatusCodeException in4xx) {
            throw new UAAException(ErrorConstants.BAD_CREDENTIALS);
        }
        catch (ResourceAccessException in5xx) {
            throw new UAAException(ErrorConstants.UAA_APPLICATION_IS_NOT_RESPONDING);
        }
    }

    /**
     * Try to refresh the access token using the refresh token provided as cookie.
     * Note that browsers typically send multiple requests in parallel which means the access token
     * will be expired on multiple threads. We don't want to send multiple requests to UAA though,
     * so we need to cache results for a certain duration and synchronize threads to avoid sending
     * multiple requests in parallel.
     *
     * @param request       the request potentially holding the refresh token.
     * @param response      the response setting the new cookies (if refresh was successful).
     * @param refreshCookie the refresh token cookie. Must not be null.
     * @return the new servlet request containing the updated cookies for relaying downstream.
     */
    public HttpServletRequest refreshToken(HttpServletRequest request, HttpServletResponse response, Cookie
        refreshCookie) {
        //check if non-remember-me session has expired
        if (cookieHelper.isSessionExpired(refreshCookie)) {
            log.info("session has expired due to inactivity");
            logout(request, response);       //logout to clear cookies in browser
            return stripTokens(request);            //don't include cookies downstream
        }
        OAuth2Cookies cookies = getCachedCookies(refreshCookie.getValue());
        synchronized (cookies) {
            //check if we have a result from another thread already
            if (cookies.getAccessTokenCookie() == null) {            //no, we are first!
                //send a refresh_token grant to UAA, getting new tokens
                String refreshCookieValue = OAuth2CookieHelper.getRefreshTokenValue(refreshCookie);
                OAuth2AccessToken accessToken = authorizationClient.sendRefreshGrant(refreshCookieValue);
                boolean rememberMe = OAuth2CookieHelper.isRememberMe(refreshCookie);
                cookieHelper.createCookies(request, accessToken, rememberMe, cookies);
                //add cookies to response to update browser
                cookies.addCookiesTo(response);
            } else {
                log.debug("reusing cached refresh_token grant");
            }
            //replace cookies in original request with new ones
            CookieCollection requestCookies = new CookieCollection(request.getCookies());
            requestCookies.add(cookies.getAccessTokenCookie());
            requestCookies.add(cookies.getRefreshTokenCookie());
            return new CookiesHttpServletRequestWrapper(request, requestCookies.toArray());
        }
    }



Secured Communication between Microservices

We can communicate between the service using the FeignClient and can secure the communication by customizing the configurations. See Class<?>[] configuration() default OAuth2UserClientFeignConfiguration.class;

Here we have enhanced default @FeignClient with AuthorizedUserFeignClient interface which consists of custom configuration as OAuth2UserClientFeignConfiguration which consists of @Bean for UserFeignClientInterceptor which manage the autehication using the headers

AuthorizedUserFeignClient.java

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@FeignClient
public @interface AuthorizedUserFeignClient {

    @AliasFor(annotation = FeignClient.class, attribute = "name")
    String name() default "";

    /**
     * A custom {@code @Configuration} for the feign client.
     *
     * Can contain override {@code @Bean} definition for the pieces that make up the client, for instance {@link
     * feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
     *
     * @see FeignClientsConfiguration for the defaults.
     */
    @AliasFor(annotation = FeignClient.class, attribute = "configuration")
    Class<?>[] configuration() default OAuth2UserClientFeignConfiguration.class;

    /**
     * An absolute URL or resolvable hostname (the protocol is optional).
     */
    String url() default "";

    /**
     * Whether 404s should be decoded instead of throwing FeignExceptions.
     */
    boolean decode404() default false;

    /**
     * Fallback class for the specified Feign client interface. The fallback class must implement the interface
     * annotated by this annotation and be a valid Spring bean.
     */
    Class<?> fallback() default void.class;

    /**
     * Path prefix to be used by all method-level mappings. Can be used with or without {@code @RibbonClient}.
     */
    String path() default "";
}

UserFeignClientInterceptor.java

public class UserFeignClientInterceptor implements RequestInterceptor{

    private static final String AUTHORIZATION_HEADER = "Authorization";

    private static final String BEARER_TOKEN_TYPE = "Bearer";

    @Override
    public void apply(RequestTemplate template) {

        SecurityContext securityContext = SecurityContextHolder.getContext();
        Authentication authentication = securityContext.getAuthentication();

        if (authentication != null && authentication.getDetails() instanceof OAuth2AuthenticationDetails) {

            OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
            template.header(AUTHORIZATION_HEADER, String.format("%s %s", BEARER_TOKEN_TYPE, details.getTokenValue()));
        }
    }
}


Might be helpful

Architecture Overview

Managing the authentication service

这篇关于如何使用OAuth2保护2个Spring Boot微服务之间的通信?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-22 11:11