Skip to content

Commit

Permalink
Merge pull request #213 from kookmin-sw/feature/be/#159-ApiRateLimiter
Browse files Browse the repository at this point in the history
Feature/be/#159 api rate limiter
  • Loading branch information
mclub4 authored May 16, 2024
2 parents f2512cb + ff0d348 commit 6df5e9b
Show file tree
Hide file tree
Showing 13 changed files with 85 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ public RouteLocator gatewayRoutes(RouteLocatorBuilder builder,
.uri("http://ruby:3000"))
.route("nonJwt-spring", r -> r.path("/api/user/signin", "/api/user/test", "/api/user/signup",
"/api/announcement/**", "/api/menu/**", "/api/speech/**", "/api/question/read", "/api/question/list",
"/api/faq/**", "/api/help/read", "/api/help/list", "/api/auth/**", "/api/swagger-ui/**", "/api/api-docs/**")
"/api/faq/**", "/api/help/read", "/api/help/list", "/api/auth/**", "/api/swagger-ui/**", "/api/api-docs/**",
"/api/auth/logout")
.uri("http://spring:8080"))
.route("spring", r -> r.path("/api/**")
.filters(f->f
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,27 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.security.Key;
import java.util.function.Function;

@Component
@Slf4j
@Component
public class AuthorizationHeaderFilter extends AbstractGatewayFilterFactory<AuthorizationHeaderFilter.Config> {
private Key key;
private final ReactiveRedisTemplate<String, Object> redisTemplate;

public AuthorizationHeaderFilter(@Value("${jwt.secret.key}") String secret) {
public AuthorizationHeaderFilter(@Value("${jwt.secret.key}") String secret,
ReactiveRedisTemplate<String, Object> redisTemplate) {
super(Config.class);
byte[] keyBytes = Decoders.BASE64.decode(secret);
this.key = Keys.hmacShaKeyFor(keyBytes);
this.redisTemplate = redisTemplate;
}

@Override
Expand All @@ -41,32 +46,43 @@ public GatewayFilter apply(Config config) {
String token = request.getHeaders()
.getFirst(HttpHeaders.AUTHORIZATION).replace("Bearer ", "");

System.out.println(token);

if (!validateToken(token)) {
throw JwtTokenInvalidException.INSTANCE;
}

String userRole = resolveTokenRole(token).replace("[", "").replace("]", "");

if (requiredRole.equalsIgnoreCase("role_admin")) {
if (!userRole.equalsIgnoreCase("role_admin")) {
throw JwtTokenInvalidException.INSTANCE;
}
} else if (requiredRole.equalsIgnoreCase("role_user")) {
if (!userRole.equalsIgnoreCase("role_user")) {
throw JwtTokenInvalidException.INSTANCE;
}
}

String uuid = extractUUID(token);
addAuthorizationHeaders(request, uuid);
return chain.filter(exchange);
return isTokenBlacked(token)
.flatMap(isBlacked -> {
if (isBlacked) {
throw JwtTokenInvalidException.INSTANCE;
}
String userRole = resolveTokenRole(token).replace("[", "").replace("]", "");

if (requiredRole.equalsIgnoreCase("role_admin")) {
if (!userRole.equalsIgnoreCase("role_admin")) {
throw JwtTokenInvalidException.INSTANCE;
}
} else if (requiredRole.equalsIgnoreCase("role_user")) {
if (!userRole.equalsIgnoreCase("role_user")) {
throw JwtTokenInvalidException.INSTANCE;
}
}

String uuid = extractUUID(token);
addAuthorizationHeaders(request, uuid);
return chain.filter(exchange);
});
};

return filter;
}

private Mono<Boolean> isTokenBlacked(String accessToken) {
return redisTemplate.opsForValue()
.get(accessToken)
.map(value -> "logout".equals(value))
.defaultIfEmpty(false);
}

private boolean validateToken(String token) {
try {
Jwts.parserBuilder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,10 @@ public RequestRateLimitFilter(

@Override
public GatewayFilter apply(Config config) {
log.info("여기 필터 지나는지 확인 1111");
GatewayFilter filter = (exchange, chain) -> {
KeyResolver keyResolver = getOrDefault(config.keyResolver, defaultKeyResolver);
RedisRateLimiter rateLimiter = getOrDefault(config.rateLimiter, defaultRateLimiter);
String routeId = config.getRouteId();
log.info("여기 필터 지나는지 확인 2222222");

return keyResolver.resolve(exchange)
.flatMap(key -> rateLimiter.isAllowed(routeId, key))
Expand Down
4 changes: 0 additions & 4 deletions back-gateway/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,3 @@ server:
port: 8081
chatbot-url: ${CHATBOT_URL}

logging:
level:
root: debug

3 changes: 2 additions & 1 deletion back/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ out/

### VS Code ###
.vscode/
.env
.env
generated
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public class QUser extends EntityPathBase<User> {

public final com.example.capstone.global.entity.QBaseTimeEntity _super = new com.example.capstone.global.entity.QBaseTimeEntity(this);

public final StringPath bigmajor = createString("bigmajor");

public final StringPath country = createString("country");

//inherited
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
import com.example.capstone.domain.auth.dto.ReissueRequest;
import com.example.capstone.domain.auth.dto.TokenResponse;
import com.example.capstone.domain.auth.service.AuthService;
import com.example.capstone.domain.jwt.JwtTokenProvider;
import com.example.capstone.global.dto.ApiResult;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestController
Expand All @@ -20,11 +20,25 @@
public class AuthController {

private final AuthService authService;
private final JwtTokenProvider jwtTokenProvider;

@PostMapping("/reissue")
public ResponseEntity<ApiResult<TokenResponse>> reissue(@RequestBody @Valid ReissueRequest reissueRequest) {
TokenResponse tokenResponse = authService.reissueToken(reissueRequest.refreshToekn());
TokenResponse tokenResponse = authService.reissueToken(reissueRequest.refreshToken());
return ResponseEntity
.ok(new ApiResult<>("Successfully get token", tokenResponse));
}

@PostMapping("/logout")
public ResponseEntity<ApiResult<String>> logout(@RequestHeader(HttpHeaders.AUTHORIZATION) String accessToken,
@RequestBody @Valid ReissueRequest reissueRequest) {
if (StringUtils.hasText(accessToken) && accessToken.startsWith("Bearer ")) {
accessToken = accessToken.substring(7);
}
jwtTokenProvider.validateToken(reissueRequest.refreshToken());
authService.logout(accessToken, reissueRequest.refreshToken());

return ResponseEntity
.ok(new ApiResult<>("Successfully logout token", "Logout successful"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
import jakarta.validation.constraints.NotBlank;

public record ReissueRequest(
@NotBlank String refreshToekn
@NotBlank String refreshToken
) {
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.example.capstone.domain.auth.service;

import com.example.capstone.domain.auth.dto.TokenResponse;
import com.example.capstone.domain.jwt.JwtClaim;
import com.example.capstone.domain.jwt.JwtTokenProvider;
import com.example.capstone.domain.jwt.PrincipalDetails;
import com.example.capstone.global.error.exception.BusinessException;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
Expand Down Expand Up @@ -44,4 +46,12 @@ public TokenResponse reissueToken(String refreshToken) {

return tokenResponse;
}

public void logout(String accessToken, String refreshToken) {
Claims claims = jwtTokenProvider.parseClaims(refreshToken);
String userId = claims.get(JwtClaim.UUID.getKey(), String.class);
redisTemplate.opsForValue().getAndDelete(userId);

redisTemplate.opsForValue().set(accessToken, "logout");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ public Authentication getAuthentication(String token) {
return new UsernamePasswordAuthenticationToken(principalDetails, "", authorities);
}

public Claims parseClaims(String token) {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
}

public boolean validateToken(String token) {
try {
Jwts.parserBuilder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public record SignupRequest(
@NotBlank String name,
@NotBlank String country,
@Pattern(regexp = "^010-\\d{4}-\\d{4}$") String phoneNumber,
@NotBlank String bigmajor,
@NotBlank String major
) implements HmacRequest {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public record UserProfileUpdateRequest(
@NotBlank String name,
@NotBlank String country,
@Pattern(regexp = "[0-9]{10,11}") String phoneNumber,
@NotBlank String major
@NotBlank String major,
@NotBlank String bigmajor
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ public class User extends BaseTimeEntity {
@Column(nullable = false, unique = true)
private String email;

@Column(nullable = false)
private String bigmajor;

@Column(nullable = false)
private String major;

Expand Down

0 comments on commit 6df5e9b

Please sign in to comment.