Announcement Announcement Module
Collapse
No announcement yet.
OAuth 1 parameter transmission oauth_signature URL encoding Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • OAuth 1 parameter transmission oauth_signature URL encoding

    I am having an issue with the OAuth not URL encoding oauth_signature for transmission using request URI query with version 1.0.0.RC2. The OAuth specification says it should be encoded but that does not seem to be happening.

    The problem occurs most commonly when the oauth_signature parameter in the URL contains a "+". This is converted to a space by the server and the signature, which then causes the signatue that is calculated by the server to differ, for example:

    Code:
    
    # Parameter in URL 
    &oauth_signature=Dtg4ar/FKoTGOVwKbam+gcLtRs4=
    
    # Server Output
    Signature value generated by server= "Dtg4ar/FKoTGOVwKbam+gcLtRs4="
    Signature value supplied by client    = "Dtg4ar/FKoTGOVwKbam gcLtRs4="
    I have tried serveral things including using an Interceptor but the HttpRequest there does not contain the parameter information. I would really appreciate any suggestions?

    Yours Bart.



    The code I am using is:

    Code:
    private ProtectedResourceDetails buildProtectedResource() {
    		
    		BaseProtectedResourceDetails protectedResource = new BaseProtectedResourceDetails();
    		Properties properties = Utility.getProperties();
    		String consumerKey = properties.getProperty("consumerKey");
    		String consumerSecret = properties.getProperty("consumerSecret");
    		SharedConsumerSecret sharedConsumerSecret = new SharedConsumerSecret(consumerSecret);
    		
    		protectedResource.setConsumerKey(consumerKey);
    		protectedResource.setSharedSecret(sharedConsumerSecret);
    		protectedResource.setUse10a(true);
    		protectedResource.setId(resourceId);
    		protectedResource.setAcceptsAuthorizationHeader(false);
    		
    		return protectedResource;
    	}
    	
    	private OAuthConsumerSupport buildConsumerSupport(final ProtectedResourceDetails protectedResource) {
    		CoreOAuthConsumerSupport consumerSupport = new CoreOAuthConsumerSupport();
    		consumerSupport.setStreamHandlerFactory(new DefaultOAuthURLStreamHandlerFactory());
    		consumerSupport.setProtectedResourceDetailsService(new ProtectedResourceDetailsService() {
    		    @Override
    		    public ProtectedResourceDetails loadProtectedResourceDetailsById(String s) {
    		    	return protectedResource;
    		    }
    		});
    		return consumerSupport;
    	}
    	
    	private URL getNewUrl(String hostname, String controller, String action) {
    		String urlString = "https://" + hostname + controller + action;
    		
    		try {
    			return new URL(urlString);
    		}
    		catch (MalformedURLException e) {
    			throw new RuntimeException(e);
    		}
    	}
    	
    	public String seekQuery(String hostname, String controller, String action) {
    		final ProtectedResourceDetails protectedResource = buildProtectedResource();
    		OAuthConsumerSupport consumerSupport = buildConsumerSupport(protectedResource);
    
    		URL url = getNewUrl(hostname, controller, action);
    		log.info("url: " + url.toString());
    		Map<String, OAuthConsumerToken> tokens = new Hashtable<String, OAuthConsumerToken>();
    		OAuthConsumerToken token = new OAuthConsumerToken();
    		tokens.put(resourceId, token);
    		
    		OAuthSecurityContextImpl securityContext = new OAuthSecurityContextImpl();
    		securityContext.setAccessTokens(tokens);
    		
    		OAuthSecurityContextHolder.setContext(securityContext);
    		
    		OAuthRestTemplate restTemplate = new OAuthRestTemplate(protectedResource);
    		restTemplate.setSupport(consumerSupport);
    
    		List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
    		interceptors.add( new SeekRestInterceptor());
    		restTemplate.setInterceptors(interceptors);
    		
    		String result = restTemplate.getForObject(url.toString(), String.class);
    		
    		return result;
    	}

  • #2
    I think this is https://jira.springsource.org/browse/SECOAUTH-56. It's quite hard to fix without a test, so if you have any ideas about how to fix it or to create a test, contributions are more than welcome.

    Comment


    • #3
      After I posted I found a reference in the documentation of some code on the spring social site that referred to the same problem.

      I have a possible solution that I am testing now, I have written a sub class for HMAC_SHA1SignatureMethod class that url encodes the signature. You also need to write a sub class for the CoreOAuthSignatureMethodFactory and some other sub classes to insure it is used. There I will post the code once I am confident it works.

      Bart.

      Comment


      • #4
        Solution to problem

        I wrote 2 new classes: a decorator class that implements the OAuthSignatureMethod interface and a sub class of the CoreOAuthSignatureMethodFactory class. The decorator simply URL ecodes the String retruned by the sign method and the factory sub class wraps the OAuthSignatureMethod object returned by the getSignatureMethod method of the super class.

        You need to make an additonal call on the OAuthRestTemplate in the client or it will not use the new CoreOAuthSignatureMethodFactory sub class. This is the call to:

        restTemplate.setRequestFactory(requestFactory);

        I would have thought that putting both the ProtectedResourceDetails and OAuthConsumerSupport in the OAuthRestTemplate would have been enough but apparently not.

        The code:

        SignatureMethodUrlEncode Class
        Code:
        public class SignatureMethodUrlEncode implements OAuthSignatureMethod {
        	
        	private static Logger log = Logger.getLogger(SignatureMethodUrlEncode.class);
        	
        	private OAuthSignatureMethod signatureMethod;
        	
        	/**
        	 * The constructor accepts a parameter that implements the OAuthSignatureMethod. 
        	 * 
        	 * @param signatureMethod
        	 */
        	SignatureMethodUrlEncode(OAuthSignatureMethod signatureMethod) {
        		this.signatureMethod = signatureMethod;
        	}
        	
        	/**
        	 * Simple delegates the method call to the OAuthSignatureMethod object it was constructed with.
        	 */
        	public String getName() {
        		return signatureMethod.getName();
        	}
        	
        	/**
        	 * The value returned by the OAuthSignatureMethod it delegates to is URL Encoded. 
        	 * 
        	 * @param signatureBaseString
        	 */
        	public String sign(String signatureBaseString) {
        		log.info("signatureBaseString: " + signatureBaseString);
        		
        		String signature = signatureMethod.sign(signatureBaseString);
        		log.info("signature: " + signature);
        		
        		try {
        			String encodedSignature = URLEncoder.encode(signature, "UTF-8");
        			log.info("encodedSignature: " + encodedSignature);
        			
        			return encodedSignature;
        		}
        		catch (UnsupportedEncodingException e) {
        			e.printStackTrace();
        		}
        		return signature;
        	}
        	
        	/**
        	 * Simple delegates the method call to the OAuthSignatureMethod object it was constructed with.
        	 * 
        	 * @param signatureBaseString
        	 * @param signature
        	 */
        	public void verify(String signatureBaseString, String signature) throws InvalidSignatureException {
        		signatureMethod.verify(signatureBaseString, signature);
        	}
        	
        }
        OAuthSignatureMethodFactoryUrlEncode Class
        Code:
        public class OAuthSignatureMethodFactoryUrlEncode extends CoreOAuthSignatureMethodFactory {
        	
        	private static Logger log = Logger.getLogger(OAuthSignatureMethodFactoryUrlEncode.class);
        	
        	/**
        	 * This method  decorates the OAuthSignatureMethod returned by a getSignatureMethod call 
        	 * to the super class with a SignatureMethodUrlEncode object. 
        	 */
        	@Override
        	public OAuthSignatureMethod getSignatureMethod(String methodName, SignatureSecret signatureSecret, String tokenSecret) {
        		log.debug("methodName: " + methodName);
        		log.debug("signatureSecret: " + signatureSecret.toString());
        		log.debug("tokenSecret: " + tokenSecret);
        		
        		OAuthSignatureMethod signatureMethod = super.getSignatureMethod(methodName, signatureSecret, tokenSecret);
        		
        		return new SignatureMethodUrlEncode(signatureMethod);
        	}
        
        }
        Client Class
        Code:
        public class SeekClient {
        
        	private static Logger log = Logger.getLogger(SeekClient.class);
        	
        	private final static String RESOURCE_ID = "resourceId";
        	
        	private final ProtectedResourceDetails protectedResource = buildProtectedResource();
        	private OAuthConsumerSupport consumerSupport = buildConsumerSupport();
        	private ClientHttpRequestFactory requestFactory = buildSeekClientHttpRequestFactory();
        	
        	private ProtectedResourceDetails buildProtectedResource() {
        		BaseProtectedResourceDetails protectedResource = new BaseProtectedResourceDetails();
        		Properties properties = Utility.getProperties();
        		String consumerKey = properties.getProperty("consumerKey");
        		String consumerSecret = properties.getProperty("consumerSecret");
        		SharedConsumerSecret sharedConsumerSecret = new SharedConsumerSecret(consumerSecret);
        		
        		protectedResource.setConsumerKey(consumerKey);
        		protectedResource.setSharedSecret(sharedConsumerSecret);
        		protectedResource.setUse10a(false);
        		protectedResource.setId(RESOURCE_ID);
        		protectedResource.setAcceptsAuthorizationHeader(false);
        		
        		return protectedResource;
        	}
        	
        	private OAuthConsumerSupport buildConsumerSupport() {
        		CoreOAuthConsumerSupport consumerSupport = new CoreOAuthConsumerSupport();
        		
        		consumerSupport.setStreamHandlerFactory(new DefaultOAuthURLStreamHandlerFactory());
        		consumerSupport.setSignatureFactory(new OAuthSignatureMethodFactoryUrlEncode()); // new decorator for signature generation
        		consumerSupport.setProtectedResourceDetailsService(new ProtectedResourceDetailsService() {
        		    @Override
        		    public ProtectedResourceDetails loadProtectedResourceDetailsById(String s) {
        		    	return protectedResource;
        		    }
        		});
        //		log.info("OAuth params  Query: " + consumerSupport.getOAuthQueryString(protectedResource, null, url, "GET", null));
        //		log.info("OAuth params Header: " + consumerSupport.getAuthorizationHeader(protectedResource, null, url, "GET", null));
        
        		return consumerSupport;
        	}
        	
        	/*
        	 * This needs to be built so that the OAuthSignatureMethodFactoryUrlEncode is called rather than 
        	 * the default.
        	 */
        	private ClientHttpRequestFactory buildSeekClientHttpRequestFactory() {
        		ClientHttpRequestFactory springRequestFactory = new SimpleClientHttpRequestFactory();
        //		OAuthClientHttpRequestFactory requestFactory = new OAuthClientHttpRequestFactorySeek(springRequestFactory, protectedResource, consumerSupport);
        		OAuthClientHttpRequestFactory requestFactory = new OAuthClientHttpRequestFactory(springRequestFactory, protectedResource, consumerSupport);
        		
        		return requestFactory;
        	}
        	
        	/**
        	 * Makes a REST call to the specified URL. The servers expects OAuth 1.0a and all OAuth parameters 
        	 * on the URL of the Get call.
        	 *  
        	 * @param url URL of the REST server. 
        	 * @return String from REST call. 
        	 */
        	public String restQuery(URL url) {
        		log.info("URL: " + url);
        		
        		Map<String, OAuthConsumerToken> tokens = new Hashtable<String, OAuthConsumerToken>();
        		OAuthConsumerToken token = new OAuthConsumerToken();
        		tokens.put(RESOURCE_ID, token);
        		
        		OAuthSecurityContextImpl securityContext = new OAuthSecurityContextImpl();
        		securityContext.setAccessTokens(tokens);
        		
        		OAuthSecurityContextHolder.setContext(securityContext);
        		
        		OAuthRestTemplate restTemplate = new OAuthRestTemplate(protectedResource);
        		restTemplate.setSupport(consumerSupport);
        		
        		//If this call is not made then the OAuthSignatureMethodFactoryUrlEncode will not be used in making rest calls. 
        		restTemplate.setRequestFactory(requestFactory); 
        		
        		return restTemplate.getForObject(url.toString(), String.class);
        	}
        	
        }

        Comment


        • #5
          That's potentially really useful. Are you suggesting that these should be the default implementations of those strategies? Are there any test cases? (Please also read the contributor's guidleines in the README.)

          Comment


          • #6
            I do not have enough experience with OAuth or knowledge about the specification to say with confidence that it should be the default. My quick reading of the relevant sections of the 1.0a specification is that the signature should be URL encoded if you are using the URL transmit the OAuth parameters, not sure that would work if you are using the authorisation headers or body to transmit the parameters.

            The one thing that did surprise me and initially made me think this would not work is the the SignatueMethod is not used unless you make a OAuthClientHttpRequestFactory with the call:

            Code:
            OAuthClientHttpRequestFactory requestFactory = new OAuthClientHttpRequestFactory(springRequestFactory, protectedResource, consumerSupport);
            Then add that to the OAuthRestTemplate, despite the fact the the OAuthConsumerSupport was already being added to the OAuthRestTemplate. If you are setting the OAuthConsumerSupport on the OAuthRestTemplate I would expect the default OAuthClientHttpRequestFactory to use it. The alternative might be to have a constructor where you can add the OAuthConsumerSupport but then you should probably remove the setter for that attribute. I think it would then conform to the principle of "least astonishment" a little better.

            I am working on the testing now.

            Comment


            • #7
              As I was working on the testing I made some changes to the SignatureMethodUrlEncode that make class more complete.

              Code:
              public class SignatureMethodUrlEncode implements OAuthSignatureMethod {
              	
              	private static final String NAME_PREFIX  = "UrlEncoded-";
              	
              	private static Logger log = Logger.getLogger(SignatureMethodUrlEncode.class);
              	
              	private OAuthSignatureMethod signatureMethod;
              	private String name;
              	
              	/**
              	 * The constructor accepts a parameter that implements the OAuthSignatureMethod. 
              	 * It names the object by prefixing the name returned by the delegate OAuthSignatureMethod 
              	 * with the value of NAME_PREFIX.
              	 * 
              	 * @param signatureMethod
              	 */
              	SignatureMethodUrlEncode(OAuthSignatureMethod signatureMethod) {
              		this.signatureMethod = signatureMethod;
              		name = NAME_PREFIX + signatureMethod.getName();
              	}
              	
              	/**
              	 * Name of the signature object.  
              	 */
              	public String getName() {
              		return name;
              	}
              	
              	/**
              	 * The value returned by the OAuthSignatureMethod it delegates to is URL Encoded. 
              	 * 
              	 * @param signatureBaseString
              	 */
              	public String sign(String signatureBaseString) {
              		log.debug("signatureBaseString: " + signatureBaseString);
              		
              		String signature = signatureMethod.sign(signatureBaseString);
              		log.debug("signature: " + signature);
              		
              		try {
              			String encodedSignature = URLEncoder.encode(signature, "UTF-8");
              			log.debug("encodedSignature: " + encodedSignature);
              			
              			return encodedSignature;
              		}
              		catch (UnsupportedEncodingException e) {
              			e.printStackTrace();
              		}
              		return signature;
              	}
              	
              	/**
              	 * Simply delegates the method call to the OAuthSignatureMethod object it was constructed with
              	 * after URL decoding the encodedSignature parameter. 
              	 * 
              	 * @param signatureBaseString
              	 * @param encodedSignature This is URL decoded before being used to invoke the delegate method.
              	 */
              	public void verify(String signatureBaseString, String encodedSignature) throws InvalidSignatureException {
              		log.debug("signatureBaseString: " + signatureBaseString + " encodedSignature: " + encodedSignature);
              		
              		try {
              			String signature = URLDecoder.decode(encodedSignature, "UTF-8");
              			
              			log.debug("signature: " + signature);
              			
              			signatureMethod.verify(signatureBaseString, signature);
              		}
              		catch (UnsupportedEncodingException e) {
              			e.printStackTrace();
              		}
              	}
              
              }

              Comment

              Working...
              X