본문으로 건너뛰기
AIDevOps
  • Learn
  • Learning Paths
  • Practice
  • Open Source
  • Books
  • Engineering

    AI DevOpsAI 서비스 개발·운영 전체 지도LLMOpsLLM 배포·평가·관측실전 프로젝트AI Agent 프로젝트 실습

    Knowledge

    Docs기술 문서 모음Blog엔지니어링 아티클Plogger개발 기록 피드

    Validate

    Certification3단계 역량 인증 · 준비 중
AI Models
LlamaMistralGemmaDeepSeekQwen
🌱 Spring Cloud
Spring 입문 & 로드맵Spring Cloud GatewaySpring BootJava|Spring AISpring SecuritySpring BatchSpring JPA
🤖 AI 실전 개발
AI 실전 입문 & 로드맵Hugging FaceLangChainLlamaIndexLLMOps|LangGraphMCPMulti-AgentAgent Evaluation
🧠 AI Core
AI 입문 & 로드맵ML FundamentalsLLM Fundamentals|Python AIC++|PyTorchTensorFlowJAX
🧠 AI Agent 개발
금융 AI AgentLLM API 서버주식 투자 AgentAIOps AI Agent교육 AI Agent코딩 AI Agent
🐳 DevOps
DevOps 입문 & 로드맵LinuxDockerCI/CD|Kubernetes 기본K8s 심화/실무PrometheusGrafana
🧱 인프라
인프라 입문 & 로드맵NginxRedis
☁️ 클라우드
클라우드 입문 & 로드맵AWSGCPAzureNCPCloudflare
🎨 Frontend
Frontend 입문 & 로드맵JavaScriptTypeScript|ReactNext.js|VueNuxt
📱 Mobile
Mobile 입문 & 로드맵KotlinAndroidFlutter
⚙️ Backend
Backend 입문 & 로드맵Python 기본FastAPIDjangoFlask|CGoGinNode.js
💾 Database
DB 입문 & 로드맵공통 SQLOracleMySQLPostgreSQL|MongoDB벡터 DB
🧪 검증
k6JMeternGrinder
AIDevOps

Engineering AI. From Code to Production.
AI와 AI Agent를 개발하고 운영하기 위한 엔지니어링 학습 플랫폼

Learn

  • 전체 가이드
  • Learning Paths
  • Practice
  • Books

Resources

  • AI DevOps
  • LLMOps
  • 실전 프로젝트
  • Docs
  • Blog
  • Plogger
  • Open Source
  • Certification (준비 중)

Start Here

  • AI Core 로드맵
  • AI 실전 개발 로드맵
  • Spring Cloud 로드맵
  • DevOps 로드맵
  • 인프라 로드맵

 

  • 클라우드 로드맵
  • Frontend 로드맵
  • Mobile 로드맵
  • Backend 로드맵
  • Database 로드맵
© 2026 AI DevOps Korea. All rights reserved.
이용약관개인정보처리방침Sitemaptestforge.kr
  1. Home
  2. Learn
  3. Spring Cloud
  4. Spring Security
Spring Boot 인증 & 인가 가이드

🔐 Spring Security 완전 가이드

Visitors

Spring Boot 기반 Spring Security 6로 JWT 인증, OAuth2 소셜 로그인, Role 기반 인가, Method Security, CSRF 방어, 테스트까지 실전 보안 아키텍처를 구성합니다.

  • Advanced · 심화
  • 업데이트 2026.09.19
  • 약 13분 읽기
  • 9개 섹션
  • 예제 코드 9개

포함된 Learning Path

이 가이드는 아래 경로의 한 단계입니다. 앞뒤 순서와 함께 학습해보세요.

  • Spring Cloud Engineer →
JWT 인증 서버OAuth2 소셜 로그인Role/Permission 인가Spring Boot 보안

관련 프레임워크 & 개발환경

🍃Spring Boot→☕Java→🗄️Spring JPA→GWSpring Cloud Gateway→

목차

