[Spring] Spring Security

[verify$y]·2025년 6월 6일

Spring

목록 보기
15/16

💡 Spring 핵심 개념 인터뷰 Q&A


스프링 시큐리티란

  • Spring 기반 애플리케이션 보안의 표준
  • java 애플리케이션에 인증과 권한 부여를 모두 제공하는 목적의 프레임워크
  • Spring에서 제공하는 Authentication, Authorization 에 대한 처리를 위임하는 프레임워크
  • 인증과 인가를 Filter를 사용하여 처리한다.
  • 이 Filter는 Dispather Servlet으로 가기전에 요청응답을 가로채어 적용한다.
  • Spring Security는 보안과 관련해서 체계적으로 많은 옵션을 제공하여 직접 로직을 작성하지 않아도 된다는 장점이 있다.


스프링시큐리티 동작과정

  1. 클리어언트에 request
  2. Spring Security에서 인증/ 인가 처리 → 성공
  3. 컨트롤러에서 response반환


스프링시큐리티 적용

  1. 의존성 추가
  2. SecurityFilterChain(config) 설정
  3. 인증방식 설정(JWT)

JWT 로그인 동작 방식 요약

  1. 클라이언트가 로그인하면 /token 요청
  2. 서버가 JWT 토큰을 발급
  3. 클라이언트는 이 토큰을 Authorization 헤더에 담아 요청
  4. 서버는 토큰을 검사하고, 유효하면 인증된 사용자로 처리
  5. 이후 인증 절차 없이 토큰으로 계속 접근 가능 (세션 X)

JWT 로그인 예시

  1. 클라이언트 요청을 filter에서 가로채어 Http Header의 Authorization으로 들어온 Bear Token을 조회
  2. 조회한 Bean Token에 대한 유효성 검사
  3. 마지막으로 Token이 유효하다면, 필요한 정보를 담아 Authenticatoin객체생성
  4. Authentication객체를 SecutiryContextHolder에 담고,
  5. 다음 인증시 SpringSecurityFilterChain중 가장 앞 단에 있는 SecurityContextPersistenceFilter에서 인증을 시도한 사용자가 이전에 세션에 저장한 이력이 있는지 확인
  6. 인증이력이 있다면 SecurityContext를 꺼내와서 SecurityContextHolder에 저장
  7. jwt인증 → 로그인한 유저정보를 조회할때 다시 인증절차를 거칠필요가 없다.

JwtLoginApplication

/**
 * The application entry point.
 *
 * @author Josh Cummings
 */
@SpringBootApplication
public class JwtLoginApplication {

	public static void main(String[] args) {
		SpringApplication.run(JwtLoginApplication.class, args);
	}

}




Restconfig - 보안설정 및 JWT구성

import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class RestConfig {

	@Value("${jwt.public.key}")
	RSAPublicKey key;

	@Value("${jwt.private.key}")
	RSAPrivateKey priv;
	
	
	@Bean
		public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
			http
				// 모든 요청 인증 필요
				.authorizeHttpRequests(authz -> authz.anyRequest().authenticated())
				// /token 경로는 CSRF 비활성화
				.csrf(csrf -> csrf.ignoringRequestMatchers("/token"))
				// 기본 HTTP 인증 사용
				.httpBasic(Customizer.withDefaults())
				// JWT 기반 인증 설정
				.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
				// 세션 사용 안함 (무상태)
				.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
				// 인증 실패 및 인가 실패 처리 핸들러 등록
				.exceptionHandling(exceptions -> exceptions
					.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
					.accessDeniedHandler(new BearerTokenAccessDeniedHandler()));
			return http.build();
}

	@Bean
	UserDetailsService users() {
		//메모리 사용자 등록
		return new InMemoryUserDetailsManager(
			User.withUsername("user")
				.password("{noop}password")
				.authorities("app")
				.build()
		);
		// @formatter:on
	}

	@Bean
	JwtDecoder jwtDecoder() {
		//디코더 설정
		return NimbusJwtDecoder.withPublicKey(this.key).build();
	}

	@Bean
	JwtEncoder jwtEncoder() {
		// 공개키 - 개인키 기반 JWT 인코더 설정
		JWK jwk = new RSAKey.Builder(this.key).privateKey(this.priv).build();
		JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
		return new NimbusJwtEncoder(jwks);
	}

}



HelloController - 인증된 사용자 이름 반환


import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

	@GetMapping("/")
	public String hello(Authentication authentication) {
		// 인증 객체로부터 사용자 이름 반환
		return "Hello, " + authentication.getName() + "!";
	}

}

TokenController - 토큰발급

import java.time.Instant;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TokenController {

	@Autowired
	JwtEncoder encoder;

	@PostMapping("/token")
	public String token(Authentication authentication) {
		Instant now = Instant.now();
		long expiry = 36000L;
		// 권한 정보 문자영로 추출
		String scope = authentication.getAuthorities().stream()
				.map(GrantedAuthority::getAuthority)
				.collect(Collectors.joining(" "));
				
		
		//클레임생성
		JwtClaimsSet claims = JwtClaimsSet.builder()
				.issuer("self")
				.issuedAt(now)
				.expiresAt(now.plusSeconds(expiry))
				.subject(authentication.getName())
				.claim("scope", scope)
				.build();
	
	
		// JWT생성 및 반환
		return this.encoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
	}

}



JWT에는 세션없음

  • jWt는 클라이언트가 토큰 자체에 사용자 정보를 담아 매 요청마다 전달하므로, 서버는 세션을 유지할 필요가 없다
  • 따라서 SessionCreationPolicy.STATELESS로 설정하며, SecuriryContextPersistenceFilter로는 주로 초기화처리만 한다.
  • 그러나 세션기반이 휠씬 간편하고 자동화되어 있다.

JWT기반 인증의 장점

  • stateless : 토큰 자체에 모든 정보를담아 세션상태를 저장하지 않음
  • 확장성 : 서버가 늘어나도 세션 공유 필요없음
  • 모바일/SPA : 클라이언트가 직접 토큰을 자장하고 Authorization 헤더로 전송
  • 마이크로 서비스에 적합 : 각 서비스에서 토큰만 검증하면 되므로 분산 구조에 잘 맞음

인증/인가방식 선택

상황추천 방식
내부 관리자용 웹 서비스 (브라우저 기반)Session
외부 공개 REST API, 모바일 앱JWT
대규모 트래픽, 분산 서버 구조JWT
빠른 MVP 개발Session





profile
welcome

0개의 댓글