前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >spring security oauth2 资源服务/客户端无法正确获取权限

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

作者头像
路过君
发布2020-08-11 14:54:14
3.2K0
发布2020-08-11 14:54:14
举报

异常现象

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

spring security 版本2.3.8

资源服务配置

代码语言:javascript
复制
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
代码语言:javascript
复制
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
代码语言:javascript
复制
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
代码语言:javascript
复制
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
代码语言:javascript
复制
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
代码语言:javascript
复制
@Configuration
@EnableWebMvc
public class WebConfiguration implements WebMvcConfigurer {
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.defaultContentType(MediaType.APPLICATION_JSON);
    }
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-08-05 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 异常现象
  • spring security 版本2.3.8
  • 资源服务配置
  • 源码跟踪
    • 1. 授权服务
      • 2. 资源服务
        • 真相在这里
          • 解决方案
          相关产品与服务
          文件存储
          文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档