spring cloud oauth2 feign 遇到的坑及解決
spring cloud oauth2 feign 遇到的坑
關(guān)于oauth2相關(guān)的內(nèi)容這里不重復(fù)描述,在spring cloud中在管理內(nèi)部api時(shí)鑒權(quán)相信有很多人會(huì)有疑問(wèn),這里描述兩種比較low的用法,由于公司內(nèi)部使用的是阿里云edas這里僅僅是記錄一下,如果有更好的用法在請(qǐng)賜教,不喜勿噴!
客戶端模式
提供三方j(luò)ar包
這里需要抽一個(gè)jar包,需要用到feign的為服務(wù)端直接利用maven模式引入feign即可
<dependencies> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>org.springframework.cloud</groupId> ? ? ? ? ? ? <artifactId>spring-cloud-starter-feign</artifactId> ? ? ? ? </dependency> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>com.netflix.feign</groupId> ? ? ? ? ? ? <artifactId>feign-okhttp</artifactId> ? ? ? ? ? ? <version>8.18.0</version> ? ? ? ? </dependency> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>org.springframework.cloud</groupId> ? ? ? ? ? ? <artifactId>spring-cloud-starter-oauth2</artifactId> ? ? ? ? </dependency> ? ? </dependencies>
核心類
- CustomHystrixConcurrencyStrategy.java
- Oauth2ClientProperties.java
- OAuth2FeignAutoConfiguration.java
- OAuth2FeignRequestInterceptor.java
package com.paascloud.security.feign; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; /** ?* The class Oauth 2 client properties. ?* ?* @author paascloud.net @gmail.com ?*/ @Data @ConfigurationProperties(prefix = "paascloud.oauth2.client") public class Oauth2ClientProperties { ? ? private String id; ? ? private String accessTokenUrl; ? ? private String clientId; ? ? private String clientSecret; ? ? private String clientAuthenticationScheme; }
package com.paascloud.security.feign; import com.netflix.hystrix.strategy.HystrixPlugins; import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; import org.springframework.stereotype.Component; import java.util.concurrent.Callable; /** ?* The class Custom hystrix concurrency strategy. ?* ?* @author paascloud.net @gmail.com ?*/ @Component public class CustomHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy { ? ? /** ? ? ?* Instantiates a new Custom hystrix concurrency strategy. ? ? ?*/ ? ? public CustomHystrixConcurrencyStrategy() { ? ? ? ? HystrixPlugins.getInstance().registerConcurrencyStrategy(this); ? ? } ? ? /** ? ? ?* Wrap callable callable. ? ? ?* ? ? ?* @param <T> ? ? ?the type parameter ? ? ?* @param callable the callable ? ? ?* ? ? ?* @return the callable ? ? ?*/ ? ? @Override ? ? public <T> Callable<T> wrapCallable(Callable<T> callable) { ? ? ? ? return new HystrixContextWrapper<T>(callable); ? ? } ? ? /** ? ? ?* The class Hystrix context wrapper. ? ? ?* ? ? ?* @param <V> the type parameter ? ? ?* ? ? ?* @author paascloud.net @gmail.com ? ? ?*/ ? ? public static class HystrixContextWrapper<V> implements Callable<V> { ? ? ? ? private HystrixRequestContext hystrixRequestContext; ? ? ? ? private Callable<V> delegate; ? ? ? ? /** ? ? ? ? ?* Instantiates a new Hystrix context wrapper. ? ? ? ? ?* ? ? ? ? ?* @param delegate the delegate ? ? ? ? ?*/ ? ? ? ? HystrixContextWrapper(Callable<V> delegate) { ? ? ? ? this.hystrixRequestContext = HystrixRequestContext.getContextForCurrentThread(); ? ? ? ? ? ? this.delegate = delegate; ? ? ? ? } ? ? ? ? /** ? ? ? ? ?* Call v. ? ? ? ? ?* ? ? ? ? ?* @return the v ? ? ? ? ?* ? ? ? ? ?* @throws Exception the exception ? ? ? ? ?*/ ? ? ? ? @Override ? ? ? ? public V call() throws Exception { ? ? ? ? ? ? HystrixRequestContext existingState = HystrixRequestContext.getContextForCurrentThread(); ? ? ? ? ? ? try { ? ? ? ? ? ? ? ? HystrixRequestContext.setContextOnCurrentThread(this.hystrixRequestContext); ? ? ? ? ? ? ? ? return this.delegate.call(); ? ? ? ? ? ? } finally { ? ? ? ? ? ? ? ? HystrixRequestContext.setContextOnCurrentThread(existingState); ? ? ? ? ? ? } ? ? ? ? } ? ? } }
package com.paascloud.security.feign; import feign.Logger; import feign.RequestInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.Netty4ClientHttpRequestFactory; import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; import org.springframework.security.oauth2.client.OAuth2RestTemplate; import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; import org.springframework.security.oauth2.common.AuthenticationScheme; /** ?* The class O auth 2 feign auto configuration. ?* ?* @author paascloud.net @gmail.com ?*/ @Configuration @EnableConfigurationProperties(Oauth2ClientProperties.class) public class OAuth2FeignAutoConfiguration { ? ? private final Oauth2ClientProperties oauth2ClientProperties; ? ? /** ? ? ?* Instantiates a new O auth 2 feign auto configuration. ? ? ?* ? ? ?* @param oauth2ClientProperties the oauth 2 client properties ? ? ?*/ ? ? @Autowired ? ? public OAuth2FeignAutoConfiguration(Oauth2ClientProperties oauth2ClientProperties) { ? ? ? ? this.oauth2ClientProperties = oauth2ClientProperties; ? ? } ? ? /** ? ? ?* Resource details client credentials resource details. ? ? ?* ? ? ?* @return the client credentials resource details ? ? ?*/ ? ? @Bean("paascloudClientCredentialsResourceDetails") ? ? public ClientCredentialsResourceDetails resourceDetails() { ? ? ? ? ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails(); ? ? ? ? details.setId(oauth2ClientProperties.getId()); ? ? ? ? details.setAccessTokenUri(oauth2ClientProperties.getAccessTokenUrl()); ? ? ? ? details.setClientId(oauth2ClientProperties.getClientId()); ? ? ? ? details.setClientSecret(oauth2ClientProperties.getClientSecret()); ? ? ? ? details.setAuthenticationScheme(AuthenticationScheme.valueOf(oauth2ClientProperties.getClientAuthenticationScheme())); ? ? ? ? return details; ? ? } ? ? /** ? ? ?* O auth 2 rest template o auth 2 rest template. ? ? ?* ? ? ?* @return the o auth 2 rest template ? ? ?*/ ? ? @Bean("paascloudOAuth2RestTemplate") ? ? public OAuth2RestTemplate oAuth2RestTemplate() { ? ? ? ? final OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resourceDetails(), new DefaultOAuth2ClientContext()); ? ? ? ? oAuth2RestTemplate.setRequestFactory(new Netty4ClientHttpRequestFactory()); ? ? ? ? return oAuth2RestTemplate; ? ? } ? ? /** ? ? ?* Oauth 2 feign request interceptor request interceptor. ? ? ?* ? ? ?* @param oAuth2RestTemplate the o auth 2 rest template ? ? ?* ? ? ?* @return the request interceptor ? ? ?*/ ? ? @Bean ? ? public RequestInterceptor oauth2FeignRequestInterceptor(@Qualifier("paascloudOAuth2RestTemplate") OAuth2RestTemplate oAuth2RestTemplate) { ? ? ? ? return new OAuth2FeignRequestInterceptor(oAuth2RestTemplate); ? ? } ? ? /** ? ? ?* Feign logger level logger . level. ? ? ?* ? ? ?* @return the logger . level ? ? ?*/ ? ? @Bean ? ? Logger.Level feignLoggerLevel() { ? ? ? ? return Logger.Level.FULL; ? ? } }
package com.paascloud.security.feign; import feign.RequestInterceptor; import feign.RequestTemplate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.oauth2.client.OAuth2RestTemplate; import org.springframework.util.Assert; /** ?* The class O auth 2 feign request interceptor. ?* ?* @author paascloud.net @gmail.com ?*/ public class OAuth2FeignRequestInterceptor implements RequestInterceptor { ? ? private static final String AUTHORIZATION_HEADER = "Authorization"; ? ? private static final String BEARER_TOKEN_TYPE = "bearer"; ? ? private final OAuth2RestTemplate oAuth2RestTemplate; ? ? /** ? ? ?* Instantiates a new O auth 2 feign request interceptor. ? ? ?* ? ? ?* @param oAuth2RestTemplate the o auth 2 rest template ? ? ?*/ ? ? OAuth2FeignRequestInterceptor(OAuth2RestTemplate oAuth2RestTemplate) { ? ? ? ? Assert.notNull(oAuth2RestTemplate, "Context can not be null"); ? ? ? ? this.oAuth2RestTemplate = oAuth2RestTemplate; ? ? } ? ? /** ? ? ?* Apply. ? ? ?* ? ? ?* @param template the template ? ? ?*/ ? ? @Override ? ? public void apply(RequestTemplate template) { ? ? ? ? template.header(AUTHORIZATION_HEADER, String.format("%s %s", BEARER_TOKEN_TYPE, ?oAuth2RestTemplate.getAccessToken().toString())); ? ? } }
調(diào)用端配置
引入maven依賴
<dependency> ? ? ? ? ? ? <groupId>com.liuzm.paascloud.common</groupId> ? ? ? ? ? ? <artifactId>paascloud-security-feign</artifactId> ? ? ? ? ? ? <version>1.0-SNAPSHOT</version> </dependency>
@FeignClient加入configuration屬性
/** ?* The interface Mdc product feign api. ?* @author paascloud.net@gmail.com ?*/ @FeignClient(value = "paascloud-provider-mdc", configuration = OAuth2FeignAutoConfiguration.class, fallback = MdcProductFeignHystrix.class) public interface MdcProductFeignApi { ? ? /** ? ? ?* Update product stock by id int. ? ? ?* ? ? ?* @param productDto the product dto ? ? ?* ? ? ?* @return the int ? ? ?*/ ? ? @RequestMapping(value = "/api/product/updateProductStockById", method = RequestMethod.POST) ? ? int updateProductStockById(@RequestBody ProductDto productDto); }
認(rèn)證服務(wù)器配置
@Override ? ? public void configure(ClientDetailsServiceConfigurer clients) throws Exception { ? ? ? ? clients.withClientDetails(restClientDetailsService); ? ? }
package com.paascloud.provider.security; import com.paascloud.security.core.properties.OAuth2ClientProperties; import com.paascloud.security.core.properties.SecurityProperties; import org.apache.commons.lang3.ArrayUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth2.config.annotation.builders.InMemoryClientDetailsServiceBuilder; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.ClientRegistrationException; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; /** ?* The class Rest client details service. ?* ?* @author paascloud.net @gmail.com ?*/ @Component("restClientDetailsService") public class RestClientDetailsServiceImpl implements ClientDetailsService { ? ? private ClientDetailsService clientDetailsService; ? ? @Autowired ? ? private SecurityProperties securityProperties; ? ? /** ? ? ?* Init. ? ? ?*/ ? ? @PostConstruct ? ? public void init() { ? ? ? ? InMemoryClientDetailsServiceBuilder builder = new InMemoryClientDetailsServiceBuilder(); ? ? ? ? if (ArrayUtils.isNotEmpty(securityProperties.getOauth2().getClients())) { ? ? ? ? ? ? for (OAuth2ClientProperties client : securityProperties.getOauth2().getClients()) { ? ? ? ? ? ? ? ? builder.withClient(client.getClientId()) ? ? ? ? ? ? ? ? ? ? ? ? .secret(client.getClientSecret()) ? ? ? ? ? ? ? ? ? ? ? ? .authorizedGrantTypes("refresh_token", "password", "client_credentials") ? ? ? ? ? ? ? ? ? ? ? ? .accessTokenValiditySeconds(client.getAccessTokenValidateSeconds()) ? ? ? ? ? ? ? ? ? ? ? ? .refreshTokenValiditySeconds(2592000) ? ? ? ? ? ? ? ? ? ? ? ? .scopes(client.getScope()); ? ? ? ? ? ? } ? ? ? ? } ? ? ? ? try { ? ? ? ? ? ? clientDetailsService = builder.build(); ? ? ? ? } catch (Exception e) { ? ? ? ? ? ? e.printStackTrace(); ? ? ? ? } ? ? } ? ? /** ? ? ?* Load client by client id client details. ? ? ?* ? ? ?* @param clientId the client id ? ? ?* ? ? ?* @return the client details ? ? ?* ? ? ?* @throws ClientRegistrationException the client registration exception ? ? ?*/ ? ? @Override ? ? public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException { ? ? ? ? return ?clientDetailsService.loadClientByClientId(clientId); ? ? } }
bootstrap.yml配置
security: ? ? oauth2: ? ? ? tokenStore: jwt ? ? ? clients[0]: ? ? ? ? clientId: paascloud-client-uac ? ? ? ? clientSecret: paascloudClientSecret ? ? ? ? accessTokenValidateSeconds: 7200 ? ? ? ? scope: "*" ? ? ? clients[1]: ? ? ? ? clientId: paascloud-browser ? ? ? ? clientSecret: paascloudClientSecret ? ? ? ? accessTokenValidateSeconds: 7200 ? ? ? ? scope: "*" ? ? ? clients[2]: ? ? ? ? clientId: paascloud-client-gateway ? ? ? ? clientSecret: paascloudClientSecret ? ? ? ? accessTokenValidateSeconds: 7200 ? ? ? ? scope: "*" ? ? ? clients[3]: ? ? ? ? clientId: paascloud-client-zipkin ? ? ? ? clientSecret: paascloudClientSecret ? ? ? ? accessTokenValidateSeconds: 7200 ? ? ? ? scope: "*" ? ? ? clients[4]: ? ? ? ? clientId: paascloud-client-mdc ? ? ? ? clientSecret: paascloudClientSecret ? ? ? ? accessTokenValidateSeconds: 7200 ? ? ? ? scope: "*" ? ? ? clients[5]: ? ? ? ? clientId: paascloud-client-omc ? ? ? ? clientSecret: paascloudClientSecret ? ? ? ? accessTokenValidateSeconds: 7200 ? ? ? ? scope: "*" ? ? ? clients[6]: ? ? ? ? clientId: paascloud-client-opc ? ? ? ? clientSecret: paascloudClientSecret ? ? ? ? accessTokenValidateSeconds: 7200 ? ? ? ? scope: "*"
到此客戶端模式配置完成!
基于spring security
開(kāi)放權(quán)限,利用url規(guī)范來(lái)規(guī)劃客戶端的url不通過(guò)auth2鑒權(quán),這里唯一的區(qū)別是在feign攔截器里處理的邏輯改一下,代碼如下
@Autowired private OAuth2ClientContext context; @Override ? ? public void apply(RequestTemplate template) { ? ? ? ? if(context.getAccessToken() != null && context.getAccessToken().getValue() != null && OAuth2AccessToken.BEARER_TYPE.equalsIgnoreCase(context.getAccessToken().getTokenType()) ){ ? ? ? ? ? ? template.header("Authorization", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, context.getAccessToken().getValue())); ? ? ? ? } ? ? }
spring cloud微服務(wù)增加oauth2權(quán)限后 feign調(diào)用報(bào)null
在授權(quán)服務(wù)里,用戶通過(guò)用戶名密碼,或者手機(jī)和驗(yàn)證碼等方式登陸之后,在http頭里會(huì)有授權(quán)的標(biāo)識(shí),在客戶端調(diào)用時(shí),需要添加當(dāng)時(shí)有效的token才可以正常訪問(wèn)被授權(quán)的頁(yè)面。
Content-Type:application/json Authorization:Bearer d79c064c-8675-4047-a119-fac692e447e8
而在業(yè)務(wù)層里,服務(wù)與服務(wù)之間使用feign來(lái)實(shí)現(xiàn)調(diào)用,而授權(quán)的代碼我們可以通過(guò)攔截器實(shí)現(xiàn),在feign請(qǐng)求之前,把當(dāng)前服務(wù)的token添加到目標(biāo)服務(wù)的請(qǐng)求頭就可以了
一般是這樣實(shí)現(xiàn)的
/** ?* 發(fā)送FeignClient設(shè)置Header信息. ?* http://www.itmuch.com/spring-cloud-sum/hystrix-threadlocal/ ?* Hystrix傳播ThreadLocal對(duì)象 ?*/ @Component public class TokenFeignClientInterceptor implements RequestInterceptor { ? /** ? ?* token放在請(qǐng)求頭. ? ?* ? ?* @param requestTemplate 請(qǐng)求參數(shù) ? ?*/ ? @Override ? public void apply(RequestTemplate requestTemplate) { ? ? RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); ? ? if (requestAttributes != null) { ? ? ? HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); ? ? ? requestTemplate.header("Authorization", request.getHeader("Authorization")); ? ? } ? } }
上面的攔截器代碼沒(méi)有什么問(wèn)題,也很好理解,但事實(shí)上,當(dāng)你的feign開(kāi)啟了hystrix功能,如果開(kāi)啟了,需要把hystrix的策略進(jìn)行修改,默認(rèn)是THREAD的,這個(gè)級(jí)別時(shí)ThreadLocal是空的,所以你的授權(quán)不能傳給feign的攔截器.
hystrix:? ?? ?command:? ?? ??? ?default:? ?? ??? ??? ?execution:? ?? ??? ??? ??? ?isolation:? ?? ??? ??? ??? ??? ?strategy: SEMAPHORE?
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
如何用JAVA判斷當(dāng)前時(shí)間是否為節(jié)假日、周末、工作日及調(diào)休日(不報(bào)錯(cuò):IOException!)
最近公司有個(gè)業(yè)務(wù)需要判斷工作日,但是每年的節(jié)假日不一樣,下面這篇文章主要給大家介紹了關(guān)于如何用JAVA判斷當(dāng)前時(shí)間是否為節(jié)假日、周末、工作日及調(diào)休日的相關(guān)資料,且不報(bào)錯(cuò):IOException!,需要的朋友可以參考下2023-12-12Java每隔兩個(gè)數(shù)刪掉一個(gè)數(shù)問(wèn)題詳解
這篇文章主要介紹了Java每隔兩個(gè)數(shù)刪掉一個(gè)數(shù)問(wèn)題詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04SpringSecurity?表單登錄的實(shí)現(xiàn)
本文主要介紹了SpringSecurity?表單登錄的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12Spring與Mybatis相結(jié)合實(shí)現(xiàn)多數(shù)據(jù)源切換功能
這篇文章主要介紹了Spring與Mybatis相結(jié)合實(shí)現(xiàn)多數(shù)據(jù)源切換功能的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06SpringBoot中的五種對(duì)靜態(tài)資源的映射規(guī)則的實(shí)現(xiàn)
這篇文章主要介紹了SpringBoot中的五種對(duì)靜態(tài)資源的映射規(guī)則的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12