BasicAuthRequestInterceptor
BasicAuthRequestInterceptor
翻译过来就是 Basic 认证请求拦截器。
Basic 认证
Basic认证
是一种较为简单的HTTP认证方式,客户端通过明文(Base64编码格式)传输用户名和密码到服务端进行认证,通常需要配合HTTPS来保证信息传输的安全。
比如Security
就支持这种方式,在发送认证请求时,按照以下格式:
// 请求头Authorization 添加Basic 认证信息
Authorization: Basic 用户名:密码Base64编码格式
使用案例
首先注入一个BasicAuthRequestInterceptor
。
@Bean
BasicAuthRequestInterceptor basicAuthRequestInterceptor(){
return new BasicAuthRequestInterceptor("zhangsan","123456");
}
然后使用Feign
调用远程服务,可以在日志中看到在消息头中添加了Basic认证
信息:
源码
源码也很简单,就是将用户名密码,经过编码后放入到消息头中:
public class BasicAuthRequestInterceptor implements RequestInterceptor {
private final String headerValue;
public BasicAuthRequestInterceptor(String username, String password) {
this(username, password, Util.ISO_8859_1);
}
public BasicAuthRequestInterceptor(String username, String password, Charset charset) {
Util.checkNotNull(username, "username", new Object[0]);
Util.checkNotNull(password, "password", new Object[0]);
this.headerValue = "Basic " + base64Encode((username + ":" + password).getBytes(charset));
}
private static String base64Encode(byte[] bytes) {
return Base64.encode(bytes);
}
public void apply(RequestTemplate template) {
template.header("Authorization", new String[]{
this.headerValue});
}
}
OAuth2FeignRequestInterceptor
OAuth2FeignRequestInterceptor
属于spring-cloud-security
包,可以看到都标记为了过时,这是因为security-oauth2
已经快停止维护,换了新的项目,已经正式发布。
源码分析
先按照顺序看下这个拦截器的处理逻辑,可以看到就是使用一个OAuth2
客户端上下文对象来存储令牌信息,每次请求时,会去校验当前上下文中令牌是否过期或不存在,不可用时,调用配置的认证中心获取令牌,然后将令牌放入到请求头中。
public class OAuth2FeignRequestInterceptor implements RequestInterceptor {
public static final String BEARER = "Bearer";
public static final String AUTHORIZATION = "Authorization";
private final OAuth2ClientContext oAuth2ClientContext;
private final OAuth2ProtectedResourceDetails resource;
private final String tokenType;
private final String header;
private AccessTokenProvider accessTokenProvider;
public OAuth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext, OAuth2ProtectedResourceDetails resource) {
this(oAuth2ClientContext, resource, "Bearer", "Authorization");
}
public OAuth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext, OAuth2ProtectedResourceDetails resource, String tokenType, String header) {
this.accessTokenProvider = new AccessTokenProviderChain(Arrays.asList(new AuthorizationCodeAccessTokenProvider(), new ImplicitAccessTokenProvider(), new ResourceOwnerPasswordAccessTokenProvider(), new ClientCredentialsAccessTokenProvider()));
this.oAuth2ClientContext = oAuth2ClientContext;
this.resource = resource;
this.tokenType = tokenType;
this.header = header;
}
public void apply(RequestTemplate template) {
// 5. 将Oauth2 令牌放入到消息头中。
template.header(this.header, new String[0]);
template.header(this.header, new String[]{
this.extract(this.tokenType)});
}
protected String extract(String tokenType) {
OAuth2AccessToken accessToken = this.getToken();
return String.format("%s %s", tokenType, accessToken.getValue());
}
public OAuth2AccessToken getToken() {
// 1. 获取 OAuth2令牌
OAuth2AccessToken accessToken = this.oAuth2ClientContext.getAccessToken();
if (accessToken == null || accessToken.isExpired()) {
// 2. 如果令牌不存在或者过期,重新获取一个令牌
try {
accessToken = this.acquireAccessToken();
} catch (UserRedirectRequiredException var5) {
this.oAuth2ClientContext.setAccessToken((OAuth2AccessToken)null);
String stateKey = var5.getStateKey();
if (stateKey != null) {
Object stateToPreserve = var5.getStateToPreserve();
if (stateToPreserve == null) {
stateToPreserve = "NONE";
}
this.oAuth2ClientContext.setPreservedState(stateKey, stateToPreserve);
}
throw var5;
}
}
return accessToken;
}
protected OAuth2AccessToken acquireAccessToken() throws UserRedirectRequiredException {
// 3. 创建一个获取令牌的请求对象
AccessTokenRequest tokenRequest = this.oAuth2ClientContext.getAccessTokenRequest();
if (tokenRequest == null) {
throw new AccessTokenRequiredException("Cannot find valid context on request for resource '" + this.resource.getId() + "'.", this.resource);
} else {
String stateKey = tokenRequest.getStateKey();
if (stateKey != null) {
tokenRequest.setPreservedState(this.oAuth2ClientContext.removePreservedState(stateKey));
}
OAuth2AccessToken existingToken = this.oAuth2ClientContext.getAccessToken();
if (existingToken != null) {
this.oAuth2ClientContext.setAccessToken(existingToken);
}
// 4. 使用令牌提供者获取一个令牌
OAuth2AccessToken obtainableAccessToken = this.accessTokenProvider.obtainAccessToken(this.resource, tokenRequest);
if (obtainableAccessToken != null && obtainableAccessToken.getValue() != null) {
this.oAuth2ClientContext.setAccessToken(obtainableAccessToken);
return obtainableAccessToken;
} else {
throw new IllegalStateException(" Access token provider returned a null token, which is illegal according to the contract.");
}
}
}
public void setAccessTokenProvider(AccessTokenProvider accessTokenProvider) {
this.accessTokenProvider = accessTokenProvider;
}
}
应用场景
当某个服务是Oauth 2
的资源服务器,第三方使用Feign 去访问时,需要携带Oauth 2
令牌去访问,这个时候就可以使用当前拦截器添加Oauth 2
认证。
使用案例
场景:现在我们有一个微服务项目,采用Oauth 2认证方式,这个时候有个第三方,想通过Feign 调用我们的资源服务器,这个时候就可以让第三方集成我们的 Oauth 2客户端。
首先需要引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-security</artifactId>
</dependency>
然后注入 一个 Oauth 2客户端,这里演示使用密码模式。
@Bean
OAuth2ClientContext oAuth2ClientContext(){
return new DefaultOAuth2ClientContext();
}
@Bean
OAuth2FeignRequestInterceptor oAuth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext, ResourceOwnerPasswordResourceDetails resourceOwnerPasswordResourceDetails){
return new OAuth2FeignRequestInterceptor(oAuth2ClientContext,resourceOwnerPasswordResourceDetails);
}
@Bean
ResourceOwnerPasswordResourceDetails resourceOwnerPasswordResourceDetails(){
ResourceOwnerPasswordResourceDetails resourceOwnerPasswordResourceDetails = new ResourceOwnerPasswordResourceDetails();
resourceOwnerPasswordResourceDetails.setUsername("hnmqet");
resourceOwnerPasswordResourceDetails.setPassword("123456");
resourceOwnerPasswordResourceDetails.setClientId("ZD");
resourceOwnerPasswordResourceDetails.setClientSecret("123456");
resourceOwnerPasswordResourceDetails.setAccessTokenUri("http://192.168.58.1:21101/oauth/token");
return resourceOwnerPasswordResourceDetails;
}
测试,可以看到该拦截器,最终会调用OAuth2AccessTokenSupport
去远程获取令牌,然后放在消息头中去发送Feign 请求。