java oauth cryptography rsa joauth

java - Verificar la solicitud firmada de OAuth1a utilizando Twitter joauth con RSA-SHA1?



cryptography (1)

Tengo un caso de uso para autenticar la solicitud de OAuth1 que se firma con la clave privada RSA y se verifica al final del servidor con la clave pública de RSA.

Encontré esta biblioteca de Twitter que nos ayuda a autenticar / verificar las solicitudes firmadas de Oauth. https://github.com/twitter/joauth

Quiero aprovechar esta biblioteca para verificar la solicitud del método de acción de Jersey o Spring MVC. La solicitud del cliente se habría firmado usando clave privada. En mi extremo, usaría la clave pública del cliente para verificar la solicitud. lo que significa RSA-SHA1 algo.

Twitter joauth parece ser útil pero me falta el código que transformaría HttpServletRequest en OAuthRequest

El archivo de lectura de la biblioteca sugiere esto como una función, pero no pude encontrar un código que haga la transformación javax.servlet.http.HttpServletRequest -> com.twitter.joauth.OAuthRequest .

La verificación de la solicitud ocurre en el método de verificación que tiene la siguiente firma.

public VerifierResult verify(UnpackedRequest.OAuth1Request request, String tokenSecret, String consumerSecret);

En segundo lugar, también quiero saber cuál es la forma más adecuada de usar / leer la clave pública RSA con twitter joauth cuando el método de verificación toma el parámetro String.


Nunca he usado ninguna biblioteca para autenticar usuarios a través de Twitter. Pero acabo de buscar en la solicitud UnpackedRequest.OAuth1. Puede crear una instancia de esta clase completando todos los parámetros. Escribí el creador de Twitter OAuth Header, por lo que puede usarlo para completar esos parámetros o enviar solicitudes POST directamente sin una biblioteca.

Aquí todas las clases lo que necesita:

Firma : para generar una firma OAuth.

public class Signature { private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; public static String calculateRFC2104HMAC(String data, String key) throws java.security.SignatureException { String result; try { SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM); Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM); mac.init(signingKey); byte[] rawHmac = mac.doFinal(data.getBytes()); result = new String(Base64.encodeBase64(rawHmac)); } catch (Exception e) { throw new SignatureException("Failed to generate HMAC : " + e.getMessage()); } return result; } }

NvpComparator : para ordenar los parámetros que necesita en el encabezado.

public class NvpComparator implements Comparator<NameValuePair> { @Override public int compare(NameValuePair arg0, NameValuePair arg1) { String name0 = arg0.getName(); String name1 = arg1.getName(); return name0.compareTo(name1); } }

OAuth : para codificación de URL.

class OAuth{ ... public static String percentEncode(String s) { return URLEncoder.encode(s, "UTF-8") .replace("+", "%20").replace("*", "%2A") .replace("%7E", "~"); } ... }

HeaderCreator : para crear todos los parámetros necesarios y generar un param de encabezado OAuth.

