日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

使用HMAC(Play 2.0)保护REST服务

發(fā)布時(shí)間:2023/12/3 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用HMAC(Play 2.0)保护REST服务 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
我們有HTTPS,還需要什么?

當(dāng)您談?wù)摶赗EST的API的安全性時(shí),人們通常會(huì)指向HTTPS。 借助HTTPS,您可以使用每個(gè)人都熟悉的方法輕松保護(hù)您的服務(wù)免遭窺視。 但是,當(dāng)您需要更高級(jí)別的安全性或HTTPS不可用時(shí),您需要替代方法。 例如,您可能需要跟蹤每個(gè)客戶對(duì)API的使用情況,或者需要確切地知道誰在進(jìn)行所有這些調(diào)用。 您可以將HTTPS與客戶端身份驗(yàn)證一起使用,但這將需要設(shè)置完整的PKI基礎(chǔ)結(jié)構(gòu)以及一種安全的方式來標(biāo)識(shí)您的客戶并交換私鑰。 與基于SOAP的WS-Security服務(wù)相比,我們沒有可用于REST的標(biāo)準(zhǔn)。

解決此問題的常用方法(Microsoft,Amazon,Google和Yahoo采用此方法)是通過基于客戶端與服務(wù)之間的共享機(jī)密對(duì)消息進(jìn)行簽名。 請(qǐng)注意,使用這種方法,我們僅對(duì)數(shù)據(jù)進(jìn)行簽名,而不對(duì)數(shù)據(jù)進(jìn)行加密。 在這種情況下,我們所討論的簽名通常稱為基于哈希的消息認(rèn)證代碼(簡稱HMAC)。 使用HMAC,我們根據(jù)已交換的密鑰為請(qǐng)求創(chuàng)建消息認(rèn)證碼(MAC)。

在本文中,我將向您展示如何為基于Play 2.0的REST服務(wù)實(shí)現(xiàn)此算法。 如果您使用其他技術(shù),則步驟將幾乎相同。

HMAC方案