0 / 11
  1. 가이드 사용법
  2. 구조 다이어그램
  3. Spring Security란?
  4. Spring Boot 프로젝트 설정
  5. SecurityFilterChain 구성
  6. JWT 필터 구현
  7. UserDetailsService
  8. Role 기반 인가
  9. Method Security
  10. OAuth2 소셜 로그인
  11. 보안 테스트
목차 11개 섹션
  1. 가이드 사용법
  2. 구조 다이어그램
  3. Spring Security란?
  4. Spring Boot 프로젝트 설정
  5. SecurityFilterChain 구성
  6. JWT 필터 구현
  7. UserDetailsService
  8. Role 기반 인가
  9. Method Security
  10. OAuth2 소셜 로그인
  11. 보안 테스트

가이드 사용법

읽는 방향

Spring Security를 실무 흐름으로 이해하기

Spring Boot 기반 Spring Security 6로 JWT 인증, OAuth2 소셜 로그인, Role 기반 인가, Method Security, CSRF 방어, 테스트까지 실전 보안 아키텍처를 구성합니다. 이 가이드는 개념을 나열하기보다, 실제 프로젝트에서 판단해야 하는 순서대로 내용을 따라갈 수 있게 구성했습니다.

핵심 관점

백엔드 / 시스템 개발

문법보다 요청이 들어와 검증, 처리, 저장, 응답으로 이어지는 경계를 먼저 잡습니다.

JWT 인증 서버OAuth2 소셜 로그인Role/Permission 인가Spring Boot 보안

구조 다이어그램

글로 읽은 내용을 머릿속에 오래 남기려면 먼저 흐름을 그림으로 잡는 편이 좋습니다. 아래 두 그림은 Spring Security를 학습할 때 계속 되돌아볼 수 있는 기준 지도입니다.

학습 흐름

다이어그램 렌더링 중…

아키텍처 관점

다이어그램 렌더링 중…

Spring Security란?

Spring Security를 처음 펼칠 때는 세부 명령보다 큰 그림이 먼저입니다. 이 섹션에서는 앞으로 배울 개념들이 어떤 문제를 풀기 위해 등장했는지부터 잡아봅니다.

Spring Security는 Spring Boot 애플리케이션의 인증(Authentication)과 인가(Authorization)를 담당하는 보안 프레임워크입니다. 필터 체인 기반으로 동작하며 JWT, OAuth2, Session 방식 모두 지원합니다.
개념설명
Authentication사용자가 누구인지 확인 (로그인, JWT 검증)
Authorization어떤 자원에 접근할 수 있는지 결정 (Role, Permission)
SecurityFilterChainHTTP 요청마다 실행되는 보안 필터 체인
UserDetails사용자 정보를 Spring Security에 전달하는 인터페이스

Spring Boot 프로젝트 설정

여기서는 Spring Boot 프로젝트 설정을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.

Spring Initializr에서 Spring Security, Spring Data JPA, Validation, Lombok을 추가합니다.
build.gradle.ktsKOTLIN
plugins {
    id("java")
    id("org.springframework.boot") version "3.3.5"
    id("io.spring.dependency-management") version "1.1.6"
}

java { toolchain { languageVersion.set(JavaLanguageVersion.of(21)) } }

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-security")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-validation")
    implementation("io.jsonwebtoken:jjwt-api:0.12.6")
    runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.6")
    runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.6")
    compileOnly("org.projectlombok:lombok")
    annotationProcessor("org.projectlombok:lombok")
    runtimeOnly("com.h2database:h2")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.springframework.security:spring-security-test")
}

SecurityFilterChain 구성

여기서는 SecurityFilterChain 구성을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.

