自定义ResourceServerTokenServices仿照RemoteTokenServices替换RestTemplate使用dubbo实现远程鉴权回馈

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

/**
 * RPC接口,权限相关
 */
public interface IAouthService {
    /**
     *根据token注销登录
     */
    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));
        //替换restTemplate
        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();
        //自定义获取用户权限对象,而不是username(没有请自行省略部分代码)
        defaultAccessTokenConverter.setUserTokenConverter(userAuthenticationConverter);
        tokenService.setAccessTokenConverter(defaultAccessTokenConverter);
        return tokenService;
    }

}

以上我使用dubbo远程调用替换了http请求,并完成了测试。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值