{"id":13695632,"url":"https://github.com/sief/play-guard","last_synced_at":"2025-05-03T13:32:53.136Z","repository":{"id":24818883,"uuid":"28233308","full_name":"sief/play-guard","owner":"sief","description":"Play2 module for rate limiting, based on token bucket algorithm","archived":false,"fork":false,"pushed_at":"2023-12-18T08:07:31.000Z","size":220,"stargazers_count":131,"open_issues_count":0,"forks_count":20,"subscribers_count":10,"default_branch":"master","last_synced_at":"2024-08-03T18:19:51.072Z","etag":null,"topics":["failure-rate","play-framework","rate-limiting","scala","security"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sief.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2014-12-19T14:51:12.000Z","updated_at":"2024-04-19T13:09:17.000Z","dependencies_parsed_at":"2023-12-18T09:28:41.005Z","dependency_job_id":"c652fcfe-b647-4264-a432-c1a63032e3a9","html_url":"https://github.com/sief/play-guard","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sief%2Fplay-guard","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sief%2Fplay-guard/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sief%2Fplay-guard/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sief%2Fplay-guard/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sief","download_url":"https://codeload.github.com/sief/play-guard/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224364355,"owners_count":17299055,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["failure-rate","play-framework","rate-limiting","scala","security"],"created_at":"2024-08-02T18:00:31.432Z","updated_at":"2024-11-12T23:30:44.936Z","avatar_url":"https://github.com/sief.png","language":"Scala","funding_links":[],"categories":["容错组件","Libraries"],"sub_categories":["Spring Cloud框架","Security/Authentification"],"readme":"# Play Framework Guard Module\n\n[![Maven](https://img.shields.io/maven-central/v/com.digitaltangible/play-guard_2.10.svg?label=latest%20release%20for%20Scala%202.10)](http://mvnrepository.com/artifact/com.digitaltangible/play-guard_2.10)\n[![Maven](https://img.shields.io/maven-central/v/com.digitaltangible/play-guard_2.11.svg?label=latest%20release%20for%20Scala%202.11)](http://mvnrepository.com/artifact/com.digitaltangible/play-guard_2.11)\n[![Maven](https://img.shields.io/maven-central/v/com.digitaltangible/play-guard_2.12.svg?label=latest%20release%20for%20Scala%202.12)](http://mvnrepository.com/artifact/com.digitaltangible/play-guard_2.12)\n[![Maven](https://img.shields.io/maven-central/v/com.digitaltangible/play-guard_2.13.svg?label=latest%20release%20for%20Scala%202.13)](http://mvnrepository.com/artifact/com.digitaltangible/play-guard_2.13)\n[![Maven](https://img.shields.io/maven-central/v/com.digitaltangible/play-guard_3.svg?label=latest%20release%20for%20Scala%203)](http://mvnrepository.com/artifact/com.digitaltangible/play-guard_3)\n\n\nPlay module for blocking and throttling abusive requests.\n\n- throttling specific Actions based on request attributes (e.g. IP address)\n- throttling specific Actions based on request attributes (e.g. IP address) and failure rate (e.g. HTTP status or any other Result attribute)\n\n- global IP address whitelisting/blacklisting\n- global request throttling\n\n## Target\n\nThis module targets the __Scala__ version of __Play 2.x.x__ and __3.x.x__\n\n## Rate Limit Algorithm\n\nBased on the token bucket algorithm: http://en.wikipedia.org/wiki/Token_bucket\n\n\n## Getting play-guard\n\nFor Play 3.0.x:\n```scala\n  \"com.digitaltangible\" %% \"play-guard\" % \"3.0.0\"\n```\n\nFor Play 2.9.x:\n```scala\n  \"com.digitaltangible\" %% \"play-guard\" % \"2.6.0\"\n```\n\nFor Play 2.8.x:\n```scala\n  \"com.digitaltangible\" %% \"play-guard\" % \"2.5.0\"\n```\n\nFor Play 2.7.x:\n```scala\n  \"com.digitaltangible\" %% \"play-guard\" % \"2.4.0\"\n```\n\n\nFor Play 2.6.x:\n```scala\n  \"com.digitaltangible\" %% \"play-guard\" % \"2.2.0\"\n```\n\n\nFor Play 2.5.x:\n```scala\n  \"com.digitaltangible\" %% \"play-guard\" % \"2.0.0\"\n```\n\n\nFor Play 2.4.x:\n```scala\n  \"com.digitaltangible\" %% \"play-guard\" % \"1.6.0\"\n```\n\n\nFor Play 2.3.x:\n```scala\n  \"com.digitaltangible\" %% \"play-guard\" % \"1.4.1\"\n```\n\n\n\n\n\n# 1. RateLimitAction\n\nAction function/filter for request and failure rate limiting specific actions. You can derive the bucket key from the request.\n\nThe rate limit functions/filters all take a RateLimiter instance as the first parameter:\n\n```scala\nclass RateLimiter(val size: Long, val rate: Double, logPrefix: String = \"\", clock: Clock = CurrentTimeClock)\n```\n\nIt holds the token bucket group with the specified size and rate and can be shared between actions if you want to use the same bucket group for various actions.\n\n\n\n1.1 Request rate limit\n-------\n\nThere is a general ActionFilter for handling any type of request so you can chain it behind you own ActionTransformer:\n\n```scala\n/**\n * ActionFilter which holds a RateLimiter with a bucket for each key returned by `keyFromRequest`.\n * Can be used with any Request type. Useful if you want to use content from a wrapped request, e.g. User ID\n *\n * @param rateLimiter\n * @tparam R\n */\nabstract class RateLimitActionFilter[R[_] \u003c: Request[_]](rateLimiter: RateLimiter)(\n  implicit val executionContext: ExecutionContext\n) extends ActionFilter[R] {\n\n  def keyFromRequest[A](implicit request: R[A]): Any\n\n  def rejectResponse[A](implicit request: R[A]): Future[Result]\n\n  def bypass[A](implicit request: R[A]): Boolean = false\n  \n  // ...\n}\n```\n\nThere are also two convenience filters:\n\n__IP address as key__ (from the sample app):\n\n```scala\n// allow 3 requests immediately and get a new token every 5 seconds\nprivate val ipRateLimitFilter: IpRateLimitFilter[Request] = new IpRateLimitFilter[Request](new RateLimiter(3, 1f / 5, \"test limit by IP address\")) {\n  override def rejectResponse[A](implicit request: Request[A]): Future[Result] =\n    Future.successful(TooManyRequests(s\"\"\"rate limit for ${request.remoteAddress} exceeded\"\"\"))\n}\n\ndef limitedByIp: Action[AnyContent] = (Action andThen ipRateLimitFilter) {\n  Ok(\"limited by IP\")\n}\n```\n\n__Action parameter as key__ (from the sample app):\n\n```scala\n// allow 4 requests immediately and get a new token every 15 seconds\nprivate val keyRateLimitFilter: KeyRateLimitFilter[String, Request] =\n  new KeyRateLimitFilter[String, Request](new RateLimiter(4, 1f / 15, \"test by token\")) {\n    override def rejectResponse4Key[A](key: String): Request[A] =\u003e Future[Result] =\n      _ =\u003e Future.successful(TooManyRequests(s\"\"\"rate limit for '$key' exceeded\"\"\"))\n  }\n\ndef limitedByKey(key: String): Action[AnyContent] =\n  (Action andThen keyRateLimitFilter(key)) {\n    Ok(\"limited by token\")\n  }\n```\n\n1.2 Error rate limit\n-------\n\nThere is a general ActionFunction for handling any type of request so you can chain it behind your own ActionTransformer and determine failure from the Result:\n\n```scala\n/**\n * ActionFunction which holds a RateLimiter with a bucket for each key returned by method keyFromRequest.\n * Tokens are consumed only by failures determined by function resultCheck. If no tokens remain, requests with this key are rejected.\n * Can be used with any Request type. Useful if you want to use content from a wrapped request, e.g. User ID\n *\n * @param rateLimiter\n * @param resultCheck\n * @param executionContext\n * @tparam R\n */\nabstract class FailureRateLimitFunction[R[_] \u003c: Request[_]](\n     rateLimiter: RateLimiter,\n     resultCheck: Result =\u003e Boolean,\n)(implicit val executionContext: ExecutionContext)\n  extends ActionFunction[R, R] {\n\n  def keyFromRequest[A](implicit request: R[A]): Any\n\n  def rejectResponse[A](implicit request: R[A]): Future[Result]\n\n  def bypass[A](implicit request: R[A]): Boolean = false\n  \n  // ...\n}\n```\n\nThe convenience action HttpErrorRateLimitAction __limits the HTTP error rate for each IP address__. This is for example useful if you want to prevent brute force bot attacks on authentication requests.\n\nFrom the sample app:\n\n```scala\n// allow 2 failures immediately and get a new token every 10 seconds\nprivate val httpErrorRateLimitFunction: HttpErrorRateLimitFunction[Request] =\nnew HttpErrorRateLimitFunction[Request](new RateLimiter(2, 1f / 10, \"test failure rate limit\")) {\n  override def rejectResponse[A](implicit request: Request[A]): Future[Result] = Future.successful(BadRequest(\"failure rate exceeded\"))\n}\n\ndef failureLimitedByIp(fail: Boolean): Action[AnyContent] =\n(Action andThen httpErrorRateLimitFunction) {\n  if (fail) BadRequest(\"failed\")\n  else Ok(\"Ok\")\n}\n```\n\n1.3 Integration with Silhouette\n-------\n\nhttps://www.silhouette.rocks/docs/rate-limiting\n\n\n\n\n\n# 2. GuardFilter\n\n\nFilter for global rate limiting and IP address whitelisting/blacklisting.\n\n__Note: this global filter is only useful if you don't have access to a reverse proxy like nginx where you can handle these kind of things__\n\n2.1 Rules\n----------\nRejects requests based on the following rules:\n\n```\nif IP address is in whitelist =\u003e let pass\nelse if IP address is in blacklist =\u003e reject with ‘403 FORBIDDEN’\nelse if IP address rate limit exceeded =\u003e reject with ‘429 TOO_MANY_REQUEST’\nelse if global rate limit exceeded =\u003e reject with ‘429 TOO_MANY_REQUEST’\n```\n\n2.2 Usage\n----------\n\nFor compile time DI:\n\n```scala\nclass ApplicationComponents(context: Context) extends BuiltInComponentsFromContext(context) with PlayGuardComponents {\n\n  override lazy val httpFilters: Seq[EssentialFilter] = Seq(guardFilter)\n}\n```\n\nRuntime DI with Guice:\n\n```scala\n@Singleton\nclass Filters @Inject()(env: Environment, guardFilter: GuardFilter) extends DefaultHttpFilters(guardFilter)\n```\n\n\nThe filter uses the black/whitelists from the configuration by default. You can also plug in you own IpChecker implementation. With runtime time DI you have to disable the default module in your application.conf and bind your implementation in your app's module:\n\n ```\n play {\n   modules {\n     disabled += \"com.digitaltangible.playguard.PlayGuardIpCheckerModule\"\n   }\n }\n ```\n\n\n2.2 Configuration\n----------\n\n\n```\nplayguard {\n  filter {\n    enabled = true\n    global {\n      bucket {\n        size = 100\n        rate = 100\n      }\n    }\n    ip {\n      whitelist = [\"1.1.1.1\", \"2.2.2.2\"]\n      blacklist = [\"3.3.3.3\", \"4.4.4.4\"]\n      bucket {\n        size = 50\n        rate = 50\n      }\n    }\n  }\n}\n```\n\n# 3. Configuring the remote IP address\n\nIP-based rate limits will use `RequestHeader.remoteAddress` as the address of the client. Depending on [how you have configured Play](https://www.playframework.com/documentation/2.6.x/HTTPServer#Configuring-trusted-proxies) this may be the actual remote address of clients connecting directly, or it may be read from the common `X-Forwarded-For` or `Forwarded` headers that are set by proxies and load balancers.\n\nIf you are using a reverse proxy (e.g. [nginx](https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/), HAProxy or an [AWS ELB](https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/x-forwarded-headers.html)) in front of your application, you should take care to configure the [`play.http.forwarded.trustedProxies`](https://www.playframework.com/documentation/2.6.x/HTTPServer#Configuring-trusted-proxies) setting, otherwise all requests will be rate-limited against the IP address of the upstream proxy (definitely not what you want).\n\nFor scenarios where you don't know your immediate connection's IP address beforehand (to configure it as a trusted proxy) but can still trust it, e.g. on Heroku, there is a custom RequestHandler `XForwardedTrustImmediateConnectionRequestHandler` which replaces the immediate connection with the last IP address in the X-Forwarded-For header (RFC 7239 is not supported). This handler can be configured as described [here](https://www.playframework.com/documentation/2.6.x/ScalaHttpRequestHandlers#Implementing-a-custom-request-handler) \n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsief%2Fplay-guard","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsief%2Fplay-guard","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsief%2Fplay-guard/lists"}