Spring Boot Security 학습 정리 (2/2) - 핵심 개념

박지명·2026년 5월 11일

스프링부트

목록 보기
9/10

1. SecurityFilterChain

HTTP 요청에 대한 보안 필터 설정

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    
    // 1. 권한 설정
    http.authorizeHttpRequests(auth -> auth
        .requestMatchers("/public/**").permitAll()
        .requestMatchers("/admin/**").hasRole("ADMIN")
        .anyRequest().authenticated()
    );
    
    // 2. 로그인 설정
    http.formLogin(auth -> auth
        .loginPage("/login")
        .loginProcessingUrl("/loginok")
    );
    
    // 3. 예외 처리
    http.exceptionHandling(auth -> auth
        .authenticationEntryPoint((req, res, e) -> res.sendRedirect("/login"))
        .accessDeniedHandler((req, res, e) -> res.sendRedirect("/denied"))
    );
    
    // 4. 로그아웃
    http.logout(auth -> auth
        .logoutUrl("/logout")
        .logoutSuccessUrl("/")
    );
    
    return http.build();
}

2. PasswordEncoder

BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

// 암호화 (회원가입 시)
String encoded = encoder.encode("1234");
// → $2a$10$nOUIs5blnq... (매번 다름)

// 검증 (로그인 시 자동)
boolean match = encoder.matches("1234", encoded);  // true
특징설명
일방향복호화 불가
동적같은 입력값도 매번 다른 결과
알고리즘BCrypt (업계 표준)
용도비밀번호만 암호화

3. UserDetails & UserDetailsService

UserDetails: Spring Security가 인식할 수 있는 사용자 정보

public interface UserDetails {
    Collection<? extends GrantedAuthority> getAuthorities();  // 권한
    String getPassword();  // 비밀번호
    String getUsername();  // 사용자명
    boolean isAccountNonExpired();
    boolean isAccountNonLocked();
    boolean isCredentialsNonExpired();
    boolean isEnabled();
}

필수 구현 (최소):

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    // DB의 role 반환
    authorities.add(() -> member.getRole());
    return authorities;
}

@Override
public String getPassword() {
    return member.getPassword();
}

@Override
public String getUsername() {
    return member.getUsername();
}

4. 권한 설정 상세

표기의미예시
ROLE_ADMINDB에 저장되는 권한"ROLE_ADMIN"
hasRole("ADMIN")SecurityConfig에서 사용ROLE_ADMIN 찾음
// DB에는 이렇게 저장
private String role;  // "ROLE_MEMBER" 또는 "ROLE_ADMIN"

// SecurityConfig에서는 이렇게 사용 (ROLE_ 자동 추가)
.hasRole("ADMIN")   // → "ROLE_ADMIN" 찾음
.hasRole("MEMBER")  // → "ROLE_MEMBER" 찾음

5. 로그인 상세 흐름

1️⃣ GET /login
   ↓ AuthController.login()
   ↓ login.html (username, password 폼)

2️⃣ POST /loginok (username, password, _csrf)
   ↓ Spring Security 필터

3️⃣ CustomUserDetailsService.loadUserByUsername(username)
   ↓ MemberRepository.findById(username)
   ↓ DB 조회

4️⃣ CustomUserDetails 객체 생성
   ↓ getAuthorities() → member.getRole()
   ↓ getPassword() → member.getPassword()

5️⃣ BCryptPasswordEncoder.matches(입력password, DB_password)
   ↓ [일치] 세션 저장
   ↓ [불일치] 로그인 실패

6️⃣ 권한 확인
   ↓ /admin → ROLE_ADMIN만 접근
   ↓ /member → ROLE_MEMBER 또는 ROLE_ADMIN
   ↓ 권한 없음 → 403 (AccessDenied)

6. 실무 활용

현재 로그인 사용자 정보

// 방법 1: Principal
@GetMapping("/mypage")
public String mypage(Principal principal) {
    String username = principal.getName();
}

// 방법 2: @AuthenticationPrincipal (추천)
@GetMapping("/mypage")
public String mypage(@AuthenticationPrincipal CustomUserDetails user) {
    Member member = user.getMember();
}

