[Spring] Spring Security의 기초 이해

2026. 5. 19. 09:15·Spring

Spring Security란?

Spring Security란 Spring 기반 애플리케이션의 인증과 인가를 담당하는 보안 프레임워크다.

Filter 레이어에서 동작하기 때문에, 요청이 컨트롤러에 도달하기 전에 보안 처리가 완료된다.
개발자가 직접 보안 로직을 작성하지 않아도, 체계적인 보안 옵션을 제공해준다.


필요한 이유

웹 애플리케이션을 만들다보면 비로그인과 로그인 회원의 기능(마이페이지 등), 권한에 따른 기능(관리자 전용 기능)들이 나누어지기 마련이다. 만약 이러한 보안 코드를 직접 구현한다면 다음과 같은 코드가 필요할 것이다.

@GetMapping("/admin")
public String adminPage(HttpSession session) {

    User user = (User) session.getAttribute("user");
   
   ✔️ 유저 체크  
    if (user == null) {
        return "redirect:/login";         
    }
    
    ✔️ 권한 체크 
    if (!user.getRole().equals("ADMIN")) {
        return "redirect:/forbidden";    
    }
    
    ✔️ 이후 실제 로직
}
모든 컨트롤러마다 위의 보안 코드가 들어갈 것이며, 빠뜨리면 보안 문제가 생기고, 수정할 때 전부 수정해야 한다.
Spring Security는 이 문제를 필터 레이어에서 일괄 처리해주어 컨트롤러에 보안 코드를 작성하지 않아도 된다.

인증(Authentication), 인가(Authorization)

Spring Security는 기본적으로 인증절차 후에 인가절차를 거치며, Principal을 아이디로, Credential을 비밀번호로 사용하는 Credential 기반의 인증 방식을 사용한다. 
구분 내용 설명
인증
(Authentication)
해당 사용자가 본인이 맞는지 확인하는 절차 로그인
인가
( Authorization )
인증된 사용자가 요청한 자원에 접근이 가능한지 확인하는 절차 관리자 페이지 접근

 

Credential 기반 인증 방식

Spring Security는 인증할 때 두 가지 개념을 사용한다.

✔️ Principal : "누구인가?" 사용자를 식별하는 값으로 보통 아이디가 된다.
✔️ Credential : "해당 사용자라는 증거"에 대한 값으로 보통 비밀번호가 된다.

비유하자면 Principal은 주민등록번호이고, Credential은 주민등록증이다.

정리

Spring Security의 순서가 인증 -> 인가인 이유는 다음과 같다.
Principhal과 Credential로 어떤 사용자인지 알아야, 어떤 리소스에 접근할 수 있는지 판단할 수 있기 때문이다.

Spring Security 동작 원리 - 필터 체인

Spring Security의 핵심은 필터 체인으로, HTTP 요청이 컨트롤러에 도달하기 전에 여러 개의 필터를 통과한다.
각 필터가 한 개씩 보안 검사를 수행하고, 통과하지 못하면 컨트롤러까지 요청이 도달하지 못한다.

 

컨트롤러는 위 필터들을 전부 통과한 요청만 받기에, 보안 로직은 컨트롤러와 완전히 분리된다.

 

SecurityContextHolder

SecurityContextHolder는 인증된 사용자 정보를 저장하는 공간으로, 로그인 성공시 Spring Security가 이곳에 인증 정보를 담는다. 이후 요청마다 이 정보로 인가 처리에 사용한다.
✔️ 현재 로그인 사용자 정보
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();

Spring Security의 로그인 처리

Spring Security가 로그인 처리를 할 때 사용자 정보를 어떻게 가져오는지 알아야 한다.


UserDetails

Spring Security가 사용자 정보를 다룰 때 사용하는 인터페이스다. Spring Security는 우리가 만든 User 엔티티를 모르기에, 우리는 Security가 이해할 수 있는 형태로 변환해주는 규격이 UserDetails다.
public class CustomUserDetails implements UserDetails {

    private UserEntity userEntity;
    private BCryptPasswordEncoder bcrypt;

    public CustomUserDetails(UserEntity userEntity) {
        this.userEntity = userEntity;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {

        Collection<GrantedAuthority> collection = new ArrayList<>();
        collection.add(new GrantedAuthority() {

            @Override
            public String getAuthority() {
                return userEntity.getRole();
            }
        });
        return collection;
    }

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

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

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

GrantedAuthority

Spring Security에서 사용자의 권한 하나를 나타내는 인터페이스이다. getAuthority() 메서드 하나만 있고, 권한 이름 문자열을 반환한다.
여기서 getAuthorities()가 Collection인 이유는 사용자는 권한을 여러 개 가질 수 있기 때문이다.
ex) ROLE_USER, ROLE_ADMIN 등

 

계정 상태 메서드

계정의 상태를 Security에게 알려주는 메서드들이다. false를 반환하면 다음과 같은 이유로 로그인이 불가능하다.
✔ 계정 자체가 만료됐는가? (장기 미사용 계정 비활성화 같은 기능)
✔ true = 만료 안 됨 = 정상
@Override
public boolean isAccountNonExpired() {
    return true;  
}

✔ 계정이 잠겼는지 (로그인 5회 실패 시 잠금 같은 기능)
✔ true = 잠금 안 됨 = 정상
@Override
public boolean isAccountNonLocked() {
    return true;  
}

✔ 비밀번호가 만료됐는지 (90일마다 비밀번호 변경 강제 같은 기능)
✔ true = 만료 안 됨 = 정상
@Override
public boolean isCredentialsNonExpired() {
    return true;  
}

✔ 계정이 활성화됐는지 (이메일 인증 완료 여부 같은 기능)
✔ true = 활성화 = 정상
@Override
public boolean isEnabled() {
    return true;   
}

사용 예시

✔ 로그인 5회 실패 시 계정 잠금
@Override
public boolean isAccountNonLocked() {
    return userEntity.getLoginFailCount() < 5;
    ✔ 5회 이상이면 false → Security가 LockedException 던짐 → 로그인 실패
}

UserDetailService

UserDetails를 어떻게 가져올지 정의하는 인터페이스이다. DB에서 사용자를 조회하는 방법을 Spring Security에게 알려주는 역할을 한다.
@Service
@RequiredArgsConstructor
public class CustomUserDetailService implements UserDetailsService {

