1.RemoteTokenServices和RemoteTokenServices
public interface ResourceServerTokenServices {
OAuth2Authentication loadAuthentication(String var1) throws AuthenticationException, InvalidTokenException;
OAuth2AccessToken readAccessToken(String var1);
}
RemoteTokenServices接口定义了加载用户权限的接口而它的实现类RemoteTokenServices实现了该接口,他的实现方式是通过restTemplate构造http请求授权服务器的checkToken接口获取返回的用户权限值,其中的具体流程可以自行打断点查看源码也可以看一下这篇文章: Springsecurity-oauth2之RemoteTokenServices 因为在自己的项目中使用了微服务架构nacos+cloud alibaba+dubbo+gateway等技术 再用写死的ip地址做http请求就显得很鸡肋了,所以我打算重新构造远程鉴权部分的代码,使用dubbo替代http请求服务名和订阅机制替代ip地址 RemoteTokenServices中
public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
MultiValueMap<String, String> formData = new LinkedMultiValueMap();
formData.add(this.tokenName, accessToken);
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", this.getAuthorizationHeader(this.clientId, this.clientSecret));
Map<String, Object> map = this.postForMap(this.checkTokenEndpointUrl, formData, headers);
if (map.containsKey("error")) {
this.logger.debug("check_token returned error: " + map.get("error"));
throw new InvalidTokenException(accessToken);
} else if (!Boolean.TRUE.equals(map.get("active"))) {
this.logger.debug("check_token returned active attribute: " + map.get("active"));
throw new InvalidTokenException(accessToken);
} else {
return this.tokenConverter.extractAuthentication(map);
}
}
private Map<String, Object> postForMap(String path, MultiValueMap<String, String> formData, HttpHeaders headers) {
if (headers.getContentType() == null) {
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
}
Map map = (Map)this.restTemplate.exchange(path, HttpMethod.POST, new HttpEntity(formData, headers), Map.class, new Object[0]).getBody();
return map;
}
2.实现思路
1.在抽离的公共模块中添加dubbo接口checkToken
public interface IAouthService {
void userLogoutByToken(String token);
SecurityUser loadUserByUsername(String username);
Map<String, ?> checkToken(String token);
}
2.在自己的权限服务模块中创建自定义CustomCheckTokenEndpoint类直接复制CheckTokenEndpoint代码,因为CheckTokenEndpoint原本是个controller控制器需要移除部分代码(原本的 oauthServer.allowFormAuthenticationForClients().checkTokenAccess(“permitAll()”);验证配置会无效如需添加请自行增减方法参数,在方法内自行判断,当然permitAll配置是直接放行的) 主要方法
public Map<String, ?> checkToken(String value) {
OAuth2AccessToken token = this.resourceServerTokenServices.readAccessToken(value);
if (token == null) {
throw new InvalidTokenException("Token was not recognised");
} else if (token.isExpired()) {
throw new InvalidTokenException("Token has expired");
} else {
OAuth2Authentication authentication = this.resourceServerTokenServices.loadAuthentication(token.getValue());
Map<String, Object> response = (Map<String, Object>) this.accessTokenConverter.convertAccessToken(token, authentication);
response.put("active", true);
return response;
}
}
将CustomCheckTokenEndpoint注入到实现类中
import org.apache.dubbo.config.annotation.Service;
@Service
@Slf4j
public class AouthServiceImpl implements IAouthService {
@Autowired
private TokenStore tokenStore;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private CustomCheckTokenEndpoint checkTokenEndpoint;
@Override
public void userLogoutByToken(String accessToken) {
。。。。。。。。
}
@Override
public SecurityUser loadUserByUsername(String username) {
。。。。。。。。。。。。。
}
@Override
public Map<String, ?> checkToken(String token) {
return checkTokenEndpoint.checkToken(token);
}
}
3.子模块服务订阅授权服务(因为子模块需要调用授权模块的实现方法)
# Dubbo
dubbo:
# 提供方应用信息,用于计算依赖关系
application:
name: provider-service
# 禁用QOS同一台机器可能会有端口冲突现象
qos-enable: false
qos-accept-foreign-ip: false
#订阅服务,服务提供者不写会一直警告,aouth-service提供aouth接口
cloud:
subscribed-services: aouth-service
4.创建CustomRemoteTokenServices类代码复制RemoteTokenServices即可,移除多余代码,添加IAouthService 接口
public class CustomRemoteTokenServices implements ResourceServerTokenServices {
protected final Log logger = LogFactory.getLog(this.getClass());
private String clientId;
private String clientSecret;
private String tokenName = "token";
private AccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();
private IAouthService aouthService;
public CustomRemoteTokenServices() {
}
public void setAouthService(IAouthService aouthService) {
this.aouthService = aouthService;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public void setAccessTokenConverter(AccessTokenConverter accessTokenConverter) {
this.tokenConverter = accessTokenConverter;
}
public void setTokenName(String tokenName) {
this.tokenName = tokenName;
}
public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
MultiValueMap<String, String> formData = new LinkedMultiValueMap();
formData.add(this.tokenName, accessToken);
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", this.getAuthorizationHeader(this.clientId, this.clientSecret));
Map<String, Object> map = (Map<String, Object>) aouthService.checkToken(accessToken);
if (map.containsKey("error")) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("check_token returned error: " + map.get("error"));
}
throw new InvalidTokenException(accessToken);
} else if (!Boolean.TRUE.equals(map.get("active"))) {
this.logger.debug("check_token returned active attribute: " + map.get("active"));
throw new InvalidTokenException(accessToken);
} else {
return this.tokenConverter.extractAuthentication(map);
}
}
public OAuth2AccessToken readAccessToken(String accessToken) {
throw new UnsupportedOperationException("Not supported: read access token");
}
private String getAuthorizationHeader(String clientId, String clientSecret) {
if (clientId == null || clientSecret == null) {
this.logger.warn("Null Client ID or Client Secret detected. Endpoint that requires authentication will reject request with 401 error.");
}
String creds = String.format("%s:%s", clientId, clientSecret);
try {
return "Basic " + new String(Base64.encode(creds.getBytes("UTF-8")));
} catch (UnsupportedEncodingException var5) {
throw new IllegalStateException("Could not convert String");
}
}
}
5.在资源服务器中配置使用
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_IDS = "order";
@Value("${security.oauth2.client.client-id}")
private String clientId;
@Value("${security.oauth2.client.client-secret}")
private String secret;
@Value("${security.oauth2.authorization.check-token-access}")
private String checkTokenEndpointUrl;
@Autowired
private CustomAuthExceptionHandler customAuthExceptionHandler;
@Autowired
UserLogoutSuccessHandler userLogoutSuccessHandler;
@Autowired
CustomUserAuthenticationConverter userAuthenticationConverter;
@Reference
private IAouthService aouthService;
。。。。。。。。。。。。。。。。
@Bean
public CustomRemoteTokenServices tokenService() {
CustomRemoteTokenServices tokenService = new CustomRemoteTokenServices();
tokenService.setClientId(clientId);
tokenService.setClientSecret(secret);
tokenService.setAouthService(aouthService);
DefaultAccessTokenConverter defaultAccessTokenConverter = new DefaultAccessTokenConverter();
defaultAccessTokenConverter.setUserTokenConverter(userAuthenticationConverter);
tokenService.setAccessTokenConverter(defaultAccessTokenConverter);
return tokenService;
}
}
以上我使用dubbo远程调用替换了http请求,并完成了测试。
|