hmac hmac.new

我们有HTTPS,还需要什么?

当您谈论基于REST的API的安全性时,人们通常会指向HTTPS。 借助HTTPS,您可以使用每个人都熟悉的方法轻松保护您的服务免遭窥视。 但是,当您需要更高级别的安全性或HTTPS不可用时,您需要替代方法。 例如,您可能需要跟踪每个客户对API的使用,或者需要确切地知道谁在进行所有这些调用。 您可以将HTTPS与客户端身份验证一起使用,但这将需要设置完整的PKI基础结构以及一种安全的方式来标识您的客户并交换私钥。 与基于SOAP的WS-Security服务相比,我们没有可用于REST的标准。

解决此问题的一种常见方法(Microsoft,Amazon,Google和Yahoo采用这种方法)是通过基于客户端和服务之间的共享机密对消息进行签名。 请注意,使用这种方法,我们仅对数据进行签名,而不对数据进行加密。 在这种情况下,我们所讨论的签名通常称为基于哈希的消息认证代码(简称HMAC)。 使用HMAC,我们基于已交换的密钥为请求创建消息认证代码(MAC)。

在本文中,我将向您展示如何为基于Play 2.0的REST服务实现此算法。 如果您使用其他技术,则步骤将几乎相同。

HMAC方案

对于客户端,我将仅使用基于HTTPClient的简单应用程序。 要实现这一点,我们必须采取以下步骤:

  1. 首先,我们需要与外部客户端交换共享机密。 通常,这是由API提供程序使用电子邮件发送给客户端的,或者提供程序具有您可以在其中查找共享密钥的网站。 请注意,此机密仅在您和服务之间共享,每个客户端将具有不同的共享机密。 这不是像公用密钥那样共享的东西,
  2. 为了确保客户端和服务在相同的内容上计算签名,我们需要对要签名的请求进行规范化。 如果我们不这样做,则服务器可能会以与客户端不同的方式解释空格,并得出签名无效的结论。
  3. 基于此规范化消息,客户端使用共享机密创建HMAC值。
  4. 现在,客户端已准备好将请求发送到服务。 他将HMAC值添加到标头中,还将一些内容标识为用户。 例如,用户名或其他公共值。
  5. 当服务接收到请求时,它将从标头中提取用户名和HMAC值。
  6. 根据用户名,服务知道应该使用哪个共享密钥对消息进行签名。 例如,该服务将从某处的数据存储中检索此信息。
  7. 现在,服务以与客户端相同的方式对请求进行规范化,并为其自身计算HMAC值。
  8. 如果来自客户端的HMAC与从服务器计算出的HMAC相匹配,则您将知道消息的完整性得到保证,并且客户端就是他所说的身份。 如果提供了错误的用户名,或者使用了错误的机密来计算标题,则HMAC值将不匹配。

要实现HMAC,我们需要做什么? 在以下部分中,我们将研究以下主题。

  • 确定用于输入的字段。
  • 创建可以计算此HMAC的客户端代码并添加相应的标头
  • 创建基于Play 2.0的拦截器来检查HMAC标头

确定输入字段

我们要做的第一件事是确定HMAC计算的输入。 下表描述了我们将包括的元素:

领域 描述
HTTP方法 使用REST,我们执行的HTTP方法定义了服务器端的行为。 对特定URL的删除与对该URL的GET处理不同。
内容MD5 这个HTTP标头是标准的HTTP标头。 这是请求正文的MD5哈希。 如果我们将此标头包含在HMAC代码生成中,则会得到一个HMAC值,该值会随着请求正文的更改而更改。
Content-Type标头 Content-Type头是进行REST调用时的重要头。 服务器可以根据媒体类型对请求做出不同的响应,因此应将其包含在HMAC中。
日期标题 我们还包括创建请求以计算HMAC的日期。 在服务器端,我们可以确保日期在传输中没有更改。 除此之外,我们可以在服务器上添加消息过期功能。
路径 由于URI标识REST中的资源,因此调用的URL的路径部分也用于HMAC计算。

我们将包括的几乎是来自请求的以下信息:

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

可用于创建HMAC签名的客户端代码

在下面,您可以看到客户端代码,我们将用它们调用受HMAC保护的服务。 这只是一个基于HTTPClient的快速客户端,我们可以使用它来测试我们的服务。

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算法基于共享机密创建签名。

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();}}

