inblog logo
|
DailyCoding
    Spring Boot

    14-1. 시큐리티 필터 체인 (실습)

    박은서's avatar
    박은서
    Feb 25, 2026
    14-1. 시큐리티 필터 체인 (실습)
    Contents
    1. 세팅 코드1) handler 폴더2) util 폴더3) auth 폴더4) board 폴더5) reply 폴더6) user 폴더7) db 폴더8) properties 파일2. 실습 코드1) config 폴더2) filter 폴더3. 코드 이해하기1️⃣ SecurityConfig 역할2️⃣ JwtAuthorizationFilter 역할3️⃣ 전체 동작 순서 (가장 중요한 부분)⚠️ 중요한 개념적 문제점 (실습 코드의 핵심 이해 포인트)4️⃣ 실습 코드의 진짜 목적5️⃣ 동작 흐름 한 줄 요약✅ 핵심 포인트 정리
    시큐리티 필터 · es0127park-dotcom/spring-rest-start@129bf82
    Contribute to es0127park-dotcom/spring-rest-start development by creating an account on GitHub.
    시큐리티 필터 · es0127park-dotcom/spring-rest-start@129bf82
    https://github.com/es0127park-dotcom/spring-rest-start/commit/129bf825f4b871db337313c401a697cf5f74e245#diff-346a20fc2434e67cce565425ee427485b00745ee409552df67ea97574034c652
    시큐리티 필터 · es0127park-dotcom/spring-rest-start@129bf82

    1. 세팅 코드

    1) handler 폴더

    ① Exception400.java

    C:\workspace\spring_lab\spring-rest-start\src\main\java\com\metacoding\springv2\_core\handler\ex\Exception400.java
    package com.metacoding.springv2._core.handler.ex; public class Exception400 extends RuntimeException { public Exception400(String message) { super(message); } }

    ② Exception401.java

    C:\workspace\spring_lab\spring-rest-start\src\main\java\com\metacoding\springv2\_core\handler\ex\Exception401.java
    package com.metacoding.springv2._core.handler.ex; public class Exception401 extends RuntimeException { public Exception401(String message) { super(message); } }

    ③ Exception403.java

    C:\workspace\spring_lab\spring-rest-start\src\main\java\com\metacoding\springv2\_core\handler\ex\Exception403.java
    package com.metacoding.springv2._core.handler.ex; public class Exception403 extends RuntimeException { public Exception403(String message) { super(message); } }

    ④ Exception404.java

    C:\workspace\spring_lab\spring-rest-start\src\main\java\com\metacoding\springv2\_core\handler\ex\Exception404.java
    package com.metacoding.springv2._core.handler.ex; public class Exception404 extends RuntimeException { public Exception404(String message) { super(message); } }

    ⑤ Exception500.java

    C:\workspace\spring_lab\spring-rest-start\src\main\java\com\metacoding\springv2\_core\handler\ex\Exception500.java
    package com.metacoding.springv2._core.handler.ex; public class Exception500 extends RuntimeException { public Exception500(String message) { super(message); } }

    ⑥ GlobalExceptionHandler.java

    C:\workspace\spring_lab\spring-rest-start\src\main\java\com\metacoding\springv2\_core\handler\GlobalExceptionHandler.java
    package com.metacoding.springv2._core.handler; import lombok.extern.slf4j.Slf4j; import org.springframework.http.*; import org.springframework.web.bind.annotation.*; import com.metacoding.springv2._core.handler.ex.*; import com.metacoding.springv2._core.util.Resp; @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception400.class) public ResponseEntity<?> exApi400(Exception400 e) { log.warn("[WARN] 사용자 입력 유효성 실패: " + e.getMessage()); return Resp.fail(HttpStatus.BAD_REQUEST, e.getMessage()); } @ExceptionHandler(Exception401.class) public ResponseEntity<?> exApi401(Exception401 e) { log.warn("[WARN] 사용자 인증 실패: " + e.getMessage()); return Resp.fail(HttpStatus.UNAUTHORIZED, e.getMessage()); } @ExceptionHandler(Exception403.class) public ResponseEntity<?> exApi403(Exception403 e) { log.warn("[WARN] 사용자 권한 실패: " + e.getMessage()); return Resp.fail(HttpStatus.FORBIDDEN, e.getMessage()); } @ExceptionHandler(Exception404.class) public ResponseEntity<?> exApi404(Exception404 e) { log.warn("[WARN] 사용자 자원 찾기 실패: " + e.getMessage()); return Resp.fail(HttpStatus.NOT_FOUND, e.getMessage()); } @ExceptionHandler(Exception500.class) public ResponseEntity<?> exApi500(Exception500 e) { log.warn("[ERROR] 예상 가능한 서버 오류: " + e.getMessage()); return Resp.fail(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); } // 해당 오류가 발생하면 직접 처리 혹은 Exception500으로 관리하는 것이 좋다. @ExceptionHandler(Exception.class) public ResponseEntity<?> exUnKnown(Exception e) { log.error("[SYSTEM] 예상 불가능한 서버 오류: " + e.getMessage()); e.printStackTrace(); return Resp.fail(HttpStatus.INTERNAL_SERVER_ERROR, "관리자에게 문의하세요"); } }

    ⑦ GlobalValidationHandler.java

    C:\workspace\spring_lab\spring-rest-start\src\main\java\com\metacoding\springv2\_core\handler\GlobalValidationHandler.java
    package com.metacoding.springv2._core.handler; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import org.springframework.validation.*; import com.metacoding.springv2._core.handler.ex.Exception400; import java.util.List; @Aspect // 관점 관리 @Component public class GlobalValidationHandler { @Before("@annotation(org.springframework.web.bind.annotation.PostMapping) || @annotation(org.springframework.web.bind.annotation.PutMapping)") public void badRequestAdvice(JoinPoint jp) { // jp는 실행될 실제 메서드의 모든 것을 투영하고 있다. Object[] args = jp.getArgs(); // 메서드의 매개변수들 for (Object arg : args) { // 매개변수 개수만큼 반복 (어노테이션은 제외) if (arg instanceof Errors) { Errors errors = (Errors) arg; // 에러가 존재한다면!! if (errors.hasErrors()) { List<FieldError> fErrors = errors.getFieldErrors(); for (FieldError fieldError : fErrors) { throw new Exception400(fieldError.getField() + ":" + fieldError.getDefaultMessage()); } } } } } }

    2) util 폴더

    ① JwtProvider.java

    C:\workspace\spring_lab\spring-rest-start\src\main\java\com\metacoding\springv2\_core\util\JwtProvider.java
    package com.metacoding.springv2._core.util; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; import com.metacoding.springv2.user.User; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @Component public class JwtProvider { // Bearer JWT -> JWT만 추출하기 public String resolveToken(HttpServletRequest request) { return null; } // 토큰을 검증하고 Authentication 반환 public Authentication getAuthentication(String token) { return null; } // 토큰이 유효한지 단순 체크 public boolean validateToken(String token) { return false; } }

    ② JwtUtil.java

    C:\workspace\spring_lab\spring-rest-start\src\main\java\com\metacoding\springv2\_core\util\JwtUtil.java
    package com.metacoding.springv2._core.util; import java.util.Date; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; import com.metacoding.springv2.user.User; public class JwtUtil { public static final String HEADER = "Authorization"; // HTTP 헤더 이름 public static final String TOKEN_PREFIX = "Bearer "; // 토큰 접두사 public static final String SECRET = "메타코딩시크릿키"; // 토큰 서명에 사용될 비밀 키 (강력하게 변경 필요!) public static final Long EXPIRATION_TIME = 1000L * 60 * 60 * 24 * 7; // 토큰 유효기간 7일 // JWT 토큰 생성 public static String create(User user) { return null; } // JWT 토큰 검증 및 디코딩 public static User verify(String jwt) { return null; } }

    ③ Resp.java

    C:\workspace\spring_lab\spring-rest-start\src\main\java\com\metacoding\springv2\_core\util\Resp.java
    package com.metacoding.springv2._core.util; import lombok.Data; import org.springframework.http.*; @Data public class Resp<T> { private Integer status; private String msg; private T body; public Resp(Integer status, String msg, T body) { this.status = status; this.msg = msg; this.body = body; } public static <B> ResponseEntity<Resp<B>> ok(B body) { Resp<B> resp = new Resp<>(200, "성공", body); return new ResponseEntity<>(resp, HttpStatus.OK); // body, header를 응답할 수 있는 클래스 } public static ResponseEntity<?> fail(HttpStatus status, String msg) { Resp<?> resp = new Resp<>(status.value(), msg, null); return new ResponseEntity<>(resp, status); } }

    ④ RespFilter.java

    C:\workspace\spring_lab\spring-rest-start\src\main\java\com\metacoding\springv2\_core\util\RespFilter.java
    package com.metacoding.springv2._core.util; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import java.io.*; @Slf4j public class RespFilter { private static ObjectMapper om = new ObjectMapper(); public static void fail(HttpServletResponse response, int status, String msg) throws IOException { response.setStatus(status); response.setContentType("application/json;charset=utf-8"); Resp<?> resp = new Resp<>(status, msg, null); String responseBody = null; try { responseBody = om.writeValueAsString(resp); } catch (JsonProcessingException e) { log.error("JSON 변환 실패", e); responseBody = """ {"status":500, "msg":"서버 내부 오류", "body":null} """; ; } PrintWriter out = response.getWriter(); out.println(responseBody); out.flush(); } }

    3) auth 폴더

    ① AuthController.java

    C:\workspace\spring_lab\spring-rest-start\src\main\java\com\metacoding\springv2\auth\AuthController.java
    package com.metacoding.springv2.auth; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class AuthController { @GetMapping("/health") public String healthCheck() { return "health ok"; } }

    ② AuthRequest.java

    C:\workspace\spring_lab\spring-rest-start\src\main\java\com\metacoding\springv2\auth\AuthRequest.java
    package com.metacoding.springv2.auth; import com.metacoding.springv2.user.User; import jakarta.validation.constraints.*; public class AuthRequest { public record JoinDTO( @Size(min = 4, max = 20, message = "유저네임은 4자 이상 20자 이하로 입력해주세요") @NotEmpty(message = "유저네임을 입력해주세요") String username, @NotBlank(message = "비밀번호를 입력해주세요") @Size(min = 4, max = 12, message = "비밀번호는 4~12자여야 합니다") String password, @Email(message = "이메일 형식이 올바르지 않습니다") String email) { public User toEntity(String encPassword) { return User.builder() .username(username) .password(encPassword) .email(email) .roles("USER") .build(); } } public record LoginDTO( @NotEmpty(message = "유저네임을 입력해주세요") String username, @NotBlank(message = "비밀번호를 입력해주세요") String password) { } }

    ③ AuthResponse.java

    C:\workspace\spring_lab\spring-rest-start\src\main\java\com\metacoding\springv2\auth\AuthResponse.java
    package com.metacoding.springv2.auth; import com.metacoding.springv2.user.User; public class AuthResponse { public record DTO(Integer id, String username, String email, String roles) { public DTO(User user) { this(user.getId(), user.getUsername(), user.getEmail(), user.getRoles()); } } }

    4) board 폴더

    ① Board.java

    C:\workspace\spring_lab\spring-rest-start\src\main\java\com\metacoding\springv2\board\Board.java
    package com.metacoding.springv2.board; import java.sql.Timestamp; import java.util.*; import org.hibernate.annotations.CreationTimestamp; import com.metacoding.springv2.reply.Reply; import com.metacoding.springv2.user.User; import jakarta.persistence.*; import lombok.*; @NoArgsConstructor @Getter @Entity @Table(name = "board_tb") public class Board { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(length = 30, nullable = false) private String title; @Column(length = 300, nullable = false) private String content; @ManyToOne(fetch = FetchType.LAZY) private User user; @CreationTimestamp private Timestamp createdAt; @OneToMany(mappedBy = "board", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE) private List<Reply> replies = new ArrayList<>(); public void update(String title, String content) { this.title = title; this.content = content; } @Builder public Board(Integer id, String title, String content, User user, Timestamp createdAt) { this.id = id; this.title = title; this.content = content; this.user = user; this.createdAt = createdAt; } }

    5) reply 폴더

    ① Reply.java

    C:\workspace\spring_lab\spring-rest-start\src\main\java\com\metacoding\springv2\reply\Reply.java
    package com.metacoding.springv2.reply; import java.sql.Timestamp; import org.hibernate.annotations.CreationTimestamp; import com.metacoding.springv2.board.Board; import com.metacoding.springv2.user.User; import jakarta.persistence.*; import lombok.*; @NoArgsConstructor @Getter @Entity @Table(name = "reply_tb") public class Reply { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(length = 100, nullable = false) private String comment; @ManyToOne(fetch = FetchType.LAZY) private User user; @ManyToOne(fetch = FetchType.LAZY) private Board board; @CreationTimestamp private Timestamp createdAt; @Builder public Reply(Integer id, String comment, User user, Board board, Timestamp createdAt) { this.id = id; this.comment = comment; this.user = user; this.board = board; this.createdAt = createdAt; } }

    6) user 폴더

    ① User.java

    C:\workspace\spring_lab\spring-rest-start\src\main\java\com\metacoding\springv2\user\User.java
    package com.metacoding.springv2.user; import java.sql.Timestamp; import org.hibernate.annotations.CreationTimestamp; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @NoArgsConstructor @Getter @Entity @Table(name = "user_tb") public class User { @GeneratedValue(strategy = GenerationType.IDENTITY) @Id private Integer id; @Column(unique = true, length = 20, nullable = false) private String username; @Column(length = 60, nullable = false) private String password; @Column(length = 30, nullable = false) private String email; private String roles; // 디폴트값은 USER @CreationTimestamp private Timestamp createdAt; @Builder public User(Integer id, String username, String password, String email, String roles, Timestamp createdAt) { this.id = id; this.username = username; this.password = password; this.email = email; this.roles = roles; this.createdAt = createdAt; } public void update(String email, String password) { this.email = email; this.password = password; } }

    7) db 폴더

    ① data.sql

    C:\workspace\spring_lab\spring-rest-start\src\main\resources\db\data.sql
    insert into user_tb (username, password, email, roles,created_at) values ('ssar', '$2y$04$0yDwb5VSijD7z8Wj3lFlwu50bcZRkUwqZQWekol9g.h1eCEto02VK', 'ssar@metacoding.com', 'USER',now()); insert into user_tb (username, password, email, roles,created_at) values ('cos', '$2y$04$0yDwb5VSijD7z8Wj3lFlwu50bcZRkUwqZQWekol9g.h1eCEto02VK', 'cos@metacoding.com', 'USER,ADMIN',now()); insert into board_tb (title, content, user_id,created_at) values ('title1', 'content1', 1,now()); insert into board_tb (title, content, user_id,created_at) values ('title2', 'content2', 1,now()); insert into board_tb (title, content, user_id,created_at) values ('title3', 'content3', 1,now()); insert into board_tb (title, content, user_id,created_at) values ('title4', 'content4', 2,now()); insert into board_tb (title, content, user_id,created_at) values ('title5', 'content5', 2,now()); insert into reply_tb (comment, board_id, user_id,created_at) values ('comment1', 4, 2,now()); insert into reply_tb (comment, board_id, user_id,created_at) values ('comment2', 4, 2,now()); insert into reply_tb (comment, board_id, user_id,created_at) values ('comment3', 4, 2,now()); insert into reply_tb (comment, board_id, user_id,created_at) values ('comment4', 5, 1,now()); insert into reply_tb (comment, board_id, user_id,created_at) values ('comment5', 5, 1,now());

    8) properties 파일

    ① application-dev.properties

    C:\workspace\spring_lab\spring-rest-start\src\main\resources\application-dev.properties
    # ===== Server ===== server.port=8080 spring.servlet.encoding.charset=UTF-8 spring.servlet.encoding.enabled=true spring.servlet.encoding.force=true # ===== H2 Datasource ===== spring.datasource.driver-class-name=org.h2.Driver spring.datasource.url=jdbc:h2:mem:test spring.datasource.username=sa spring.datasource.password= spring.h2.console.enabled=true # ===== JPA / Hibernate ===== spring.jpa.hibernate.ddl-auto=create spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true spring.jpa.open-in-view=false # ===== SQL Init (data.sql) ===== spring.sql.init.data-locations=classpath:db/data.sql spring.jpa.defer-datasource-initialization=true # ===== Logging Level ===== logging.level.com.metacoding.springv2=DEBUG # ===== CORS Allow ===== app.cors.allowed-origins=* spring.output.ansi.enabled=always

    ② application-prod.properties

    C:\workspace\spring_lab\spring-rest-start\src\main\resources\application-prod.properties
    # ===== Server ===== server.port=8080 spring.servlet.encoding.charset=UTF-8 spring.servlet.encoding.enabled=true spring.servlet.encoding.force=true # ===== MySQL Datasource ===== spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://${RDS_HOSTNAME}:${RDS_PORT}/${RDS_DB_NAME} spring.datasource.username=${RDS_USERNAME} spring.datasource.password=${RDS_PASSWORD} # ===== H2 console (사용 안 함) ===== spring.h2.console.enabled=false # ===== JPA / Hibernate ===== spring.jpa.hibernate.ddl-auto=none spring.jpa.open-in-view=false # ===== Logging Level ===== logging.level.com.metacoding.springv2=INFO # ===== CORS Allow ===== app.cors.allowed-origins=http://${FRONTEND_HOSTNAME}:${FRONTEND_PORT}

    ③ application.properties

    C:\workspace\spring_lab\spring-rest-start\src\main\resources\application.properties
    spring.profiles.active=dev

    2. 실습 코드

    1) config 폴더

    ① SecurityConfig.java

    C:\workspace\spring_lab\spring-rest-start\src\main\java\com\metacoding\springv2\_core\config\SecurityConfig.java
    package com.metacoding.springv2._core.config; 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; import com.metacoding.springv2._core.filter.JwtAuthorizationFilter; @Configuration public class SecurityConfig { // 시큐리티 필터 등록 @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // 인증/권한 주소 커스터마이징 http.authorizeHttpRequests(authorize -> authorize .requestMatchers("/api/**").authenticated() .anyRequest().permitAll() ); // 폼 로그인 비활성화 ( POST : x-www-form urlencoded : username, password ) http.formLogin(f -> f.disable()); // 베이직 인증 비활성화 (request 할 때마다 username, password를 요구) http.httpBasic(b -> b.disable()); // 인증 필터를 변경 (before/after 상관 없음!) http.addFilterBefore(new JwtAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } }

    2) filter 폴더

    ① JwtAuthorizationFilter.java

    C:\workspace\spring_lab\spring-rest-start\src\main\java\com\metacoding\springv2\_core\filter\JwtAuthorizationFilter.java
    package com.metacoding.springv2._core.filter; import java.io.IOException; import org.springframework.web.filter.OncePerRequestFilter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; // 인가 필터 public class JwtAuthorizationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { System.out.println("~~~~~~~~~~~~~~~~~~JwtAuthorizationFilter~~~~~~~~~~~~~"); // localhost:8080/good?username=ssar&password=1234 String username = request.getParameter("username"); String password = request.getParameter("password"); if (username.equals("ssar") && password.equals("1234")) { filterChain.doFilter(request, response); } else { response.getWriter().println("get out"); } } }

    3. 코드 이해하기

    1️⃣ SecurityConfig 역할

    SecurityConfig는 말 그대로 보안 정책 + 필터 체인 설정 담당

    1) 핵심 코드

    http.authorizeHttpRequests(authorize -> authorize .requestMatchers("/api/**").authenticated() .anyRequest().permitAll() );
    • 해당 설정의 의미
      • 경로
        의미
        /api/**
        인증된 사용자만 접근 가능
        나머지 요청
        모두 허용
        ⬇️
        /api/users → 인증 필요 /home → 그냥 접근 가능

    2) formLogin / httpBasic 비활성화

    http.formLogin(f -> f.disable()); http.httpBasic(b -> b.disable());
    ➡️ 기본 로그인 방식 제거
    • formLogin → username/password 폼 로그인 제거
    • httpBasic → 요청마다 인증창 뜨는 방식 제거
    ➡️ 우리가 직접 필터로 인증 처리하겠다는 의미

    3) 커스텀 필터 등록 (가장 중요)

    http.addFilterBefore( new JwtAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class );
    • 의미
      • Spring Security 기본 인증 필터보다 먼저 JwtAuthorizationFilter 실행
        ⬇️
        요청 → JwtAuthorizationFilter → 기본 시큐리티 필터들

    2️⃣ JwtAuthorizationFilter 역할

    인가(Authorization) 필터처럼 동작하도록 만든 커스텀 필터
    public class JwtAuthorizationFilter extends OncePerRequestFilter

    1) OncePerRequestFilter 의미

    요청당 딱 한 번만 실행되는 필터 (중복 실행 방지)

    2) 실제 수행 로직

    String username = request.getParameter("username"); String password = request.getParameter("password");
    ➡️ 요청 파라미터 추출
    localhost:8080/api/test?username=ssar&password=1234

    3) 인증 체크

    if (username.equals("ssar") && password.equals("1234"))
    • 조건 만족하면
      • filterChain.doFilter(request, response);
        ➡️ 다음 필터로 넘김 (통과)
    • 조건 실패하면
      • response.getWriter().println("get out");
        ➡️ 요청 종료 (체인 중단)

    3️⃣ 전체 동작 순서 (가장 중요한 부분)

    브라우저 요청 발생
    GET /api/test?username=ssar&password=1234

    1) 요청 진입

    클라이언트 요청 ↓ Spring Security Filter Chain 시작

    2) JwtAuthorizationFilter 실행

    왜 먼저 실행?
    👉 addFilterBefore() 때문
    JwtAuthorizationFilter
    콘솔:
    ~~~~~~~~~~~~~~~~~~JwtAuthorizationFilter~~~~~~~~~~~~~

    3) username / password 검사

    ✅ 성공 케이스
    username=ssar password=1234
    → doFilter 호출
    → 다음 필터로 이동
    ❌ 실패 케이스
    조건 불만족
    → "get out" 출력
    → 필터 체인 종료
    → 컨트롤러까지 못 감

    4) Security 인증 체크

    이제 Spring Security 정책 적용:
    .requestMatchers("/api/**").authenticated()
    문제 발생 가능 포인트 👇

    ⚠️ 중요한 개념적 문제점 (실습 코드의 핵심 이해 포인트)

    1) 상황

    • 현재 필터
      • ✔ 통과 / 차단만 함
        ❌ SecurityContext에 인증 객체 저장 안 함
    ⬇️
    • Spring Security 입장
      • "이 사용자가 인증된 사용자라는 정보가 없음"
    그래서 실제로는 아래와 같은 일 벌어짐

    2) 문제

    ssar / 1234 입력해도 필터는 통과시키지만 Spring Security는 인증 안 된 사용자로 판단
    → /api/** 접근 거부 가능

    3) 문제가 생기는 이유

    Spring Security가 인증 판단하는 기준
    SecurityContextHolder.getContext().getAuthentication()
    여기에 인증 객체가 있어야 함.
    현재 코드에는 이 작업이 없음.

    4️⃣ 실습 코드의 진짜 목적

    • 필터 체인 구조 이해
    • 필터 순서 이해
    • doFilter() 역할 이해
    • 체인 중단 메커니즘 이해

    5️⃣ 동작 흐름 한 줄 요약

    요청 발생 ↓ JwtAuthorizationFilter 실행 ↓ username/password 검사 ↓ 성공 → 다음 필터 실패 → 응답 종료 ↓ Spring Security 정책 검사 ↓ 컨트롤러 진입

    ✅ 핵심 포인트 정리

    1) SecurityConfig

    • 보안 정책 정의
    • 필터 체인 구성
    • 커스텀 필터 위치 지정

    2) JwtAuthorizationFilter

    • 요청 가로채기
    • 인증 조건 검사
    • 통과 / 차단 결정

    3) filterChain.doFilter()

    의미 “내 역할 끝났으니 다음 필터로 넘긴다”
    Share article
    Contents
    1. 세팅 코드1) handler 폴더2) util 폴더3) auth 폴더4) board 폴더5) reply 폴더6) user 폴더7) db 폴더8) properties 파일2. 실습 코드1) config 폴더2) filter 폴더3. 코드 이해하기1️⃣ SecurityConfig 역할2️⃣ JwtAuthorizationFilter 역할3️⃣ 전체 동작 순서 (가장 중요한 부분)⚠️ 중요한 개념적 문제점 (실습 코드의 핵심 이해 포인트)4️⃣ 실습 코드의 진짜 목적5️⃣ 동작 흐름 한 줄 요약✅ 핵심 포인트 정리

    DailyCoding

    RSS·Powered by Inblog