상황 : webflux 기반 서버는 기존의 mvc 서버와 다르게 설정이 필요하다.

 

 

SecurityConfig

@Configuration
@EnableWebFluxSecurity
@RequiredArgsConstructor
public class SecurityConfig {
    private final JWTUtil jwtUtil;
    private final RedisService redisService;

    @Bean
    public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
        http
                .csrf(ServerHttpSecurity.CsrfSpec::disable);
        http
                .formLogin(ServerHttpSecurity.FormLoginSpec::disable);
        http
                .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable);
        http
                .securityContextRepository(NoOpServerSecurityContextRepository.getInstance()); // session stateless 설정 방법
        http
                .authorizeExchange(auth -> auth
                        .pathMatchers("/api/users/test").permitAll()
                        .pathMatchers("/api/users/login").permitAll()
                        .pathMatchers(HttpMethod.POST, "/api/users").permitAll()
                        .pathMatchers(HttpMethod.GET, "/api/users").permitAll()
                        .anyExchange().authenticated());
        http
                .addFilterBefore(new JWTFilter(jwtUtil), SecurityWebFiltersOrder.AUTHENTICATION)
                .addFilterBefore(new ReissueFilter(jwtUtil, redisService),
                        SecurityWebFiltersOrder.AUTHENTICATION)
                .addFilterBefore(new CustomLogoutFilter(jwtUtil, redisService),
                        SecurityWebFiltersOrder.LOGOUT);
        return http.build();
    }
}

 

JWTFilter

public class JWTFilter implements WebFilter {
    private final JWTUtil jwtUtil;

    public JWTFilter(JWTUtil jwtUtil) {
        this.jwtUtil = jwtUtil;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String accessToken = request.getHeaders().getFirst("Authorization");
        if (ObjectUtils.isEmpty(accessToken)) {
            return chain.filter(exchange);
        }
        String path = request.getURI().getPath();
        HttpMethod httpMethod = request.getMethod();
        try {
            jwtUtil.isExpired(accessToken);
        } catch (ExpiredJwtException e) {
            if (path.equals("/reissue") && httpMethod == HttpMethod.POST) {
                exchange.getAttributes().put("reissue", true);
                return chain.filter(exchange);
            } else {
                throw new RuntimeException("access token expired");
            }
        }
        String category = jwtUtil.getCategory(accessToken);
        if (!category.equals("access")) {
            throw new RuntimeException("This is Invalid Token");
        }
        Long userId = jwtUtil.getUserId(accessToken);
        String role = jwtUtil.getRole(accessToken);
        List<GrantedAuthority> roles = new ArrayList<>();
        roles.add(new SimpleGrantedAuthority(role));
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(userId, null, roles);
        SecurityContext securityContext = new SecurityContextImpl(authenticationToken);
        if (checkNeedUserIdPath(path, httpMethod)) {
            request.mutate().header("X-User-Id", String.valueOf(userId));
        }
        return chain.filter(exchange).contextWrite(ReactiveSecurityContextHolder
                .withSecurityContext(Mono.just(securityContext)));
    }

    private boolean checkNeedUserIdPath(String path, HttpMethod method) {
        return path.equals("/api/users") && method.equals(HttpMethod.PATCH);
    }
}

 

ReissueTokenFilter

public class ReissueFilter implements WebFilter {
    private final JWTUtil jwtUtil;
    private final RedisService redisService;

    public ReissueFilter(JWTUtil jwtUtil, RedisService redisService) {
        this.jwtUtil = jwtUtil;
        this.redisService = redisService;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        Boolean reissue = exchange.getAttribute("reissue");
        if (reissue!=null && reissue) {
            String refresh = exchange.getRequest().getHeaders().getFirst("Refresh");
            if (refresh == null) {
                throw new RuntimeException("This is Invalid Token");
            }
            try {
                jwtUtil.isExpired(refresh);
            } catch (ExpiredJwtException e) {
                throw new RuntimeException("This is Invalid Token");
            }
            String category = jwtUtil.getCategory(refresh);
            if (!category.equals("refresh")) {
                throw new RuntimeException("This is Invalid Token");

            }
            Long userId = jwtUtil.getUserId(refresh);
            String role = jwtUtil.getRole(refresh);
            if(!Objects.equals(redisService.getValue(RedisKeyPrefix.REFRESH,userId), refresh)){
                throw new RuntimeException("This is Invalid Token");
            }
            ServerHttpResponse response = exchange.getResponse();
            String newAccess = jwtUtil.createToken("access", userId, role, 600000L);
            response.getHeaders().add("Authorization", newAccess);
            response.setStatusCode(HttpStatus.OK);
            return Mono.empty();
        }
        return chain.filter(exchange);
    }
}

 

LogoutFilter

public class CustomLogoutFilter implements WebFilter{
    private final JWTUtil jwtUtil;
    private final RedisService redisService;

    public CustomLogoutFilter(JWTUtil jwtUtil, RedisService redisService) {
        this.jwtUtil = jwtUtil;
        this.redisService = redisService;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        HttpMethod httpMethod = request.getMethod();
        if(path.equals("/logout")&&httpMethod==HttpMethod.POST){
            String refresh = request.getHeaders().getFirst("Refresh");
            if(!ObjectUtils.isEmpty(refresh)){
                Long userId = jwtUtil.getUserId(refresh);
                redisService.deleteKey(RedisKeyPrefix.REFRESH, userId);
            }
            ServerHttpResponse response = exchange.getResponse();
            HttpHeaders headers = response.getHeaders();
            response.setStatusCode(HttpStatus.OK);
            headers.set("Authorization", null);
            headers.set("Refresh", null);
            return Mono.empty();
        }
        return chain.filter(exchange);
    }
}

+ Recent posts