Spring Boot 3.x / Security 6에서는 WebSecurityConfigurerAdapter 대신 SecurityFilterChain Bean을 직접 등록합니다. 요청 하나는 여러 필터를 순서대로 통과하며, 커스텀 JWT 필터는 표준 인증 필터보다 앞에 배치해 토큰을 먼저 검증하고 SecurityContext를 채워둡니다.
다이어그램 렌더링 중…
SecurityConfig.javaJAVA
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtAuthenticationFilter jwtAuthFilter;
    private final UserDetailsService userDetailsService;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            .csrf(csrf -> csrf.disable())
            .sessionManagement(sm -> sm.sessionCreationPolicy(STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
            .build();
    }

    @Bean
    public AuthenticationProvider authenticationProvider() {
        var provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(passwordEncoder());
        return provider;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config)
            throws Exception {
        return config.getAuthenticationManager();
    }
}

JWT 필터 구현

여기서는 JWT 필터 구현을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.

OncePerRequestFilter를 상속해 모든 요청에서 Authorization 헤더를 추출하고 토큰을 검증합니다. 이름 그대로 하나의 요청당 정확히 한 번만 실행되도록 보장되는 필터이며, SecurityContextHolder에 인증 정보를 직접 채워 넣기 때문에 이후 컨트롤러·서비스 어디서든 @AuthenticationPrincipal 등으로 현재 로그인한 사용자를 조회할 수 있게 됩니다.
JwtAuthenticationFilter.javaJAVA
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtService jwtService;
    private final UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {

        final String authHeader = request.getHeader("Authorization");
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }

        final String jwt = authHeader.substring(7);
        final String username = jwtService.extractUsername(jwt);

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            if (jwtService.isTokenValid(jwt, userDetails)) {
                var authToken = new UsernamePasswordAuthenticationToken(
                    userDetails, null, userDetails.getAuthorities()
                );
                authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authToken);
            }
        }
        filterChain.doFilter(request, response);
    }
}

UserDetailsService

여기서는 UserDetailsService을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.

UserDetailsService를 구현해 DB에서 사용자 정보를 로드합니다. Spring Security는 인증 과정에서 이 인터페이스의 loadUserByUsername만 호출하므로, 실제 사용자 정보가 DB든 외부 API든 어디에 있든 이 한 곳만 구현하면 인증 로직 나머지는 프레임워크가 동일하게 처리해줍니다.
CustomUserDetailsService.javaJAVA
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        return userRepository.findByEmail(email)
            .orElseThrow(() -> new UsernameNotFoundException("사용자 없음: " + email));
    }
}

// User Entity — UserDetails 구현
@Entity @Table(name = "users")
@Getter @Builder @NoArgsConstructor @AllArgsConstructor
public class User implements UserDetails {
    @Id @GeneratedValue(strategy = IDENTITY) private Long id;
    private String name;
    @Column(unique = true) private String email;
    private String password;
    @Enumerated(STRING) private Role role;

    @Override public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(new SimpleGrantedAuthority("ROLE_" + role.name()));
    }
    @Override public String getUsername()  { return email; }
    @Override public boolean isAccountNonExpired()  { return true; }
    @Override public boolean isCredentialsNonExpired() { return true; }
    @Override public boolean isEnabled() { return true; }
}

Role 기반 인가

여기서는 Role 기반 인가을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.

HTTP 요청 단위 또는 메서드 단위로 Role을 검사합니다. 회원가입·로그인처럼 인증 이전에 호출되는 API는 SecurityConfig에서 permitAll로 열어두고, 비밀번호는 절대 평문으로 저장하지 않고 PasswordEncoder로 해시한 값만 DB에 남기는 것이 기본 원칙입니다.
AuthController.javaJAVA
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {

    private final AuthService authService;

    @PostMapping("/register")
    @ResponseStatus(HttpStatus.CREATED)
    public AuthResponse register(@Valid @RequestBody RegisterRequest req) {
        return authService.register(req);
    }

    @PostMapping("/login")
    public AuthResponse login(@Valid @RequestBody LoginRequest req) {
        return authService.login(req);
    }
}

