Rate limiting af API-adgang
Beskyt Deres autentificeringsendpoints og API'er mod brute force- og denial-of-service-angreb ved hjælp af rate limiting.
Rate limiting af API-adgang er en gratis Spring Security 6 og JWT-godkendelse-lektion på CoddyKit. Dette er lektion 2 af 4. Du kan læse alle 3 lektioner i dette læringsspor gratis i deres fulde længde — derefter låser CoddyKit PRO alle lektioner op samt praktiske øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Den er en del af læringsforløbet i Spring Security 6 og JWT-godkendelse, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Spring Security 6 og JWT-godkendelse-kurset indeholder 4 lektioner i alt.
Hvad er hastighedsbegrænsning?
Hastighedsbegrænsning er en vigtig sikkerhedsforanstaltning, der styrer, hvor ofte en bruger eller et system kan gentage en handling inden for et bestemt tidsrum.
Tænk på det som en dørmand på en klub, der kun lukker et bestemt antal personer ind pr. minut for at forhindre overfyldning.
Det er afgørende for at beskytte dine API'er og godkendelsesendepunkter mod ondsindede aktiviteter.
Hvorfor API'er har brug for hastighedsbegrænsning
Uden hastighedsbegrænsning er din applikation sårbar over for flere angreb:
- Brute force-angreb: Gentagne loginforsøg for at gætte adgangskoder.
- Denial-of-Service (DoS): Overbelastning af din server med forespørgsler, så den bliver utilgængelig.
- Udtømning af ressourcer: Legitime brugere kan blive ramt, hvis én bruger forbruger for mange ressourcer.
Hastighedsbegrænsning hjælper med at opretholde stabilitet og sikkerhed.
Strategier til hastighedsbegrænsning
Der er flere måder at implementere hastighedsbegrænsning på:
- Fast vindue: Tillader N forespørgsler pr. vindue (f.eks. 100 forespørgsler pr. minut). Det er enkelt, men kan give et "burst"-problem ved vinduets grænser.
- Glidende vindue: En mere avanceret tilgang, der udjævner trafikken og forhindrer bursts.
- Token Bucket/Leaky Bucket: Mere komplekse metoder, der ofte bruges til trafikformning.
I denne lektion fokuserer vi på en enkel tilgang med et fast vindue.
Implementering i Spring Boot
I Spring Boot kan vi implementere hastighedsbegrænsning ved hjælp af et brugerdefineret Servlet-filter.
Et filter opfanger indgående HTTP-forespørgsler, før de når dine controllermetoder. Det giver os mulighed for at kontrollere forespørgslens hastighedsgrænse og blokere den, hvis det er nødvendigt.
Vi opretter en dedikeret tjeneste til at håndtere logikken for hastighedsbegrænsning.
Grundlæggende tjeneste til hastighedsbegrænsning
Lad os først oprette en enkel tjeneste til at holde styr på antallet af forespørgsler. Dette eksempel bruger et map i hukommelsen til demonstrationen.
Prøv at køre denne grundlæggende tjenesteopsætning:
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class RateLimiterService {
private final int MAX_REQUESTS;
private final long TIME_WINDOW_MILLIS; // e.g., 1 minute = 60000ms
// Stores request counts per client (e.g., IP address) within a window
private final ConcurrentHashMap<String, RequestInfo> requestCounts = new ConcurrentHashMap<>();
public RateLimiterService(int maxRequests, long timeWindowMillis) {
this.MAX_REQUESTS = maxRequests;
this.TIME_WINDOW_MILLIS = timeWindowMillis;
}
public boolean tryAcquire(String clientIdentifier) {
long currentTime = System.currentTimeMillis();
requestCounts.compute(clientIdentifier, (key, info) -> {
if (info == null || currentTime - info.windowStartTime > TIME_WINDOW_MILLIS) {
// New window or old window expired
return new RequestInfo(currentTime, 1);
} else {
// Same window, increment count
info.requestCount.incrementAndGet();
return info;
}
});
return requestCounts.get(clientIdentifier).requestCount.get() <= MAX_REQUESTS;
}
private static class RequestInfo {
long windowStartTime;
AtomicInteger requestCount;
RequestInfo(long windowStartTime, int initialCount) {
this.windowStartTime = windowStartTime;
this.requestCount = new AtomicInteger(initialCount);
}
}
public static void main(String[] args) {
RateLimiterService limiter = new RateLimiterService(3, 10000); // 3 requests per 10 seconds
System.out.println("Client A - Attempt 1: " + limiter.tryAcquire("clientA"));
System.out.println("Client A - Attempt 2: " + limiter.tryAcquire("clientA"));
System.out.println("Client A - Attempt 3: " + limiter.tryAcquire("clientA"));
System.out.println("Client A - Attempt 4: " + limiter.tryAcquire("clientA")); // Should be false
}
}Forstå tjenesten
Vores RateLimiterService bruger en ConcurrentHashMap til at gemme RequestInfo for hver klientidentifikator (f.eks. en IP-adresse).
MAX_REQUESTS: Det maksimale antal tilladte forespørgsler.TIME_WINDOW_MILLIS: Varigheden af det faste vindue.tryAcquire(String clientIdentifier): Denne metode kontrollerer, om en klient kan sende en forespørgsel. Den nulstiller vinduet, hvis det er udløbet, eller øger tælleren.
Metoden main demonstrerer brugen af tjenesten.
Oprettelse af et brugerdefineret filter
Lad os nu oprette et brugerdefineret OncePerRequestFilter. Dette filter vil:
- Opfange alle indgående HTTP-forespørgsler.
- Udtrække en klientidentifikator (f.eks. en ekstern IP-adresse).
- Bruge vores
RateLimiterServicetil at kontrollere, om forespørgslen er tilladt. - Hvis den ikke er tilladt, sende et HTTP 429-svar (For mange forespørgsler).
Det sikrer, at hastighedsbegrænsningen håndhæves tidligt i forespørgslens livscyklus.
Eksempel på filterkode
Her er den fulde kode til vores brugerdefinerede RateLimitingFilter. Bemærk, hvordan den injicerer RateLimiterService og håndterer svaret.
Senere tilføjer vi dette filter manuelt til vores Spring Security-kæde.
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
public class RateLimitingFilter extends OncePerRequestFilter {
private final RateLimiterService rateLimiterService;
public RateLimitingFilter(RateLimiterService rateLimiterService) {
this.rateLimiterService = rateLimiterService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String clientIp = request.getRemoteAddr(); // Simple IP-based identifier
if (!rateLimiterService.tryAcquire(clientIp)) {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("Too many requests from this IP. Please try again later.");
return; // Stop the filter chain
}
filterChain.doFilter(request, response);
}
public static void main(String[] args) throws ServletException, IOException {
// Simulate filter behavior for demo
RateLimiterService limiter = new RateLimiterService(2, 5000); // 2 requests per 5 seconds
RateLimitingFilter filter = new RateLimitingFilter(limiter);
// Mock HttpServletRequest and HttpServletResponse for testing
HttpServletRequest mockRequest = new MockHttpServletRequest("127.0.0.1");
HttpServletResponse mockResponse = new MockHttpServletResponse();
FilterChain mockChain = (req, res) -> System.out.println("Request processed by chain.");
System.out.println("Attempt 1:");
filter.doFilterInternal(mockRequest, mockResponse, mockChain);
System.out.println("Response status: " + mockResponse.getStatus());
System.out.println("Attempt 2:");
mockResponse = new MockHttpServletResponse(); // Reset response for next attempt
filter.doFilterInternal(mockRequest, mockResponse, mockChain);
System.out.println("Response status: " + mockResponse.getStatus());
System.out.println("Attempt 3 (should be blocked): ");
mockResponse = new MockHttpServletResponse();
filter.doFilterInternal(mockRequest, mockResponse, mockChain);
System.out.println("Response status: " + mockResponse.getStatus());
}
// Simple mock classes for runnable example
private static class MockHttpServletRequest implements HttpServletRequest {
private String remoteAddr;
public MockHttpServletRequest(String remoteAddr) { this.remoteAddr = remoteAddr; }
@Override public String getRemoteAddr() { return remoteAddr; }
@Override public String getMethod() { return "GET"; }
@Override public String getRequestURI() { return "/test"; }
@Override public String getHeader(String name) { return null; }
@Override public java.util.Enumeration<String> getHeaderNames() { return null; }
@Override public java.util.Enumeration<String> getHeaders(String name) { return null; }
@Override public int getIntHeader(String name) { return -1; }
@Override public String getContextPath() { return ""; }
@Override public java.security.Principal getUserPrincipal() { return null; }
@Override public boolean isUserInRole(String role) { return false; }
@Override public String getAuthType() { return null; }
@Override public String getRemoteUser() { return null; }
@Override public String getPathInfo() { return null; }
@Override public String getPathTranslated() { return null; }
@Override public String getQueryString() { return null; }
@Override public String getRemoteHost() { return null; }
@Override public int getRemotePort() { return 0; }
@Override public String getLocalAddr() { return null; }
@Override public String getLocalName() { return null; }
@Override public int getLocalPort() { return 0; }
@Override public java.util.Locale getLocale() { return null; }
@Override public java.util.Enumeration<java.util.Locale> getLocales() { return null; }
@Override public String getScheme() { return "http"; }
@Override public String getServerName() { return "localhost"; }
@Override public int getServerPort() { return 8080; }
@Override public java.io.BufferedReader getReader() throws IOException { return null; }
@Override public String getRealPath(String path) { return null; }
@Override public long getDateHeader(String name) { return -1; }
@Override public java.io.InputStream getInputStream() throws IOException { return null; }
@Override public int getContentLength() { return -1; }
@Override public long getContentLengthLong() { return -1; }
@Override public String getContentType() { return null; }
@Override public String getCharacterEncoding() { return null; }
@Override public void setCharacterEncoding(String env) throws java.io.UnsupportedEncodingException {}
@Override public java.util.Map<String, String[]> getParameterMap() { return null; }
@Override public String getParameter(String name) { return null; }
@Override public java.util.Enumeration<String> getParameterNames() { return null; }
@Override public String[] getParameterValues(String name) { return null; }
@Override public String getProtocol() { return "HTTP/1.1"; }
@Override public String getServletPath() { return ""; }
@Override public jakarta.servlet.http.HttpSession getSession(boolean create) { return null; }
@Override public jakarta.servlet.http.HttpSession getSession() { return null; }
@Override public String getRequestedSessionId() { return null; }
@Override public boolean isRequestedSessionIdValid() { return false; }
@Override public boolean isRequestedSessionIdFromCookie() { return false; }
@Override public boolean isRequestedSessionIdFromURL() { return false; }
@Override public boolean isRequestedSessionIdFromUrl() { return false; }
@Override public jakarta.servlet.RequestDispatcher getRequestDispatcher(String path) { return null; }
@Override public String getForwardURI() { return null; }
@Override public String getForwardQueryString() { return null; }
@Override public String getForwardContextPath() { return null; }
@Override public String getForwardServletPath() { return null; }
@Override public String getForwardPathInfo() { return null; }
@Override public String getForwardRequestURI() { return null; }
@Override public String getDispatcherType() { return null; }
@Override public Object getAttribute(String name) { return null; }
@Override public java.util.Enumeration<String> getAttributeNames() { return null; }
@Override public void setAttribute(String name, Object o) {}
@Override public void removeAttribute(String name) {}
@Override public jakarta.servlet.ServletContext getServletContext() { return null; }
@Override public boolean isAsyncStarted() { return false; }
@Override public boolean isAsyncSupported() { return false; }
@Override public jakarta.servlet.AsyncContext startAsync() throws IllegalStateException { return null; }
@Override public jakarta.servlet.AsyncContext startAsync(jakarta.servlet.ServletRequest servletRequest, jakarta.servlet.ServletResponse servletResponse) throws IllegalStateException { return null; }
@Override public jakarta.servlet.AsyncContext getAsyncContext() { return null; }
}
private static class MockHttpServletResponse implements HttpServletResponse {
private int status = SC_OK;
private StringWriter writer = new StringWriter();
private PrintWriter printWriter = new PrintWriter(writer);
@Override public void setStatus(int sc) { this.status = sc; }
@Override public int getStatus() { return status; }
@Override public PrintWriter getWriter() throws IOException { return printWriter; }
@Override public String toString() { return "Status: " + status + ", Body: " + writer.toString(); }
@Override public void addCookie(jakarta.servlet.http.Cookie cookie) {}
@Override public boolean containsHeader(String name) { return false; }
@Override public String encodeURL(String url) { return url; }
@Override public String encodeRedirectURL(String url) { return url; }
@Override public String encodeUrl(String url) { return url; }
@Override public String encodeRedirectUrl(String url) { return url; }
@Override public void sendError(int sc, String msg) throws IOException {}
@Override public void sendError(int sc) throws IOException {}
@Override public void sendRedirect(String location) throws IOException {}
@Override public void setDateHeader(String name, long date) {}
@Override public void addDateHeader(String name, long date) {}
@Override public void setHeader(String name, String value) {}
@Override public void addHeader(String name, String value) {}
@Override public void setIntHeader(String name, int value) {}
@Override public void addIntHeader(String name, int value) {}
@Override public void setContentType(String type) {}
@Override public String getContentType() { return null; }
@Override public void setCharacterEncoding(String charset) {}
@Override public String getCharacterEncoding() { return null; }
@Override public jakarta.servlet.ServletOutputStream getOutputStream() throws IOException { return null; }
@Override public int getBufferSize() { return 0; }
@Override public void setBufferSize(int size) {}
@Override public void flushBuffer() throws IOException {}
@Override public void resetBuffer() {}
@Override public boolean isCommitted() { return false; }
@Override public void reset() {}
@Override public void setLocale(java.util.Locale loc) {}
@Override public java.util.Locale getLocale() { return null; }
@Override public void setContentLength(int len) {}
@Override public void setContentLengthLong(long len) {}
@Override public int getHeader(String name) { return -1; }
@Override public java.util.Collection<String> getHeaders(String name) { return null; }
@Override public java.util.Collection<String> getHeaderNames() { return null; }
@Override public String getTrailerFields() { return null; }
@Override public void setTrailerFields(String trailerFields) {}
@Override public boolean isAsyncSupported() { return false; }
@Override public jakarta.servlet.AsyncContext startAsync() throws IllegalStateException { return null; }
@Override public jakarta.servlet.AsyncContext startAsync(jakarta.servlet.ServletRequest servletRequest, jakarta.servlet.ServletResponse servletResponse) throws IllegalStateException { return null; }
@Override public jakarta.servlet.AsyncContext getAsyncContext() { return null; }
}
}Registrering af filteret
For at aktivere vores RateLimitingFilter skal vi registrere det i Spring Securitys konfiguration.
Det gør vi typisk i vores SecurityFilterChain-bean og sørger for, at det kører før andre sikkerhedsfiltre, især for endepunkter uden godkendelse som login.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class SecurityConfig {
private final RateLimiterService rateLimiterService;
public SecurityConfig(RateLimiterService rateLimiterService) {
this.rateLimiterService = rateLimiterService;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // Disable CSRF for simplicity in API examples
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/login").permitAll() // Allow login without auth
.requestMatchers("/api/public/**").permitAll() // Public endpoints
.anyRequest().authenticated() // All other requests need auth
)
// Add our custom rate limiting filter
.addFilterBefore(new RateLimitingFilter(rateLimiterService),
UsernamePasswordAuthenticationFilter.class); // Or other relevant filter
return http.build();
}
@Bean
public RateLimiterService rateLimiterService() {
// Configure max 5 requests per 60 seconds
return new RateLimiterService(5, 60000);
}
public static void main(String[] args) {
System.out.println("Spring Security configuration example.");
System.out.println("RateLimiterService and RateLimitingFilter would be integrated here.");
}
}Eksempel med en controller
Lad os forestille os et enkelt loginendepunkt. Når vores filter er på plads, bliver gentagne forespørgsler fra den samme IP blokeret, når grænsen er nået.
Dette er en forenklet controller, men den viser, hvor filteret ville blive aktiveret før den egentlige loginlogik.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.ResponseEntity;
// These classes (RateLimiterService, RateLimitingFilter) would be defined as @Component/@Configuration beans
// in a full Spring Boot application, as shown in previous scenes.
@RestController
class MyController {
@GetMapping("/api/login")
public ResponseEntity<String> login() {
return ResponseEntity.ok("Login successful (simulated)!");
}
@GetMapping("/api/public/hello")
public ResponseEntity<String> publicHello() {
return ResponseEntity.ok("Hello from public endpoint!");
}
}
// A simplified Spring Boot application to make it conceptually runnable
@SpringBootApplication
public class RateLimitApp {
// The beans for RateLimiterService and RateLimitingFilter
// would typically be defined in SecurityConfig or another @Configuration class.
// We include them here for a self-contained runnable demonstration context.
@Bean
public RateLimiterService rateLimiterService() {
return new RateLimiterService(3, 10000); // 3 requests per 10 seconds
}
@Bean
public RateLimitingFilter rateLimitingFilter(RateLimiterService service) {
return new RateLimitingFilter(service);
}
public static void main(String[] args) {
System.out.println("This demonstrates a Spring Boot app structure.");
System.out.println("The RateLimitingFilter would protect /api/login.");
System.out.println("In a running app, repeated access to /api/login from the same IP");
System.out.println("would result in HTTP 429 after 3 requests within 10 seconds.");
// To actually run this, you'd uncomment:
// SpringApplication.run(RateLimitApp.class, args);
}
}Test af din hastighedsbegrænser
Når den er konfigureret, kan du teste din hastighedsbegrænser ved at sende flere forespørgsler til det beskyttede endepunkt (f.eks. /api/login) fra den samme IP-adresse.
Du bør observere følgende:
- De første forespørgsler lykkes (HTTP 200 OK).
- Når grænsen overskrides, mislykkes efterfølgende forespørgsler med statuskoden HTTP 429 (For mange forespørgsler).
Det bekræfter, at din hastighedsbegrænser aktivt beskytter din API.
Hurtigt tjek
Hastighedsbegrænsning er en vigtig sikkerhedspraksis. Hvilke af følgende er de primære fordele ved at implementere hastighedsbegrænsning på API-endepunkter?
Opsummering: Hastighedsbegrænsning
Du har lært, hvor vigtigt det er at bruge hastighedsbegrænsning til at beskytte dine API-endepunkter mod angreb som brute force og DoS.
Vi undersøgte, hvordan man implementerer en grundlæggende hastighedsbegrænser med et fast vindue ved hjælp af et brugerdefineret OncePerRequestFilter og en dedikeret tjeneste i en Spring Boot-applikation.
Denne grundlæggende sikkerhedsforanstaltning hjælper med at sikre din applikations stabilitet og tilgængeligheden af ressourcer.
Lær Java med en AI-underviser — gratis
Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.
- Kurser
- 12
- Lektioner
- 48
Ofte stillede spørgsmål
Er lektionen “Rate limiting af API-adgang” gratis?
Ja — alle 3 lektioner i læringssporet Spring Security 6 og JWT-godkendelse, inklusive “Rate limiting af API-adgang”, kan læses gratis i deres fulde længde her på webstedet. Derefter låser CoddyKit PRO alle lektioner op samt interaktive øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Spring Security 6 og JWT-godkendelse-kurset indeholder 4 lektioner i alt.
Hvad lærer jeg i “Rate limiting af API-adgang”?
Beskyt Deres autentificeringsendpoints og API'er mod brute force- og denial-of-service-angreb ved hjælp af rate limiting. Du øver dig i Spring Security 6 og JWT-godkendelse med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.
Skal jeg have erfaring for at begynde på Spring Security 6 og JWT-godkendelse?
Der kræves ingen tidligere erfaring. Spring Security 6 og JWT-godkendelse på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 2 af 4.
Hvor lang tid tager lektionen “Rate limiting af API-adgang”?
De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.
Kan jeg skrive og køre kode i denne Spring Security 6 og JWT-godkendelse-lektion?
Ja. Alle Spring Security 6 og JWT-godkendelse-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.
Alle lektioner i dette kursus
- Implementering af multifaktorautentificering
- Rate limiting af API-adgang
- Brugerdefineret håndtering af autentificeringshændelser
- Kontolåsning og beskyttelse mod brute force