• spring security oauth2 资源服务/客户端无法正确获取权限


    异常现象

    当资源服务/客户端使用token-info-uri校验token时无法获取全部的授权权限,只能获取其中一个权限,使用user-info-uri则可以获取全部的授权权限

    spring security 版本2.3.8

    资源服务配置

    security:
      oauth2:
        client:
          client-id: client1
          client-secret: client1pwd
          access-token-uri: 'http://localhost:11000/oauth/token'
          user-authorization-uri: 'http://localhost:11000/oauth/authorize'
          scope: all
        resource:
          token-info-uri: 'http://localhost:11000/oauth/check_token'
          user-info-uri: 'http://localhost:11000/oauth/check_user'
          prefer-token-info: true
    
    • prefer-token-info默认值为true,既优先使用token-info-uri校验token认证信息
    • prefer-token-info设置为false,或不配置token-info-uri则会使用user-info-uri,适用于需要获取userdetails信息的场景

    源码跟踪

    1. 授权服务

    • org.springframework.security.oauth2.provider.endpoint.CheckTokenEndpoint
    public class CheckTokenEndpoint {
    @RequestMapping(value = "/oauth/check_token", method = RequestMethod.POST)
    	@ResponseBody
    	public Map<String, ?> checkToken(@RequestParam("token") String value) {
    
    		OAuth2AccessToken token = resourceServerTokenServices.readAccessToken(value);
    		if (token == null) {
    			throw new InvalidTokenException("Token was not recognised");
    		}
    
    		if (token.isExpired()) {
    			throw new InvalidTokenException("Token has expired");
    		}
    
    		OAuth2Authentication authentication = resourceServerTokenServices.loadAuthentication(token.getValue());
    
    		Map<String, Object> response = (Map<String, Object>)accessTokenConverter.convertAccessToken(token, authentication);
    
    		// gh-1070
    		response.put("active", true);	// Always true if token exists and not expired
    
    		return response;
    	}
    }
    

    在这里插入图片描述
    跟踪发现返回的信息中authorities字段是一个集合

    2. 资源服务

    使用token-info-uri

    1. 跟踪发现返回的认证信息中,集合全部被解析成了字符串
    2. 跟踪org.springframework.web.client.HttpMessageConverterExtractor
      发现返回的响应信息为xml,其中authorities集合被序列化为多个<authorities>元素,而没有被正确反序列化为集合类型
    • org.springframework.security.oauth2.provider.token.RemoteTokenServices
    public class RemoteTokenServices implements ResourceServerTokenServices {
    	// 校验令牌获取认证信息
    	@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));
    		// 发送post请求调用token-info-uri,获取认证信息
    		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 (map.containsKey("active") && !"true".equals(String.valueOf(map.get("active")))) {
    			logger.debug("check_token returned active attribute: " + map.get("active"));
    			throw new InvalidTokenException(accessToken);
    		}
    
    		return tokenConverter.extractAuthentication(map);
    	}
    	// 发送post请求
    	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;
    	}
    }
    

    在这里插入图片描述
    使用user-info-url

    1. 跟踪发现返回的认证信息中,集合解析为ArrayList
    2. 跟踪org.springframework.web.client.HttpMessageConverterExtractor发现返回的响应信息为json
      在这里插入图片描述
    • org.springframework.boot.autoconfigure.security.oauth2.resourceUserInfoTokenServices
    public class UserInfoTokenServices implements ResourceServerTokenServices {
    @Override
    	public OAuth2Authentication loadAuthentication(String accessToken)
    			throws AuthenticationException, InvalidTokenException {
    		Map<String, Object> map = getMap(this.userInfoEndpointUrl, accessToken);
    		if (map.containsKey("error")) {
    			if (this.logger.isDebugEnabled()) {
    				this.logger.debug("userinfo returned error: " + map.get("error"));
    			}
    			throw new InvalidTokenException(accessToken);
    		}
    		return extractAuthentication(map);
    	}
    }
    

    真相在这里

    进一步跟踪发现:
    请求user-info-url时header.Accept=“application/json”
    请求token-info-url时header.Accept=“application/xml, text/xml, application/json, application/+xml, application/+json”,如果授权服务器支持xml格式contenttype则会有限返回xml格式

    • org.springframework.boot.autoconfigure.security.oauth2.resource.DefaultUserInfoRestTemplateFactory
    public class DefaultUserInfoRestTemplateFactory implements UserInfoRestTemplateFactory {
    @Override
    	public OAuth2RestTemplate getUserInfoRestTemplate() {
    	...
    	// 此处加入了拦截器,为请求头加上Accept="application/json"
    	this.oauth2RestTemplate.getInterceptors()
    					.add(new AcceptJsonRequestInterceptor());
    	...
    	}
    }
    

    解决方案

    以下三种都可以,按需选择

    1. 检查授权服务是否包含jackson-dataformat-xml依赖,删除此依赖则默认返回json数据
    2. 自定义资源服务RemoteTokenServices,header加上Accept=“application/json”
    3. 配置授权服务器默认ContentType
    @Configuration
    @EnableWebMvc
    public class WebConfiguration implements WebMvcConfigurer {
        @Override
        public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
            configurer.defaultContentType(MediaType.APPLICATION_JSON);
        }
    }
    
  • 相关阅读:
    windows 7系统搭建PHP网站环境
    本机搭建PHP环境全教程(图文)
    cmd不是内部命令解决方法
    [Tex学习笔记]章节用罗马字母编号
    丁伟岳院士逝世 享年70岁
    2014年度江西省青年科学家培养对象名单(共36名)
    Alexander Grothendieck去世了
    [詹兴致矩阵论习题参考解答]序言
    2014-2015第一学期听课安排
    一个老和尚的真言
  • 原文地址:https://www.cnblogs.com/luguojun/p/14294698.html
Copyright © 2020-2023  润新知