美文网首页
Spring cloud oauth2 源码ResourceSe

Spring cloud oauth2 源码ResourceSe

作者: 输入昵称就行 | 来源:发表于2019-07-15 22:43 被阅读0次

    ResourceServerTokenServices

    oauth2资源服务器鉴权的一个核心服务接口类,只有2个接口方法

    public interface ResourceServerTokenServices {
    
        /**
         * Load the credentials for the specified access token.
         *  检验token的有效性,只返回有限的信息
         * @param accessToken The access token value.
         * @return The authentication for the access token.
         * @throws AuthenticationException If the access token is expired
         * @throws InvalidTokenException if the token isn't valid
         */
        OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException;
    
        /**
         * Retrieve the full access token details from just the value.
             * 获取token的完整信息的
         * @param accessToken the token value
         * @return the full access token with client id etc.
         */
        OAuth2AccessToken readAccessToken(String accessToken);
    
    }
    

    默认的实现类有4个

    DefaultTokenServices

    public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
            ConsumerTokenServices, InitializingBean 
    

    既实现了ResourceServerTokenServices,又实现了AuthorizationServerTokenServices,并且内部实现是对tokenStore的操作,由此可知,该类适用于认证服务器的token管理
    从源码可以看到主要的功能是:

    • 生成token
    • 刷新token
    • 获取token

    RemoteTokenServices

    检查token的服务,对应配置security.oauth2.resource.tokenInfoUrl=http://localhost:4000/oauth/check_token

    public class RemoteTokenServices implements ResourceServerTokenServices {
        private RestOperations restTemplate;
    
        private String checkTokenEndpointUrl;
    
        private String clientId;
    
        private String clientSecret;
    
        private String tokenName = "token";
    
        private AccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();
    
        public RemoteTokenServices() {
            restTemplate = new RestTemplate();
            ((RestTemplate) restTemplate).setErrorHandler(new DefaultResponseErrorHandler() {
                @Override
                // Ignore 400
                public void handleError(ClientHttpResponse response) throws IOException {
                    if (response.getRawStatusCode() != 400) {
                        super.handleError(response);
                    }
                }
            });
        }
    
          // 省略了getter setter
    
        @Override
        public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
        
            MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
            formData.add(tokenName, accessToken);
            HttpHeaders headers = new HttpHeaders();
            headers.set("Authorization", getAuthorizationHeader(clientId, clientSecret));
            Map<String, Object> map = postForMap(checkTokenEndpointUrl, formData, headers);
    
            if (map.containsKey("error")) {
                if (logger.isDebugEnabled()) {
                    logger.debug("check_token returned error: " + map.get("error"));
                }
                throw new InvalidTokenException(accessToken);
            }
    
            // gh-838
            if (!Boolean.TRUE.equals(map.get("active"))) {
                logger.debug("check_token returned active attribute: " + map.get("active"));
                throw new InvalidTokenException(accessToken);
            }
    
            return tokenConverter.extractAuthentication(map);
        }
    
        private String getAuthorizationHeader(String clientId, String clientSecret) {
    
            if(clientId == null || clientSecret == null) {
                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 e) {
                throw new IllegalStateException("Could not convert String");
            }
        }
    
        private Map<String, Object> postForMap(String path, MultiValueMap<String, String> formData, HttpHeaders headers) {
            if (headers.getContentType() == null) {
                headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
            }
            @SuppressWarnings("rawtypes")
            Map map = restTemplate.exchange(path, HttpMethod.POST,
                    new HttpEntity<MultiValueMap<String, String>>(formData, headers), Map.class).getBody();
            @SuppressWarnings("unchecked")
            Map<String, Object> result = map;
            return result;
        }
    
    }
    

    SpringSocialTokenServices

    封装了对第三方统一授权的调用方法,比如集成QQ,微信,微博

    public class SpringSocialTokenServices implements ResourceServerTokenServices {
    
        private final OAuth2ConnectionFactory<?> connectionFactory;
    
        private final String clientId;
    
        public SpringSocialTokenServices(OAuth2ConnectionFactory<?> connectionFactory,
                String clientId) {
            this.connectionFactory = connectionFactory;
            this.clientId = clientId;
        }
    
        @Override
        public OAuth2Authentication loadAuthentication(String accessToken)
                throws AuthenticationException, InvalidTokenException {
            AccessGrant accessGrant = new AccessGrant(accessToken);
            Connection<?> connection = this.connectionFactory.createConnection(accessGrant);
            UserProfile user = connection.fetchUserProfile();
            return extractAuthentication(user);
        }
    
        private OAuth2Authentication extractAuthentication(UserProfile user) {
            String principal = user.getUsername();
            List<GrantedAuthority> authorities = AuthorityUtils
                    .commaSeparatedStringToAuthorityList("ROLE_USER");
            OAuth2Request request = new OAuth2Request(null, this.clientId, null, true, null,
                    null, null, null, null);
            return new OAuth2Authentication(request,
                    new UsernamePasswordAuthenticationToken(principal, "N/A", authorities));
        }
    
        @Override
        public OAuth2AccessToken readAccessToken(String accessToken) {
            throw new UnsupportedOperationException("Not supported: read access token");
        }
    
    }
    

    UserInfoTokenServices

    获取鉴权用户信息的服务,对应的配置文件中security.oauth2.resource.user-info-uri=http://localhost:4000/auth/current

    优化配置方式

    在开发过程中,由于资源服务器和认证服务器在一个微服务体系内,所以可以考虑将配置文件的具体的ip:port的方式改为通过restTemplate的通过服务名的方式访问
    比如认证服务的服务名是auth-serivces,那么http://localhost:4000/oauth/check_token 就希望配置成http://auth-services/oauth/check_token, 如果只是这样修改,运行过程中会报错,无法识别的host,由此需要对RemoteTokenServices重写

    @Slf4j
    public class CustomCheckTokenServices implements ResourceServerTokenServices {
    
        private RestTemplate restTemplate;
    
        private String checkTokenEndpointUrl;
    
        private String clientId;
    
        private String clientSecret;
    
        private String tokenName = "token";
    
        private AccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
    
        public CustomCheckTokenServices(RestTemplate restTemplate) {
            this.restTemplate = restTemplate;
            this.restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
                @Override
                public void handleError(ClientHttpResponse response) throws IOException {
                    if (response.getRawStatusCode() != 400) {
                        super.handleError(response);
                    }
                }
            });
        }
    
        public void setCheckTokenEndpointUrl(String checkTokenEndpointUrl) {
            this.checkTokenEndpointUrl = checkTokenEndpointUrl;
        }
    
        public void setClientId(String clientId) {
            this.clientId = clientId;
        }
    
        public void setClientSecret(String clientSecret) {
            this.clientSecret = clientSecret;
        }
    
        public void setAccessTokenConverter(AccessTokenConverter accessTokenConverter) {
            this.accessTokenConverter = accessTokenConverter;
        }
    
        public void setTokenName(String tokenName) {
            this.tokenName = tokenName;
        }
    
        @Override
        public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
    
            MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
            formData.add(tokenName, accessToken);
            HttpHeaders headers = new HttpHeaders();
            headers.set(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(clientId, clientSecret));
            Map<String, Object> map = postForMap(checkTokenEndpointUrl, formData, headers);
            log.info("check token result {}", map);
            if (map.containsKey("error")) {
                log.debug("check_token returned error: " + map.get("error"));
                throw new InvalidTokenException(accessToken);
            }
    
            Assert.state(map.containsKey("client_id"), "Client id must be present in response from auth server");
            return accessTokenConverter.extractAuthentication(map);
        }
    
        @Override
        public OAuth2AccessToken readAccessToken(String accessToken) {
            throw new UnsupportedOperationException("Not supported: read access token.");
        }
    
        private String getAuthorizationHeader(String clientId, String clientSecret) {
            String creds = String.format("%s:%s", clientId, clientSecret);
            return "Basic " + new String(Base64.encodeBase64(creds.getBytes(StandardCharsets.UTF_8)));
        }
    
        private Map<String, Object> postForMap(String path, MultiValueMap<String, String> formData, HttpHeaders headers) {
            if (headers.getContentType() == null) {
                headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
            }
            @SuppressWarnings("rawtypes")
            Map map = restTemplate.postForObject(path, new HttpEntity<>(formData, headers), Map.class);
            @SuppressWarnings("unckecked")
            Map<String, Object> result = map;
            return result;
        }
    }
    

    然后在 EnableResoureceServer中进行配置(要创建restTemplate的bean 并且必须要加上@LoadBalanced注解)

    @Configuration
    @EnableResourceServer
    public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
        @Autowired
        private ResourceServerProperties resourceServerProperties;
    
        @Autowired
        private PermitAllProperties permitAllProperties;
    
        @Autowired
        private RestTemplate restTemplate;
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.csrf().disable();
            http
                    // 只会处理/**匹配的地址,假设该值是/aa/**, 则/bb/**的是不会被处理的
                    .requestMatchers().antMatchers("/**")
                    .and()
                    .authorizeRequests()
                    // 设置不需要鉴权的url匹配规则
                    .antMatchers(permitAllProperties.getPermitAllPatterns()).permitAll()
                    // 其他的地址全部需要鉴权
                    .anyRequest().authenticated();
        }
    
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            CustomCheckTokenServices customCheckTokenServices = new CustomCheckTokenServices(restTemplate);
            customCheckTokenServices.setCheckTokenEndpointUrl(resourceServerProperties.getTokenInfoUri());
            customCheckTokenServices.setClientId(resourceServerProperties.getClientId());
            customCheckTokenServices.setClientSecret(resourceServerProperties.getClientSecret());
            resources.tokenServices(customCheckTokenServices);
        }
    }
    

    同样的方式可以重写 UserInfoTokenServices的实现
    如果实在网关内重写该方法,可以直接在该方法内将一些下游服务需要的鉴权信息增强到request header中

        public void enhancer(Map<String, Object> principal) {
            RequestContext ctx = RequestContext.getCurrentContext();
            ctx.addZuulRequestHeader(SecurityConstants.USER_ID_IN_HEADER, String.valueOf(principal.get("id")));
            ctx.addZuulRequestHeader(SecurityConstants.USERNAME_IN_HEADER, String.valueOf(principal.get("username")));
        }
    

    相关文章

      网友评论

          本文标题:Spring cloud oauth2 源码ResourceSe

          本文链接:https://www.haomeiwen.com/subject/zniokctx.html