{"action":"created","issue":{"url":"https://api.github.com/repos/jhipster/generator-jhipster/issues/4595","repository_url":"https://api.github.com/repos/jhipster/generator-jhipster","labels_url":"https://api.github.com/repos/jhipster/generator-jhipster/issues/4595/labels{/name}","comments_url":"https://api.github.com/repos/jhipster/generator-jhipster/issues/4595/comments","events_url":"https://api.github.com/repos/jhipster/generator-jhipster/issues/4595/events","html_url":"https://github.com/jhipster/generator-jhipster/issues/4595","id":192967199,"number":4595,"title":"[FEATURE] API Rate Limits","user":{"login":"delverdev","id":7104443,"avatar_url":"https://avatars.githubusercontent.com/u/7104443?v=3","gravatar_id":"","url":"https://api.github.com/users/delverdev","html_url":"https://github.com/delverdev","followers_url":"https://api.github.com/users/delverdev/followers","following_url":"https://api.github.com/users/delverdev/following{/other_user}","gists_url":"https://api.github.com/users/delverdev/gists{/gist_id}","starred_url":"https://api.github.com/users/delverdev/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/delverdev/subscriptions","organizations_url":"https://api.github.com/users/delverdev/orgs","repos_url":"https://api.github.com/users/delverdev/repos","events_url":"https://api.github.com/users/delverdev/events{/privacy}","received_events_url":"https://api.github.com/users/delverdev/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":3,"created_at":"2016-12-01T21:28:34Z","updated_at":"2016-12-01T21:39:03Z","closed_at":null,"body":"<!--\r\n> Please follow the issue template below for bug reports and queries.\r\n> Bug reports or queries opened without any of these info will be **closed** without any explanation.\r\n> For feature requests, label the title with [FEATURE] and explain your use case and ideas clearly below, you can remove sections which are not relevant.\r\n> For bug reports your `.yo-rc.json` file is mandatory else we will close the issue.\r\n> You can run `yo jhipster:info` in your project folder to get most of the required info\r\n-->\r\n\r\n##### **Overview of the issue**\r\n\r\nFor security reasons, it would be great if rate limiting would exist. Rate limiting reduces request that user can send in certain amount of time (for example, 1 request per second).\r\n\r\n##### **Motivation for or Use Case**\r\n\r\nPartial protection from the requests flood.\r\n\r\n##### **Suggest a Fix**\r\n\r\n<!-- if you can't fix the bug yourself, perhaps you can point to what might be\r\n  causing the problem (line of code or commit) -->\r\n\r\nAt the moment I use the following code:\r\n\r\n`RateLimit`\r\n\r\n```\r\n@Retention(RetentionPolicy.RUNTIME)\r\n@Target(ElementType.METHOD)\r\npublic @interface RateLimit {\r\n\r\n    //amount of requests that user can send in duration time\r\n    int limit() default 1;\r\n\r\n    long duration() default 1;\r\n\r\n    TimeUnit unit() default TimeUnit.SECONDS;\r\n\r\n}\r\n```\r\n\r\nby default, user could send 1 request per second.\r\n\r\n`RateLimitAspect`\r\n\r\n```\r\n\r\n@Slf4j\r\n@Aspect\r\n@Component\r\npublic class RateLimitAspect {\r\n\r\n    private Map<String, RateLimiter> rateLimiters = new HashMap<>();\r\n\r\n    @Pointcut(\"@annotation(rateLimit)\")\r\n    private void annotatedWithRateLimit(RateLimit rateLimit) {\r\n    }\r\n\r\n    @Pointcut(\"@within(org.springframework.stereotype.Controller) || @within(org.springframework.web.bind.annotation.RestController)\")\r\n    private void controllerMethods() {\r\n    }\r\n\r\n    @Before(value = \"controllerMethods() && annotatedWithRateLimit(rateLimit)\", argNames = \"joinPoint,rateLimit\")\r\n    public void rateLimitProcess(final JoinPoint joinPoint, RateLimit rateLimit) throws RateLimitException {\r\n        HttpServletRequest request = null;\r\n        for (Object arg : joinPoint.getArgs())\r\n            if (arg instanceof HttpServletRequest) {\r\n                request = (HttpServletRequest) arg;\r\n                break;\r\n            }\r\n        if (request == null) {\r\n            log.error(\"Method, annotated @RateLimit should has HttpServletRequest parameter.\");\r\n            return;\r\n        }\r\n        String ipAddress = Util.getIpAddress(request);\r\n        String uri = request.getRequestURI();\r\n        String key = ipAddress + \",\" + uri;\r\n        rateLimiters.putIfAbsent(key, RateLimiter.create(rateLimit.limit(), rateLimit.duration(), rateLimit.unit()));\r\n        RateLimiter rateLimiter = rateLimiters.get(key);\r\n        if (rateLimiter != null && !rateLimiter.tryAcquire()) {\r\n            log.warn(\"User with IP {} has exceeded the limit of requests. URI: {}\", ipAddress, uri);\r\n            throw new RateLimitException();\r\n        }\r\n    }\r\n\r\n    @Scheduled(cron = \"0 0/1 * * * ?\")\r\n    protected void clearRateLimiters() {\r\n        rateLimiters = new HashMap<>();\r\n    }\r\n\r\n}\r\n\r\n```\r\n\r\nThis aspect uses `google-common`. I didn't find better `RateLimiter`.\r\n\r\n`Util::getIpAddress`\r\n\r\n```\r\npublic static String getIpAddress(HttpServletRequest request) {\r\n    String ip = request.getRemoteAddr();\r\n    String[] ipHeaders = new String[] {\r\n        \"X-Real-IP\", \"X-Forwarded-For\", \"Proxy-Client_IP\",\r\n        \"WL-Proxy-Client-IP\", \"HTTP_CLIENT_IP\", \"HTTP_X_FORWARDED_FOR\"\r\n    };\r\n    for (String ipHeader : ipHeaders)\r\n        if (ip != null && ip.length() > 0 && \"unknown\".equalsIgnoreCase(ip)) {\r\n            ip = ipHeader;\r\n            break;\r\n        }\r\n    return ip;\r\n}\r\n```\r\n`RateLimitException`\r\n\r\n```\r\n@ResponseStatus(value = HttpStatus.TOO_MANY_REQUESTS)\r\nclass RateLimitException extends RuntimeException {\r\n\r\n    private static final long serialVersionUID = -5649100415539733155L;\r\n\r\n    RateLimitException() {\r\n        super(\"Rate limit exceeded.\");\r\n    }\r\n\r\n}\r\n```\r\n\r\nNow we can annotate any REST mapping method by `RateLimit`. If rate limit exceeded, `429 (Too many requests)` will returned. The only limitation is that annotated method must have `HttpServletRequest` parameter.\r\n"},"comment":{"url":"https://api.github.com/repos/jhipster/generator-jhipster/issues/comments/264303463","html_url":"https://github.com/jhipster/generator-jhipster/issues/4595#issuecomment-264303463","issue_url":"https://api.github.com/repos/jhipster/generator-jhipster/issues/4595","id":264303463,"user":{"login":"jdubois","id":316835,"avatar_url":"https://avatars.githubusercontent.com/u/316835?v=3","gravatar_id":"","url":"https://api.github.com/users/jdubois","html_url":"https://github.com/jdubois","followers_url":"https://api.github.com/users/jdubois/followers","following_url":"https://api.github.com/users/jdubois/following{/other_user}","gists_url":"https://api.github.com/users/jdubois/gists{/gist_id}","starred_url":"https://api.github.com/users/jdubois/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jdubois/subscriptions","organizations_url":"https://api.github.com/users/jdubois/orgs","repos_url":"https://api.github.com/users/jdubois/repos","events_url":"https://api.github.com/users/jdubois/events{/privacy}","received_events_url":"https://api.github.com/users/jdubois/received_events","type":"User","site_admin":false},"created_at":"2016-12-01T21:39:03Z","updated_at":"2016-12-01T21:39:03Z","body":"Yes, but it already exists on the gateway, with a Cassandra back-end. I coded it :-)"}}
{"actor":{"display_login":"jdubois"}}