對(duì)于客戶端,我將僅使用基于HTTPClient的簡單應(yīng)用程序。 要實(shí)現(xiàn)這一點(diǎn),我們必須采取以下步驟:

  • 首先,我們需要與外部客戶端交換共享機(jī)密。 通常,這是由API提供程序使用電子郵件發(fā)送給客戶端的,或者提供程序具有一個(gè)您可以在其中查找共享密鑰的網(wǎng)站。 請(qǐng)注意,此機(jī)密僅在您和服務(wù)之間共享,每個(gè)客戶端將具有不同的共享機(jī)密。 這不是像公用密鑰那樣共享的東西,
  • 為了確??蛻舳撕头?wù)在同一內(nèi)容上計(jì)算簽名,我們需要對(duì)要簽名的請(qǐng)求進(jìn)行規(guī)范化。 如果我們不這樣做,則服務(wù)器可能會(huì)以與客戶端不同的方式解釋空格,并得出簽名無效的結(jié)論。
  • 基于此規(guī)范化消息,客戶端使用共享機(jī)密創(chuàng)建HMAC值。
  • 現(xiàn)在,客戶端已準(zhǔn)備好將請(qǐng)求發(fā)送到服務(wù)。 他將HMAC值添加到標(biāo)頭中,還將一些內(nèi)容標(biāo)識(shí)為用戶。 例如,用戶名或其他公共值。
  • 當(dāng)服務(wù)收到請(qǐng)求時(shí),它將從標(biāo)頭中提取用戶名和HMAC值。
  • 根據(jù)用戶名,服務(wù)知道應(yīng)該使用哪個(gè)共享密鑰對(duì)消息進(jìn)行簽名。 例如,該服務(wù)將從某處的數(shù)據(jù)存儲(chǔ)中檢索此信息。
  • 現(xiàn)在,服務(wù)以與客戶端相同的方式對(duì)請(qǐng)求進(jìn)行規(guī)范化,并為其自身計(jì)算HMAC值。
  • 如果來自客戶端的HMAC與從服務(wù)器計(jì)算出的HMAC相匹配,則您將知道消息的完整性得到保證,并且客戶端就是他所說的身份。 如果提供了錯(cuò)誤的用戶名,或者使用了錯(cuò)誤的機(jī)密來計(jì)算標(biāo)題,則HMAC值將不匹配。
  • 要實(shí)現(xiàn)HMAC,我們需要做什么? 在以下部分中,我們將研究以下主題。

    • 確定用于輸入的字段。
    • 創(chuàng)建可以計(jì)算此HMAC的客戶端代碼并添加相應(yīng)的標(biāo)頭
    • 創(chuàng)建基于Play 2.0的攔截器來檢查HMAC標(biāo)頭

    確定輸入字段

    我們要做的第一件事是確定HMAC計(jì)算的輸入。 下表描述了我們將包括的元素:

    領(lǐng)域 描述
    HTTP方法 使用REST,我們執(zhí)行的HTTP方法定義了服務(wù)器端的行為。 對(duì)特定URL的刪除與對(duì)該URL的GET處理不同。
    內(nèi)容MD5 此HTTP標(biāo)頭是標(biāo)準(zhǔn)HTTP標(biāo)頭。 這是請(qǐng)求正文的MD5哈希。 如果我們將此標(biāo)頭包含在HMAC代碼生成中,則會(huì)獲得一個(gè)HMAC值,該值會(huì)隨著請(qǐng)求正文的更改而更改。
    Content-Type標(biāo)頭 進(jìn)行REST調(diào)用時(shí),Content-Type標(biāo)頭是重要的標(biāo)頭。 服務(wù)器可以根據(jù)媒體類型對(duì)請(qǐng)求做出不同的響應(yīng),因此應(yīng)將其包含在HMAC中。
    日期標(biāo)題 我們還包括創(chuàng)建請(qǐng)求以計(jì)算HMAC的日期。 在服務(wù)器端,我們可以確保日期在傳輸中沒有更改。 除此之外,我們可以在服務(wù)器上添加消息過期功能。
    路徑 由于URI標(biāo)識(shí)REST中的資源,因此調(diào)用的URL的路徑部分也用于HMAC計(jì)算。

    我們將包括的幾乎是來自請(qǐng)求的以下信息:

    PUT /example/resource/1 Content-Md5: uf+Fg2jkrCZgzDcznsdwLg== Content-Type: text/plain; charset=UTF-8 Date: Tue, 26 Apr 2011 19:59:03 CEST

    可用于創(chuàng)建HMAC簽名的客戶端代碼

    在下面,您可以看到我們用來調(diào)用受HMAC保護(hù)的服務(wù)的客戶端代碼。 這只是一個(gè)基于HTTPClient的快速客戶端,我們可以使用它來測(cè)試我們的服務(wù)。

    public class HMACClient {private final static String DATE_FORMAT = "EEE, d MMM yyyy HH:mm:ss z";private final static String HMAC_SHA1_ALGORITHM = "HmacSHA1";private final static String SECRET = "secretsecret";private final static String USERNAME = "jos";private static final Logger LOG = LoggerFactory.getLogger(HMACClient.class);public static void main(String[] args) throws HttpException, IOException, NoSuchAlgorithmException {HMACClient client = new HMACClient();client.makeHTTPCallUsingHMAC(USERNAME);}public void makeHTTPCallUsingHMAC(String username) throws HttpException, IOException, NoSuchAlgorithmException {String contentToEncode = "{\"comment\" : {\"message\":\"blaat\" , \"from\":\"blaat\" , \"commentFor\":123}}";String contentType = "application/vnd.geo.comment+json";//String contentType = "text/plain";String currentDate = new SimpleDateFormat(DATE_FORMAT).format(new Date());HttpPost post = new HttpPost("http://localhost:9000/resources/rest/geo/comment");StringEntity data = new StringEntity(contentToEncode,contentType,"UTF-8");post.setEntity(data);String verb = post.getMethod();String contentMd5 = calculateMD5(contentToEncode);String toSign = verb + "\n" + contentMd5 + "\n"+ data.getContentType().getValue() + "\n" + currentDate + "\n"+ post.getURI().getPath();String hmac = calculateHMAC(SECRET, toSign);post.addHeader("hmac", username + ":" + hmac);post.addHeader("Date", currentDate);post.addHeader("Content-Md5", contentMd5);HttpClient client = new DefaultHttpClient();HttpResponse response = client.execute(post);System.out.println("client response:" + response.getStatusLine().getStatusCode());}private String calculateHMAC(String secret, String data) {try {SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes(), HMAC_SHA1_ALGORITHM);Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);mac.init(signingKey);byte[] rawHmac = mac.doFinal(data.getBytes());String result = new String(Base64.encodeBase64(rawHmac));return result;} catch (GeneralSecurityException e) {LOG.warn("Unexpected error while creating hash: " + e.getMessage(), e);throw new IllegalArgumentException();}}private String calculateMD5(String contentToEncode) throws NoSuchAlgorithmException {MessageDigest digest = MessageDigest.getInstance("MD5");digest.update(contentToEncode.getBytes());String result = new String(Base64.encodeBase64(digest.digest()));return result;} }

    然后使用HMAC算法基于共享機(jī)密創(chuàng)建簽名。

    private String calculateHMAC(String secret, String data) {try {SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes(), HMAC_SHA1_ALGORITHM);Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);mac.init(signingKey);byte[] rawHmac = mac.doFinal(data.getBytes());String result = new String(Base64.encodeBase64(rawHmac));return result;} catch (GeneralSecurityException e) {LOG.warn("Unexpected error while creating hash: " + e.getMessage(), e);throw new IllegalArgumentException();}}

    計(jì)算完HMAC值后,我們需要將其發(fā)送到服務(wù)器。 為此,我們提供了一個(gè)自定義標(biāo)頭:

    post.addHeader("hmac", username + ":" + hmac);

    如您所見,我們還添加了用戶名。 服務(wù)器需要使用它來確定在服務(wù)器端使用哪個(gè)密鑰來計(jì)算HMAC值。 現(xiàn)在,當(dāng)我們運(yùn)行此代碼時(shí),將執(zhí)行一個(gè)簡單的POST操作,將以下請(qǐng)求發(fā)送到服務(wù)器:

    POST /resources/rest/geo/comment HTTP/1.1[\r][\n] hmac: jos:+9tn0CLfxXFbzPmbYwq/KYuUSUI=[\r][\n] Date: Mon, 26 Mar 2012 21:34:33 CEST[\r][\n] Content-Md5: r52FDQv6V2GHN4neZBvXLQ==[\r][\n] Content-Length: 69[\r][\n] Content-Type: application/vnd.geo.comment+json; charset=UTF-8[\r][\n] Host: localhost:9000[\r][\n] Connection: Keep-Alive[\r][\n] User-Agent: Apache-HttpClient/4.1.3 (java 1.5)[\r][\n] [\r][\n] {"comment" : {"message":"blaat" , "from":"blaat" , "commentFor":123}}

    在Scala中實(shí)現(xiàn)/播放

    到目前為止,我們已經(jīng)看到客戶需要做什么才能為我們提供正確的標(biāo)題。 服務(wù)提供商通常會(huì)提供多種語言的特定庫,用于處理消息簽名的詳細(xì)信息。 但是,正如您所看到的,手工完成并不困難。 現(xiàn)在,讓我們看一下服務(wù)器端,在此我們將scala與Play 2.0框架一起使用,以檢查提供的標(biāo)頭是否包含正確的信息。 有關(guān)設(shè)置正確的Scala環(huán)境以測(cè)試此代碼的更多信息,請(qǐng)參閱我以前在scala上的帖子( http://www.smartjava.org/content/play-20-akka-rest-json-and-dependencies )。

    首先要做的是設(shè)置正確的路由以支持此POST操作。 我們?cè)赾onf / routes文件中執(zhí)行此操作

    POST /resources/rest/geo/comment controllers.Application.addComment

    這是基本的Play功能。 對(duì)/ resource / rest / geo / comment URL的所有POST調(diào)用都將傳遞到指定的控制器。 讓我們看一下該操作的樣子:

    def addComment() = Authenticated {(user, request) => {// convert the supplied json to a comment objectval comment = Json.parse(request.body.asInstanceOf[String]).as[Comment]// pass the comment object to a service for processingcommentService.storeComment(comment)println(Json.toJson(comment))Status(201)}}

    現(xiàn)在,它變得更加復(fù)雜了。 如您在上面的清單中所見,我們定義了一個(gè)addComment操作。 但是,與其直接定義這樣的動(dòng)作,不如:

    def processGetAllRequest() = Action {val result = service.processGetAllRequest;Ok(result).as("application/json");}

    我們改為這樣定義它:

    def addComment() = Authenticated {(user, request) => {

    我們?cè)谶@里所做的是創(chuàng)建一個(gè)復(fù)合動(dòng)作http://www.playframework.org/documentation/2.0/ScalaActionsComposition )。 因?yàn)镾cala是一種功能語言,所以我們可以輕松地做到這一點(diǎn)。 您在此處看到的“已認(rèn)證”引用只是對(duì)簡單函數(shù)的簡單引用,該函數(shù)以另一個(gè)函數(shù)作為參數(shù)。 在“已驗(yàn)證”功能中,我們將檢查HMAC簽名。 您可以將其讀為使用批注,但現(xiàn)在無需任何特殊構(gòu)造。 因此,我們的HMAC檢查是什么樣的。

    import play.api.mvc.Action import play.api.Logger import play.api.mvc.RequestHeader import play.api.mvc.Request import play.api.mvc.AnyContent import play.api.mvc.Result import controllers.Application._ import java.security.MessageDigest import javax.crypto.spec.SecretKeySpec import javax.crypto.Mac import org.apache.commons.codec.binary.Base64 import play.api.mvc.RawBuffer import play.api.mvc.Codec/*** Obejct contains security actions that can be applied to a specific action called from* a controller.*/ object SecurityActions {val HMAC_HEADER = "hmac"val CONTENT_TYPE_HEADER = "content-type"val DATE_HEADER = "Date"val MD5 = "MD5"val HMACSHA1 = "HmacSHA1"/*** Function authenticated is defined as a function that takes as parameter* a function. This function takes as argumens a user and a request. The authenticated* function itself, returns a result.** This Authenticated function will extract information from the request and calculate* an HMAC value.***/def Authenticated(f: (User, Request[Any]) => Result) = {// we parse this as tolerant text, since our content type// is application/vnd.geo.comment+json, which isn't picked// up by the default body parsers. Alternative would be// to parse the RawBuffer manuallyAction(parse.tolerantText) {request =>{// get the header we're working withval sendHmac = request.headers.get(HMAC_HEADER);// Check whether we've recevied an hmac headersendHmac match {// if we've got a value that looks like our header case Some(x) if x.contains(":") && x.split(":").length == 2 => {// first part is username, second part is hashval headerParts = x.split(":");val userInfo = User.find(headerParts(0))// Retrieve all the headers we're going to use, we parse the complete // content-type header, since our client also does thisval input = List(request.method,calculateMD5(request.body),request.headers.get(CONTENT_TYPE_HEADER),request.headers.get(DATE_HEADER),request.path)// create the string that we'll have to signval toSign = input.map(a => {a match {case None => ""case a: Option[Any] => a.asInstanceOf[Option[Any]].getcase _ => a}}).mkString("\n")// use the input to calculate the hmacval calculatedHMAC = calculateHMAC(userInfo.secret, toSign)// if the supplied value and the received values are equal// return the response from the delegate action, else return// unauthorizedif (calculatedHMAC == headerParts(1)) {f(userinfo, request)} else {Unauthorized}}// All the other possibilities return to 401 case _ => Unauthorized}}}}/*** Calculate the MD5 hash for the specified content*/private def calculateMD5(content: String): String = {val digest = MessageDigest.getInstance(MD5)digest.update(content.getBytes())new String(Base64.encodeBase64(digest.digest()))}/*** Calculate the HMAC for the specified data and the supplied secret*/private def calculateHMAC(secret: String, toEncode: String): String = {val signingKey = new SecretKeySpec(secret.getBytes(), HMACSHA1)val mac = Mac.getInstance(HMACSHA1)mac.init(signingKey)val rawHmac = mac.doFinal(toEncode.getBytes())new String(Base64.encodeBase64(rawHmac))} }

    那是很多代碼,但是其中大多數(shù)將很容易理解。 “ calculateHMAC”和“ calculateMD5”方法只是圍繞Java功能的基本scala包裝器。 該類內(nèi)的文檔應(yīng)該足以了解正在發(fā)生的事情。 但是,我確實(shí)想在這段代碼中突出幾個(gè)有趣的概念。 首先是方法簽名:

    def Authenticated(f: (User, Request[Any]) => Result) = {

    這意味著Authenticated方法本身將另一個(gè)方法(或函數(shù),如果要調(diào)用該方法)作為參數(shù)。 如果回頭看我們的路線目標(biāo),您會(huì)發(fā)現(xiàn)我們只是這樣做:

    def addComment() = Authenticated {(user, request) => ...

    現(xiàn)在,當(dāng)調(diào)用此“已認(rèn)證”方法時(shí)會(huì)發(fā)生什么? 我們要做的第一件事是檢查HMAC標(biāo)頭是否存在并且格式正確:

    val sendHmac = request.headers.get(HMAC_HEADER);sendHmac match {// if we've got a value that looks like our header case Some(x) if x.contains(":") && x.split(":").length == 2 => {...}// All the other possibilities return to 401 case _ => Unauthorized

    我們通過對(duì)HMAC標(biāo)頭使用匹配來實(shí)現(xiàn)。 如果它包含正確格式的值,則我們將處理標(biāo)頭并以與客戶端相同的方式計(jì)算HMAC值。 如果不是,則返回401。如果HMAC值正確,則使用以下代碼將其委托給提供的函數(shù):

    if (calculatedHMAC == headerParts(1)) {f(userInfo, request)} else {Unauthorized}

    就是這樣。 使用此代碼,您可以輕松地使用HMAC來檢查郵件在傳輸過程中是否已更改,以及您的客戶是否真正為您所知。 如您所見,非常簡單。 只是Play 2.0中有關(guān)JSON使用情況的一小部分便條。 如果您查看操作代碼,則可以看到我使用了標(biāo)準(zhǔn)的JSON功能:

    def addComment() = Authenticated {(user, request) => {// convert the supplied json to a comment objectval comment = Json.parse(request.body.asInstanceOf[String]).as[Comment]// pass the comment object to a service for processingcommentService.storeComment(comment)println(Json.toJson(comment))Status(201)}}

    首先,我們使用'json.parse'將接收到的JSON解析為'comment'類,然后存儲(chǔ)注釋,并將命令對(duì)象轉(zhuǎn)換回字符串值。 不是最有用的代碼,但它很好地演示了Play 2.0提供的一些JSON功能。 為了從JSON轉(zhuǎn)換為對(duì)象并再次返回,使用了一種稱為“隱式轉(zhuǎn)換”的方法。 我不會(huì)在細(xì)節(jié)上過多介紹,但是可以在這里找到很好的解釋: http : //www.codecommit.com/blog/ruby/implicit-conversions-more-powerful-t… 。 這里發(fā)生的是JSON.parse和Json.toJson方法在Comment類上尋找特定的方法。 如果無法在此處找到它,它將在其范圍內(nèi)查找特定的操作。 要查看此方法如何用于JSON解析,讓我們看一下Comment類及其配套對(duì)象:

    import play.api.libs.json.Format import play.api.libs.json.JsValue import play.api.libs.json.JsObject import play.api.libs.json.JsString import play.api.libs.json.JsNumber import play.api.libs.json.JsArrayobject Comment {implicit object CommentFormat extends Format[Comment] {def reads(json: JsValue): Comment = {val root = (json \ "comment")Comment((root \ "message").as[String],(root \ "from").as[String],(root \ "commentFor").as[Long])}def writes(comment: Comment): JsValue = {JsObject(List("comment" ->JsObject(Seq("message" -> JsString(comment.message),"from" -> JsString(comment.message),"commentFor" -> JsNumber(comment.commentFor)))))}}}case class Comment(message: String, from: String, commentFor: Long) {}

    您在此處看到的是,在伴隨對(duì)象中,我們創(chuàng)建了一個(gè)新的“格式”對(duì)象。 現(xiàn)在,與“ Comment”類一起使用時(shí),JSON操作將使用此對(duì)象中的“讀取”和“寫入”操作來進(jìn)行JSON轉(zhuǎn)換。 非常強(qiáng)大的功能,盡管有些神奇;-)有關(guān)在此示例中使用的Scala / Play環(huán)境的更多信息,請(qǐng)參見我以前的文章:
    http://www.smartjava.org/content/play-20-akka-rest-json-and-dependencies
    http://www.smartjava.org/content/using-querulous-scala-postgresql

    參考:來自Smart Java博客的JCG合作伙伴 Jos Dirksen 使用HMAC(Play 2.0)保護(hù)REST服務(wù) 。


    翻譯自: https://www.javacodegeeks.com/2012/04/dzoneprotect-rest-service-using-hmac.html

    總結(jié)

    以上是生活随笔為你收集整理的使用HMAC(Play 2.0)保护REST服务的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。