zoukankan      html  css  js  c++  java
  • 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);
        }
    }
    
  • 相关阅读:
    mysql BETWEEN操作符 语法
    mysql IN操作符 语法
    mysql LIKE通配符 语法
    mysql TOP语句 语法
    mysql DELETE语句 语法
    mysql Update语句 语法
    mysql INSERT语句 语法
    mysql ORDER BY语句 语法
    mysql OR运算符 语法
    mysql AND运算符 语法
  • 原文地址:https://www.cnblogs.com/luguojun/p/14294698.html
Copyright © 2011-2022 走看看