Skip to content
This repository
Browse code

Rate limiter works now, but needs testing

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

0 notes on commit 556da8e

Please sign in to comment.
Something went wrong with that request. Please try again.