计算完HMAC值后,我们需要将其发送到服务器。 为此,我们提供了一个自定义标头:

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

如您所见,我们还添加了用户名。 服务器需要使用它来确定在服务器端使用哪个密钥来计算HMAC值。 现在,当我们运行此代码时,将执行一个简单的POST操作,将以下请求发送到服务器:

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中实现/播放

到目前为止,我们已经看到客户需要做什么才能为我们提供正确的标题。 服务提供商通常会提供多种语言的特定库,用于处理消息签名的详细信息。 但是,正如您所看到的,手工操作并不困难。 现在,让我们看一下服务器端,在这里我们将scala与Play 2.0框架一起使用,以检查提供的标头是否包含正确的信息。 有关设置正确的Scala环境以测试此代码的更多信息,请参阅我以前在scala上的文章( http://www.smartjava.org/content/play-20-akka-rest-json-and-dependencies )。

首先要做的是设置正确的路由以支持此POST操作。 我们在conf / routes文件中执行此操作

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

这是基本的Play功能。 对/ resource / rest / geo / comment URL的所有POST调用都将传递到指定的控制器。 让我们看一下该操作的样子:

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)}}

现在,它变得更加复杂了。 如您在上面的清单中所见,我们定义了一个addComment操作。 但是,与其像这样直接定义一个Action:

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

我们可以这样定义它:

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

我们在这里要做的是创建一个复合动作http://www.playframework.org/documentation/2.0/ScalaActionsComposition )。 因为Scala是一种功能语言,所以我们可以轻松地做到这一点。 您在此处看到的“已认证”引用只是对简单函数的简单引用,该函数以另一个函数作为参数。 在“已验证”功能中,我们将检查HMAC签名。 您可以将其读为使用批注,但现在无需任何特殊构造。 因此,我们的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))}
}

那是很多代码,但是其中大多数将很容易理解。 “ calculateHMAC”和“ calculateMD5”方法只是围绕Java功能的基本scala包装器。 该类内的文档应该足以了解正在发生的事情。 但是,我确实想在这段代码中突出几个有趣的概念。 首先是方法签名:

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

这意味着Authenticated方法本身将另一个方法(或函数,如果要调用该方法)作为参数。 如果回头看我们的路线目标,您会发现我们只是这样做:

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

现在,当调用此“已认证”方法时会发生什么? 我们要做的第一件事是检查HMAC标头是否存在并且格式正确:

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

我们通过对HMAC标头使用匹配来实现。 如果它包含正确格式的值,我们将以与客户相同的方式处理标头并计算HMAC值。 如果不是,则返回401。如果HMAC值正确,则使用以下代码将其委托给提供的函数:

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

就是这样。 使用此代码,您可以轻松地使用HMAC来检查邮件在传输过程中是否已更改,以及您的客户是否真正为您所知。 如您所见,非常简单。 只是Play 2.0中有关JSON使用的一小部分便条。 如果您查看操作代码,可以看到我使用标准的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'类,然后存储注释,并将命令对象转换回字符串值。 不是最有用的代码,但它很好地演示了Play 2.0提供的一些JSON功能。 为了从JSON转换为对象并再次返回,使用了一种称为“隐式转换”的方法。 我不会在细节上过多介绍,但是可以在这里找到很好的解释: http : //www.codecommit.com/blog/ruby/implicit-conversions-more-powerful-t… 。 这里发生的是JSON.parse和Json.toJson方法在Comment类上寻找特定的方法。 如果找不到它,它将在其范围内查找特定的操作。 要查看此方法如何用于JSON解析,让我们看一下Comment类及其配套对象:

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) {}

您在此处看到的是,在伴随对象中,我们创建了一个新的“格式”对象。 现在,当使用“ Comment”类时,JSON操作将使用此对象中的“读取”和“写入”操作在JSON之间进行转换。 非常强大的功能,尽管有些神奇;-)有关在此示例中使用的Scala / Play环境的更多信息,请参见我以前的文章:
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)保护REST服务 。

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

hmac hmac.new