public class HeaderCreator { private String authorization = "OAuth "; private String oAuthSignature; private String oAuthNonce; private String oAuthTimestamp; private String oAuthConsumerSecret; private String oAuthTokenSecret; public String getAuthorization() { return authorization; } public String getoAuthSignature() { return oAuthSignature; } public String getoAuthNonce() { return oAuthNonce; } public String getoAuthTimestamp() { return oAuthTimestamp; } public HeaderCreator(){} public HeaderCreator(String oAuthConsumerSecret){ this.oAuthConsumerSecret = oAuthConsumerSecret; } public HeaderCreator(String oAuthConsumerSecret, String oAuthTokenSecret){ this(oAuthConsumerSecret); this.oAuthTokenSecret = oAuthTokenSecret; } public String getTwitterServerTime() throws IOException, ParseException { HttpsURLConnection con = (HttpsURLConnection) new URL("https://api.twitter.com/oauth/request_token").openConnection(); con.setRequestMethod("HEAD"); con.getResponseCode(); String twitterDate= con.getHeaderField("Date"); DateFormat formatter = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH); Date date = formatter.parse(twitterDate); return String.valueOf(date.getTime() / 1000L); } public String generatedSignature(String url, String method, List<NameValuePair> allParams, boolean withToken) throws SignatureException { oAuthNonce = String.valueOf(System.currentTimeMillis()); allParams.add(new BasicNameValuePair("oauth_nonce", oAuthNonce)); try { oAuthTimestamp = getTwitterServerTime(); allParams.add(new BasicNameValuePair("oauth_timestamp", oAuthTimestamp)); }catch (Exception ex){ //TODO: Log!! } Collections.sort(allParams, new NvpComparator()); StringBuffer params = new StringBuffer(); for(int i=0;i<allParams.size();i++) { NameValuePair nvp = allParams.get(i); if (i>0) { params.append("&"); } params.append(nvp.getName() + "=" + OAuth.percentEncode(nvp.getValue())); } String signatureBaseStringTemplate = "%s&%s&%s"; String signatureBaseString = String.format(signatureBaseStringTemplate, OAuth.percentEncode(method), OAuth.percentEncode(url), OAuth.percentEncode(params.toString())); String compositeKey = OAuth.percentEncode(oAuthConsumerSecret)+"&"; if(withToken) compositeKey+=OAuth.percentEncode(oAuthTokenSecret); oAuthSignature = Signature.calculateRFC2104HMAC(signatureBaseString, compositeKey); return oAuthSignature; } public String generatedAuthorization(List<NameValuePair> allParams){ authorization = "OAuth "; Collections.sort(allParams, new NvpComparator()); for(NameValuePair nvm : allParams){ authorization+=nvm.getName()+"="+OAuth.percentEncode(nvm.getValue())+", "; } authorization=authorization.substring(0,authorization.length()-2); return authorization; } }

Explique:
1. getTwitterServerTime
En oAuthTimestamp no necesita su tiempo de servidor, sino el tiempo de un servidor de Twitter. Puede optimizarlo guardando este parámetro si siempre envía solicitudes en cierto servidor de Twitter.

2. HeaderCreator.generatedSignature (...)
url - lógicamente URL a Twitter API
método - GET o POST. Debes usar siempre "POST"
allParams - Parámetros que sabes que generan la firma ("param_name", "param_value");
withToken - si sabes que oAuthTokenSecret es cierto. De lo contrario, falso.

3. HeaderCreator.generatedAuthorization (...)
Utilice este método después de generateSignature (...) para generar una cadena de encabezado OAuth.
allParams: son los parámetros que ha utilizado en generatedSignature (...) plus: nonce, signature, timestamp . Siempre usa:

allParams.add(new BasicNameValuePair("oauth_nonce", headerCreator.getoAuthNonce())); allParams.add(new BasicNameValuePair("oauth_signature", headerCreator.getoAuthSignature())); allParams.add(new BasicNameValuePair("oauth_timestamp", headerCreator.getoAuthTimestamp()));


Ahora puede usarlo para completar UnpackedRequest.OAuth1Request en su biblioteca.
También aquí un ejemplo para autenticar al usuario en SpringMVC sin la biblioteca:
Solicitudes : para enviar solicitudes de publicaciones.

public class Requests { public static String sendPost(String url, String urlParameters, Map<String, String> prop) throws Exception { URL obj = new URL(url); HttpsURLConnection con = (HttpsURLConnection) obj.openConnection(); con.setRequestMethod("POST"); if(prop!=null) { for (Map.Entry<String, String> entry : prop.entrySet()) { con.setRequestProperty(entry.getKey(), entry.getValue()); } } con.setDoOutput(true); DataOutputStream wr = new DataOutputStream(con.getOutputStream()); wr.writeBytes(urlParameters); wr.flush(); wr.close(); int responseCode = con.getResponseCode(); BufferedReader in; if(responseCode==200) { in = new BufferedReader( new InputStreamReader(con.getInputStream())); }else{ in = new BufferedReader( new InputStreamReader(con.getErrorStream())); } String inputLine; StringBuffer response = new StringBuffer(); while ((inputLine = in.readLine()) != null) { response.append(inputLine); } in.close(); return response.toString(); } }

twAuth (...) - póngalo en su controlador. Ejecútelo cuando un usuario quiera autenticarse en su sitio a través de Twitter.

@RequestMapping(value = "/twauth", method = RequestMethod.GET) @ResponseBody public String twAuth(HttpServletResponse response) throws Exception{ try { String url = "https://api.twitter.com/oauth/request_token"; List<NameValuePair> allParams = new ArrayList<NameValuePair>(); allParams.add(new BasicNameValuePair("oauth_callback", "http://127.0.0.1:8080/twlogin")); allParams.add(new BasicNameValuePair("oauth_consumer_key", "2YhNLyum1VY10UrWBMqBnatiT")); allParams.add(new BasicNameValuePair("oauth_signature_method", "HMAC-SHA1")); allParams.add(new BasicNameValuePair("oauth_version", "1.0")); HeaderCreator headerCreator = new HeaderCreator("RUesRE56vVWzN9VFcfA0jCBz9VkvkAmidXj8d1h2tS5EZDipSL"); headerCreator.generatedSignature(url,"POST",allParams,false); allParams.add(new BasicNameValuePair("oauth_nonce", headerCreator.getoAuthNonce())); allParams.add(new BasicNameValuePair("oauth_signature", headerCreator.getoAuthSignature())); allParams.add(new BasicNameValuePair("oauth_timestamp", headerCreator.getoAuthTimestamp())); Map<String, String> props = new HashMap<String, String>(); props.put("Authorization", headerCreator.generatedAuthorization(allParams)); String twitterResponse = Requests.sendPost(url,"",props); Integer indOAuthToken = twitterResponse.indexOf("oauth_token"); String oAuthToken = twitterResponse.substring(indOAuthToken, twitterResponse.indexOf("&",indOAuthToken)); response.sendRedirect("https://api.twitter.com/oauth/authenticate?" + oAuthToken); }catch (Exception ex){ //TODO: Log throw new Exception(); } return "main"; }

twLogin (...) : póngalo en su controlador. Es una devolución de llamada desde Twitter.

@RequestMapping(value = "/twlogin", method = RequestMethod.GET) public String twLogin(@RequestParam("oauth_token") String oauthToken, @RequestParam("oauth_verifier") String oauthVerifier, Model model, HttpServletRequest request){ try { if(oauthToken==null || oauthToken.equals("") || oauthVerifier==null || oauthVerifier.equals("")) return "main"; String url = "https://api.twitter.com/oauth/access_token"; List<NameValuePair> allParams = new ArrayList<NameValuePair>(); allParams.add(new BasicNameValuePair("oauth_consumer_key", "2YhNLyum1VY10UrWBMqBnatiT")); allParams.add(new BasicNameValuePair("oauth_signature_method", "HMAC-SHA1")); allParams.add(new BasicNameValuePair("oauth_token", oauthToken)); allParams.add(new BasicNameValuePair("oauth_version", "1.0")); NameValuePair oAuthVerifier = new BasicNameValuePair("oauth_verifier", oauthVerifier); allParams.add(oAuthVerifier); HeaderCreator headerCreator = new HeaderCreator("RUesRE56vVWzN9VFcfA0jCBz9VkvkAmidXj8d1h2tS5EZDipSL"); headerCreator.generatedSignature(url,"POST",allParams,false); allParams.add(new BasicNameValuePair("oauth_nonce", headerCreator.getoAuthNonce())); allParams.add(new BasicNameValuePair("oauth_signature", headerCreator.getoAuthSignature())); allParams.add(new BasicNameValuePair("oauth_timestamp", headerCreator.getoAuthTimestamp())); allParams.remove(oAuthVerifier); Map<String, String> props = new HashMap<String, String>(); props.put("Authorization", headerCreator.generatedAuthorization(allParams)); String twitterResponse = Requests.sendPost(url,"oauth_verifier="+oauthVerifier,props); //Get user id Integer startIndexTmp = twitterResponse.indexOf("user_id")+8; Integer endIndexTmp = twitterResponse.indexOf("&",startIndexTmp); if(endIndexTmp<=0) endIndexTmp = twitterResponse.length()-1; Long userId = Long.parseLong(twitterResponse.substring(startIndexTmp, endIndexTmp)); //Do what do you want... }catch (Exception ex){ //TODO: Log throw new Exception(); } }