// 로그인 서비스
@Service @RequiredArgsConstructor
public class AuthService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final JwtService jwtService;
    private final AuthenticationManager authManager;

    public AuthResponse register(RegisterRequest req) {
        var user = User.builder()
            .name(req.getName())
            .email(req.getEmail())
            .password(passwordEncoder.encode(req.getPassword()))
            .role(Role.USER)
            .build();
        userRepository.save(user);
        return new AuthResponse(jwtService.generateToken(user));
    }

    public AuthResponse login(LoginRequest req) {
        authManager.authenticate(
            new UsernamePasswordAuthenticationToken(req.getEmail(), req.getPassword()));
        var user = userRepository.findByEmail(req.getEmail()).orElseThrow();
        return new AuthResponse(jwtService.generateToken(user));
    }
}

Method Security

여기서는 Method Security을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.

@EnableMethodSecurity를 활성화하면 서비스 메서드에 @PreAuthorize, @PostAuthorize를 적용할 수 있습니다.
JAVA
@Service
@PreAuthorize("isAuthenticated()")
public class PostService {

    @PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")
    public List<Post> findByUser(Long userId) { ... }

    @PreAuthorize("hasRole('ADMIN')")
    public void deleteAll() { ... }

    @PostAuthorize("returnObject.author == authentication.name")
    public Post findById(Long id) { ... }
}

OAuth2 소셜 로그인

여기서는 OAuth2 소셜 로그인을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.

spring-boot-starter-oauth2-client를 추가하고 application.yml에 Client 정보를 설정하면 Google, Kakao, Naver 소셜 로그인을 구현할 수 있습니다. 우리 서버는 사용자의 비밀번호를 직접 다루지 않고, Google이 발급한 Authorization Code를 Access Token으로 교환해 사용자 정보만 넘겨받습니다.
다이어그램 렌더링 중…
application.ymlYAML
spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: ${GOOGLE_CLIENT_ID}
            client-secret: ${GOOGLE_CLIENT_SECRET}
            scope: openid, profile, email
OAuth2UserService.javaJAVA
@Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {

    private final UserRepository userRepository;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest request) throws OAuth2AuthenticationException {
        OAuth2User oAuth2User = super.loadUser(request);
        String email = oAuth2User.getAttribute("email");
        String name  = oAuth2User.getAttribute("name");

        User user = userRepository.findByEmail(email)
            .orElseGet(() -> userRepository.save(
                User.builder().email(email).name(name).role(Role.USER).build()
            ));
        return new DefaultOAuth2User(
            List.of(new SimpleGrantedAuthority("ROLE_" + user.getRole())),
            oAuth2User.getAttributes(), "email"
        );
    }
}

보안 테스트

여기서는 보안 테스트을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.

@WithMockUser, @WithUserDetails로 인증 컨텍스트를 주입하고 MockMvc로 보안 설정을 검증합니다. 실제 JWT를 발급받고 검증하는 과정을 매 테스트마다 거치지 않아도, 이미 인증된 것처럼 가정한 상태에서 권한별 접근 제어(403/401 응답 등)가 의도대로 동작하는지 빠르게 확인할 수 있습니다.
SecurityTest.javaJAVA
@SpringBootTest
@AutoConfigureMockMvc
class SecurityTest {

    @Autowired MockMvc mvc;

    @Test
    void 인증_없이_보호된_경로는_401() throws Exception {
        mvc.perform(get("/api/users"))
            .andExpect(status().isUnauthorized());
    }

    @Test
    @WithMockUser(roles = "USER")
    void 일반_유저는_사용자_목록_조회_가능() throws Exception {
        mvc.perform(get("/api/users"))
            .andExpect(status().isOk());
    }

    @Test
    @WithMockUser(roles = "USER")
    void 일반_유저는_어드민_엔드포인트_접근_불가() throws Exception {
        mvc.perform(delete("/api/admin/users/1"))
            .andExpect(status().isForbidden());
    }

    @Test
    @WithMockUser(roles = "ADMIN")
    void 어드민은_삭제_가능() throws Exception {
        mvc.perform(delete("/api/admin/users/1"))
            .andExpect(status().isNoContent());
    }
}
← 이전 가이드Spring AI다음 가이드 →Spring Batch