    private final UserRepository repository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        UserEntity findUser = repository.findByUserName(username);

        if (findUser != null) {
            return new CustomUserDetails(findUser);
        }

        throw new UsernameNotFoundException("유저를 찾을 수 없습니다: " + username);
    }
}
아이디/비밀번호 입력 
↓
UserDetailsService.loadUserByUsername(아이디) 호출
↓
DB에서 사용자 조회 → UserDetails로 변환해서 반환
↓
입력한 비밀번호 vs UserDetails.getPassword() 비교
↓
일치 → 인증 성공 / 불일치 → 인증 실패


 

세션 vs JWT

Spring Security는 기본적으로 세션 방식으로 인증을 유지한다. 로그인 성공 시 서버가 세션을 생성하고, 인증 정보를 서버 메모리에 저장하기 때문에 Stateful(상태 유지) 구조이다.

세션 동작 방식

  1. 로그인 성공
  2. 서버 세션 생성 -> JESSIONID 쿠키 클라이언트에 전달
  3. 이후 요청마다 쿠키에 JSESSIONID 포함
  4. 서버가 세션 저장소에서 인증 정보 조회
장점 단점
구현이 단순하다. 서버 메모리를 사용한다
Spring Security 기본 지원 서버가 여러대면 세션 공유 문제 발생
즉시 세션 무효화 가능 모바일 앱과 연동 불편

JWT(JSON Web Token) 방식

인증된 사용자 정보를 서버가 아닌, 토큰 자체에 담아서 클라이언트가 보관하는 인증 방식이다. 요청마다 이 토큰을 헤더에 담아서 보내면, 서버가 토큰의 서명만 검증하여 인증 처리를 한다.

서버가 세션 저장소를 따로 두지 않기 때문에  Stateless(무상태) 구조가 된다.

JWT 동작 방식

  1. 로그인 성공
  2. 서버가 JWT 토큰 생성 -> 클라이언트에게 발급
  3. 클라이언트가 토큰 보관 (localStorage 등)
  4. 이후 요청마다 Authorization 헤더에 토큰 담아 전송
  5. 서버가 토큰 서명 검증(DB/ 메모리 조회 x)

정리

서버가 1대고 일반적인 웹 서비스라면 세션이 단순하고 안전하다. 하지만 서버가 여러 대거나 모바일 앱 백엔드, REST API 서버라면 JWT가 적합하다.

 

Authorization 헤더

HTTP 요청을 보낼 때 인증 정보를 담는 헤더다. JWT 방식에서는 토큰을 이 헤더에 담아서 서버에 전송한다.
HTTP 요청
GET /api/user
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJraW0ifQ...
                ↑           ↑
            토큰 타입      실제 JWT 토큰
Bearer는 "이 토큰을 가진 사람에게 권한을 줘" 라는 뜻의 토큰 타입 표기이다. JWT쓸 때 관례적으로 붙인다.

localStorage

브라우저가 제공하는 데이터 저장 공간이다. 서버가 아닌, 사용자 브라우저 안에 데이터를 저장한다.
브라우저를 꺼도 데이터가 유지되어, JWT 토큰 저장소로 많이 사용한다.

'Spring' 카테고리의 다른 글

[Spring Boot] JPA, Spring Data JPA, QueryDSL의 이해  (0) 2026.05.16
[Spring Boot] 중복 로그인 차단 정리(스케일업, 스케일 아웃)  (0) 2026.05.13
[Spring Boot] 테스트의 이해와 @Transactional 동작 원리  (0) 2026.05.12
[Spring] - AOP(관점 지향 프로그래밍)의 이해  (0) 2026.04.28
[Spring] - BCryptPasswordEncoder를 이용한 비밀번호 암호화와 보안 원리  (1) 2026.04.16
'Spring' 카테고리의 다른 글
  • [Spring Boot] JPA, Spring Data JPA, QueryDSL의 이해
  • [Spring Boot] 중복 로그인 차단 정리(스케일업, 스케일 아웃)
  • [Spring Boot] 테스트의 이해와 @Transactional 동작 원리
  • [Spring] - AOP(관점 지향 프로그래밍)의 이해
mins0on
mins0on
비전공자의 백엔드 개발자 공부 기록 일지입니다.
  • mins0on
    꾸준함의 가치
    mins0on
  • 전체
    오늘
    어제
    • 분류 전체보기 (65) N
      • Java (7)
      • Spring (9)
      • DataBase (1)
      • Algorithm (1)
      • Network (6)
      • 운영체제 (2)
      • 코드 분석 (26)
      • Trouble Shooting (4) N
      • Project (1)
      • Migration (3)
      • 기타 (1)
      • 개념 정리 (3)
      • Coding Test (1)
        • Baekjoon (1)
  • hELLO· Designed By정상우.v4.10.6
mins0on
[Spring] Spring Security의 기초 이해
상단으로

티스토리툴바