// 방법 3: SecurityContextHolder
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
CustomUserDetails user = (CustomUserDetails) auth.getPrincipal();

HTML에서 권한 확인

<!-- 로그인 여부 -->
<div th:if="${#authentication.name != 'anonymousUser'}">
    <p th:text="${#authentication.name}"></p>
    <a href="/logout">로그아웃</a>
</div>

<!-- 관리자만 -->
<div sec:authorize="hasRole('ADMIN')">
    <a href="/admin">관리 페이지</a>
</div>

<!-- 회원만 -->
<div sec:authorize="hasRole('MEMBER')">
    <a href="/member">회원 페이지</a>
</div>

회원가입 검증

@PostMapping("/joinok")
public String joinok(MemberDto dto, Model model) {
    
    // 중복 확인
    if(repo.existsById(dto.getUsername())) {
        model.addAttribute("error", "이미 존재하는 아이디");
        return "join";
    }
    
    // 비밀번호 확인
    if(!dto.getPassword().equals(dto.getPasswordConfirm())) {
        model.addAttribute("error", "비밀번호 불일치");
        return "join";
    }
    
    service.join(dto);
    return "redirect:/login";
}

로그아웃

// SecurityConfig
http.logout(auth -> auth
    .logoutUrl("/logout")
    .logoutSuccessUrl("/")
);

// HTML
<form method="POST" action="/logout">
    <button>로그아웃</button>
    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
</form>

7. 3가지 쿼리 방식과의 비교

기술방식복잡도실무
Query Method메서드명 기반낮음단순 조회
JPQLSQL 기반중간복잡한 쿼리
Query DSL빌더 기반높음동적 조건

Spring Security도 마찬가지:

  • 기본 설정 = Query Method (간단하지만 제한적)
  • 커스텀 로그인 = JPQL (유연하지만 수동 작업)
  • DB 기반 = Query DSL (복잡하지만 강력)

8. HTTP 상태 코드

코드상황원인
200OK요청 성공
401Unauthorized인증 필요 (로그인 X)
403Forbidden권한 없음 (로그인 O)
404Not Found페이지 없음
500Internal Error서버 에러
// 401 처리
.authenticationEntryPoint((req, res, e) -> res.sendRedirect("/login"))

// 403 처리
.accessDeniedHandler((req, res, e) -> res.sendRedirect("/denied"))

9. CSRF 이해

CSRF (Cross-Site Request Forgery): 다른 사이트에서 내 계정으로 요청

<!-- 공격 예 -->
<!-- 다른 사이트: <img src="mybank.com/transfer?to=attacker&amount=1000"> -->

<!-- 방어 -->
<form method="POST" action="/loginok">
    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
</form>

Spring Security는 CSRF 토큰으로 자동 방어


10. 주요 어노테이션

어노테이션위치역할
@Configuration클래스Spring 설정
@EnableWebSecurity클래스Security 활성화
@Bean메서드Spring Bean 등록
@Service클래스비즈니스 로직
@Repository클래스DB 접근
@Entity클래스JPA 관리
@Id필드Primary Key
@AuthenticationPrincipal파라미터현재 사용자 주입

11. 용어 정리

용어의미
인증 (Auth)사용자 확인 (로그인)
권한 (Auth)사용자 권한 (역할)
세션서버의 메모리 저장소
쿠키클라이언트의 파일 저장소
토큰인증 증명 (JWT)
Role역할 (ROLE_ADMIN 등)
Principal인증된 사용자
GrantedAuthority권한 정보

12. 비교표: 기본설정 vs 커스텀 vs DB

항목기본설정커스텀DB 기반
사용자 저장메모리메모리DB
사용자 설정자동수동동적
회원가입XXO
비밀번호 암호화XXO
권한 관리단순단순복잡
테스트/학습OOX
실무XXO
// 기본설정
기본 user / 임시 비밀번호

// 커스텀
고정 username / password (수동)

// DB 기반 (추천)
Member 테이블 / BCrypt / CustomUserDetailsService

0개의 댓글