hmac hmac.new_使用HMAC(Play 2.0)保护REST服务相关推荐

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

    我们有HTTPS,还需要什么? 当您谈论基于REST的API的安全性时,人们通常会指向HTTPS. 借助HTTPS,您可以使用每个人都熟悉的方法轻松保护您的服务免遭窥视. 但是,当您需要更高级别的安全 ...

  2. WSE3.0构建Web服务安全(3):WSE3.0策略配置、证书、签名、与实例开发

    继WSE3.0构建Web服务安全(1):WSE3.0安全机制与实例开发和WSE3.0构建Web服务安全(2):非对称加密.公钥.密钥.证书.签名的区别和联系以及X.509 证书的获得和管理之后,今天我 ...

  3. WSE3.0构建Web服务安全(4):MTOM消息传输优化和文件上传、下载

    MTOM消息优化传输机制主要应用于大量数据的传输,很多文章中也直接得出结论:使用MTOM文件传输效率高.为什么MTOM的数据传输效率会比别的方式要高?MTOM真的如此完美吗,它有什么不足?什么情况下使 ...

  4. android 8.0手机开服务端,Android 8.0 + Service开启方式兼容处理

    Android 8.0 + ,对后台服务进行了限制了.如果依然采用之前startService()方式. 会导致问题. 前后台服务的一些区别: 有人可能会问,后台服务我们可以自己创建 ONGOING ...

  5. 使用HTTPS和OAuth 2.0保护服务到服务的Spring微服务

    "我喜欢编写身份验证和授权代码." 〜从来没有Java开发人员. 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证. 如果您使 ...

  6. 使用Spring Security和OAuth 2.0保护Spring微服务架构

    "我喜欢编写身份验证和授权代码." 〜从来没有Java开发人员. 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证. 每个开发 ...

  7. mysql安装最后两项无响应,win10下安装mysql8.0.23 及 “服务没有响应控制功能”问题解决办法...

    win10下安装mysql 1. 官网下载mysql 网址:https://www.mysql.com/downloads/ download页面往下拉:点击下图红框: 按下图指示操作: 下载之后解压 ...

  8. WCF创建Rest服务(附:.net2.0创建Rest服务)

    转(详见):http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/1bad2198-65cb-445d-84cb-1fae17828eef ...

  9. 鸿蒙系统照样没有谷歌服务,曝华为Mate40 Pro+升级鸿蒙2.0系统 谷歌服务照样能用...

    原标题:曝华为Mate40 Pro+升级鸿蒙2.0系统 谷歌服务照样能用 近日已经有不少网友反映,华为鸿蒙OS2.0开发者Beta公测版已经开始推送了,已经有不少人报名参加,并收到了华为的推送,系统从 ...

最新文章

  1. 【转】每天一个linux命令(34):du 命令
  2. linux脚本命令顺序执行命令,linux – nohup多个顺序命令
  3. ListView优化的代码
  4. 使用Spring Boot自动发布和监视API
  5. Linux文件查看/编辑方法介绍
  6. 安全开发流程(SDL、微软)
  7. leetcode886.PossibleBipartition
  8. 微信html页面缓存问题,浅谈微信页面入口文件被缓存解决方案
  9. Android 学习笔记(6)之Lambda和注解
  10. 520,让爱的传递永不停歇
  11. 在vue中_this和this的区别
  12. 如何反编译 cocos creator 生成 的jsc文件/反编译jsc文件(一)
  13. NIO的网络通信demo
  14. elementui tree控制节点展开与否
  15. python爬取qq好友_Python3实现QQ机器人自动爬取百度文库的搜索结果并发送给好友(主要是爬虫)...
  16. Excel-Python对数据进行线性回归
  17. TI DaVinci(达芬奇)入门
  18. 笔记本自带的英特尔驱动没了_每日新闻摘要:英特尔希望笔记本电脑知道您就在附近...
  19. js中各种数据类型检测与判定
  20. ENVI教程:InSAR技术,干涉图生成

热门文章

  1. 全国教学交流研讨会“教学为本”主题总结
  2. 《四世同堂》金句摘抄(十三)
  3. 2017最新顺口溜出炉(超级经典)!
  4. jsp连接mysql数据库 例子_JSP 连接 MySQL 数据库的例子
  5. dplayer js控制 自动全屏_vue-video-player 通过自定义按钮组件实现全屏切换效果【推荐】...
  6. python cmd闪退_使用cmd python模块时,如何使程序正常崩溃?
  7. sql server 2008安装_性能不够?基于时序数据库的Zabbix 5.2安装指南
  8. jenkins安装与配置windows_Windows下Scoop安装、配置与使用
  9. 抽取大小: 高斯sigma_无服务器:SLAppForge Sigma入门
  10. aws lambda使用_使用Lambda,Api Gateway和CloudFormation在AWS云上使用Java