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 { MultiValueMap2.实现思路formData = new linkedMultiValueMap(); formData.add(this.tokenName, accessToken); HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", this.getAuthorizationHeader(this.clientId, this.clientSecret)); Map 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 postForMap(String path, MultiValueMap 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; }
1.在抽离的公共模块中添加dubbo接口checkToken
public interface IAouthService { void userLogoutByToken(String token); SecurityUser loadUserByUsername(String username); MapcheckToken(String token); }
2.在自己的权限服务模块中创建自定义CustomCheckTokenEndpoint类直接复制CheckTokenEndpoint代码,因为CheckTokenEndpoint原本是个controller控制器需要移除部分代码(原本的 oauthServer.allowFormAuthenticationForClients().checkTokenAccess(“permitAll()”);验证配置会无效如需添加请自行增减方法参数,在方法内自行判断,当然permitAll配置是直接放行的)
主要方法
public MapcheckToken(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 response = (Map ) 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 MapcheckToken(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 { MultiValueMapformData = new linkedMultiValueMap(); formData.add(this.tokenName, accessToken); HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", this.getAuthorizationHeader(this.clientId, this.clientSecret)); //替换restTemplate Map map = (Map ) 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(); //自定义获取用户权限对象,而不是username(没有请自行省略部分代码) defaultAccessTokenConverter.setUserTokenConverter(userAuthenticationConverter); tokenService.setAccessTokenConverter(defaultAccessTokenConverter); return tokenService; } }
以上我使用dubbo远程调用替换了http请求,并完成了测试。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)