Permalink
Browse files

Rate limiter works now, but needs testing

  • Loading branch information...
1 parent 13c9aed commit 556da8e153547ef3642c1eedc844d2bb0714e570 @williewheeler committed May 20, 2012
View
@@ -37,9 +37,6 @@ takes is two easy steps.
</beans:beans>
```
-[**NOTE:** The rate-limiter doesn't actually perform any rate-limiting yet. It just does a pass-through. I'll fix this
-when I get the chance.]
-
**Step 2.** Second, you'll need to annotate the service methods. I'm assuming a transactional service here, though that's not
required:
@@ -88,8 +85,8 @@ exception types.
**Concurrency throttle:** A fail-fast concurrency throttle that rejects requests once a configurable concurrency limit
is reached. Eventually throttles will be able to reject requests based on failure to meet SLAs.
-**Rate-limiting throttle:** A throttle that rejects requests after the client reaches a configurable limit on the
-number of requests in some time period.
+**Rate-limiting throttle:** A throttle that rejects requests after the principal reaches a configurable limit on the
+number of requests in some time period. The rate limiter uses Spring Security to determine the principal involved.
I very much welcome contributions. It's pretty easy to add a new guard; just look at `org.zkybase.kite.guard` to see how
to do it.
View
@@ -28,15 +28,15 @@
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
- <!--
<dependency>
<groupId>org.springframework</groupId>
- <artifactId>spring-context-support</artifactId>
+ <artifactId>spring-core</artifactId>
</dependency>
- -->
+
+ <!-- Spring Security -->
<dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-core</artifactId>
+ <groupId>org.springframework.security</groupId>
+ <artifactId>spring-security-core</artifactId>
</dependency>
<!-- Other -->
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.zkybase.kite.exception;
+
+/**
+ * @author Willie Wheeler (willie.wheeler@gmail.com)
+ * @since 1.0
+ */
+@SuppressWarnings("serial")
+public class UnauthenticatedException extends GuardException {
+
+ public UnauthenticatedException() {
+ super("Authentication required");
+ }
+}
@@ -1,22 +1,42 @@
package org.zkybase.kite.guard;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jmx.export.annotation.ManagedResource;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
import org.zkybase.kite.AbstractGuard;
import org.zkybase.kite.GuardCallback;
import org.zkybase.kite.exception.RateLimitExceededException;
+import org.zkybase.kite.exception.UnauthenticatedException;
/**
+ * <p>
+ * A rate-limiting throttle. Currently this guard rate-limits on an hourly basis. A future version of this guard will
+ * allow a range of options with respect to the time window.
+ * </p>
+ * <p>
+ * Note that all counts reset on the hour, as measured by the wall clock.
+ * </p>
+ *
* @author Willie Wheeler (willie.wheeler@gmail.com)
* @since 1.0
*/
@ManagedResource
public class RateLimitingThrottleTemplate extends AbstractGuard {
+ private static final int MILLIS_PER_HOUR = 1000 * 60 * 60;
private static Logger log = LoggerFactory.getLogger(RateLimitingThrottleTemplate.class);
private final int limit;
+ private volatile int currentHour;
+ private final Map<Object, Integer> counts = new ConcurrentHashMap<Object, Integer>();
+
/**
* @param limit maximum number of requests permitted in the time window
*/
@@ -25,21 +45,50 @@ public RateLimitingThrottleTemplate(int limit) {
throw new IllegalArgumentException("limit must be >= 1");
}
this.limit = limit;
+ this.currentHour = (int) (System.currentTimeMillis() / MILLIS_PER_HOUR);
}
public int getLimit() { return limit; }
public <T> T execute(GuardCallback<T> action) throws Throwable {
- if (withinLimit()) {
+ resetCountsOnTheHour();
+ Object principal = getPrincipal();
+ int count = getCount(principal);
+
+ if (++count <= limit) {
+ log.debug("principal={}, count={}", principal, count);
+ counts.put(principal, count);
return action.doInGuard();
} else {
log.warn("Request rejected: rate limit {} exceeded", limit);
throw new RateLimitExceededException(limit);
}
}
- private boolean withinLimit() {
- // FIXME
- return true;
+ private void resetCountsOnTheHour() {
+ int newHour = (int) (System.currentTimeMillis() / MILLIS_PER_HOUR);
+ if (newHour > currentHour) {
+ this.currentHour = newHour;
+ counts.clear();
+ }
+ }
+
+ private Object getPrincipal() {
+ SecurityContext context = SecurityContextHolder.getContext();
+ Authentication auth = context.getAuthentication();
+
+ // FIXME There's probably a better way to detect anonymous auth.
+ if (auth == null || auth instanceof AnonymousAuthenticationToken) {
+ log.debug("Authentication required");
+ throw new UnauthenticatedException();
+ }
+
+ return auth.getPrincipal();
+ }
+
+ private int getCount(Object principal) {
+ Integer count = counts.get(principal);
+ if (count == null) { count = 0; }
+ return count;
}
}
View
@@ -20,6 +20,7 @@
<properties>
<slf4j.version>1.6.4</slf4j.version>
<spring.version>3.1.1.RELEASE</spring.version>
+ <spring.security.version>3.1.0.RELEASE</spring.security.version>
</properties>
<dependencyManagement>
@@ -38,13 +39,22 @@
</dependency>
<dependency>
<groupId>org.springframework</groupId>
- <artifactId>spring-context-support</artifactId>
+ <artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
+
+ <!-- Spring Security -->
<dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-core</artifactId>
- <version>${spring.version}</version>
+ <groupId>org.springframework.security</groupId>
+ <artifactId>spring-security-config</artifactId>
+ <version>${spring.security.version}</version>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.security</groupId>
+ <artifactId>spring-security-core</artifactId>
+ <version>${spring.security.version}</version>
+ <optional>true</optional>
</dependency>
<!-- Other -->
View
@@ -48,15 +48,19 @@
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
- <!--
<dependency>
<groupId>org.springframework</groupId>
- <artifactId>spring-context-support</artifactId>
+ <artifactId>spring-core</artifactId>
</dependency>
- -->
+
+ <!-- Spring Security -->
<dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-core</artifactId>
+ <groupId>org.springframework.security</groupId>
+ <artifactId>spring-security-config</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.security</groupId>
+ <artifactId>spring-security-core</artifactId>
</dependency>
<!-- Spring, local -->
@@ -75,6 +79,16 @@
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.springframework.security</groupId>
+ <artifactId>spring-security-web</artifactId>
+ <version>${spring.security.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.security</groupId>
+ <artifactId>spring-security-taglibs</artifactId>
+ <version>${spring.security.version}</version>
+ </dependency>
<!-- Other -->
<dependency>
@@ -14,7 +14,7 @@
<annotation-config order="0" />
<!-- Common -->
- <rate-limiting-throttle id="rateLimitingThrottle" limit="5000" />
+ <rate-limiting-throttle id="rateLimitingThrottle" limit="200" />
<!-- Message service -->
<circuit-breaker id="messageServiceBreaker" exceptionThreshold="4" timeout="30000" />
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/security"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="
+ http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
+ http://www.springframework.org/schema/security
+ http://www.springframework.org/schema/security/spring-security-3.1.xsd">
+
+ <http auto-config="true" use-expressions="true" >
+ <intercept-url pattern="/login" access="isAuthenticated()" />
+ <intercept-url pattern="/**" access="permitAll" />
+ </http>
+
+ <authentication-manager>
+ <authentication-provider>
+ <user-service>
+ <user name="willie" password="willie" authorities="ROLE_USER" />
+ <user name="ray" password="ray" authorities="ROLE_USER, ROLE_BOSS" />
+ </user-service>
+ </authentication-provider>
+ </authentication-manager>
+</beans:beans>
@@ -12,6 +12,8 @@
<mvc:annotation-driven />
<mvc:default-servlet-handler />
+ <mvc:view-controller path="/login" view-name="redirect:/" />
+
<context:component-scan base-package="org.zkybase.kite.samples.web" />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
@@ -1,6 +1,10 @@
<!DOCTYPE html>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
+
+<c:url var="loginUrl" value="/login" />
+<c:url var="logoutUrl" value="/j_spring_security_logout" />
<html lang="en">
<head>
@@ -11,6 +15,13 @@
<h1>Aggro's Towne BBS</h1>
</header>
+ <security:authorize access="isAnonymous()">
+ <p><a href="${loginUrl}">[Login]</a> (use either willie/willie or ray/ray)</p>
+ </security:authorize>
+ <security:authorize access="isAuthenticated()">
+ <p><a href="${logoutUrl}">[Logout]</a></p>
+ </security:authorize>
+
<section>
<header>
<h2>Message of the day</h2>
@@ -27,13 +27,25 @@ limitations under the License.
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
- classpath:/spring/beans-service.xml
classpath:/spring/beans-kite.xml
+ classpath:/spring/beans-security.xml
+ classpath:/spring/beans-service.xml
</param-value>
</context-param>
+
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
+
+ <filter>
+ <filter-name>springSecurityFilterChain</filter-name>
+ <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
+ </filter>
+ <filter-mapping>
+ <filter-name>springSecurityFilterChain</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

0 comments on commit 556da8e

Please sign in to comment.