TIL 52 [⚓ Spring Security + 💡 Tip ( H2, Intelij http ) ]

2025. 5. 8. 15:03·[내일배움캠프-Sparta]/Spring 6기
반응형

1️⃣ JWT

✅ JWT

JWT, 즉 Json Web Token은 인증과 정보 교환을 위해 설계된 컴팩트하고 자가 수용적인 방식의 토큰임

이느 헤더(Header), 페이로드(Payload), 그리고 서명(Signature)의 세 부분으로 구성됨.

  • 헤더(Header): 헤더는 토큰의 유형(JWT)와 사용된 해싱 알고리즘(예: HMAC SHA256)을 정의함
  • 페이로드(Payload): 페이로드는 토큰에 담길 데이터를 포함, 데이터는 Claim이라 부르며 Key, Value값으로 구성됨
  • 서명(Signature): 서명은 토큰이 변조되지 않음을 보증함, 서버의 비밀 키를 사용해 헤더와 페이로드를 서명함

✅ JWT 흐름

  1. 클라이언트가 사용자 인증 정보로 로그인 요청
  2. 서버는 인증 정보를 검증하고 JWT 토큰 생성
  3. 클라이언트는 받은 토큰을 저장하고, 요청시 헤더에 포함
  4. 서버는 요청에 포함된 토큰을 검증하고 요청 처리

✅ JWT 구현코드

// JWT 토큰 생성
public String createToken(Authentication authentication) {
    String authorities = authentication.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .collect(Collectors.joining(","));

    long now = (new Date()).getTime();
    Date validity = new Date(now + this.tokenValidityInMilliseconds);

    return Jwts.builder()
            .setSubject(authentication.getName())
            .claim("auth", authorities)
            .signWith(key, SignatureAlgorithm.HS512)
            .setExpiration(validity)
            .compact();
}

// JWT 토큰 검증
public boolean validateToken(String token) {
    try {
        Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
        return true;
    } catch (JwtException | IllegalArgumentException e) {
        return false;
    }
}

✅ 오해

  1. JWT는 암호화 되어 있다?
    • 더보기
      ❌, 암호화가 아닌 서명된 토큰일 뿐, 즉 인코딩 된 데이터를 디코딩하면 누구나 데이터를 볼 수 있음
  2. JWT 모든 데이터를 넣어도 된다
    • 더보기
      ❌, JWT 토큰 내부정보는 누구든 확인 할 수 있기 때문에 중요한 정보는 넣어선 안됨
  3. JWT를 사용하면 보안이 다 된다
    • 더보기
      ❌, JWT토큰은 기본적으로 "누가 이 토큰을 만들었는지"에 대한 사실만 적혀 있음 ➡ 내가 만들었으니 토큰 내부 정보를 믿을수 있구나! 

2️⃣ Spring Security

Dispatch Servlet이 앞에서 작동한다고 인지 ( SecurityFilterChain ) ⭐

Tomcat은 Web Server이다. 정적메소드(html, css, js)가 해당

Spring Container는 WAS ( Web Application Server )이다. WAS는 백엔드 서버가 들어가 있는것으 말함.

 

⭐ SecurityContextHolder는 사람을 특정하는 인증을 말함.  AuthenticationManager는 어떤 인증방식으로 인증을 할지 

Spring Security 흐름도

AuthenticationManager 우리가 구현한 로그인 구현체로 가줌.

AuthenticationProvider 로그인 방식 추가

Authentication 내에는 UserDetails 정보가 들어가 있음.

SecurityContextHolder에 값이 잘 들어가 있으면 정상적인 인증이 된다.

✅ 개요

Spring Security는 Spring 기반 애플리케이션의 인증(Authentication)과 권한 부여(Authorization)를 담당하는 강력한 보안 프레임워크임. 웹 애플리케이션, RESTful API 및 기타 애플리케이션 유형에 대한 포괄적인 보안 솔루션을 제공

주요 특징

  • 인증(Authentication): 사용자 신원 확인
  • 권한 부여(Authorization): 인증된 사용자가 특정 리소스에 접근할 수 있는지 확인
  • 보안 취약점 방어: CSRF, XSS등으로 부터 보호
  • 다양한 인증 방식 지원: 폼 로그인, HTTP Basic, OAuth2, JWT 등
  • 메소드 수준 보안: 특정 메소드 호출에 대한 권한 체크
  • 시큐리티 컨텍스트 전파: 쓰레드간 보안 컨텍스트 전파

✅ 개념 설명

인증(Authentication)

인증은 사용자가 자신이 주장하는 사람인지 확인하는 과정입니다.

Spring Security에서는 AuthenticationManager가 인증을 담당, 실제 인증 로직은 AuthenticationProvider에서 수행합니다.

지금은 컨트롤러에서 service를 직접 만들어 구현했습니다

더보기

Controller에서 AuthenticationManager 사용하는 방법

@RestController
@RequestMapping("/api/auth")
public class AuthController {

    private static final Logger log = LoggerFactory.getLogger(AuthController.class);
    
    private final JwtTokenProvider tokenProvider;
    private final AuthenticationManager authenticationManager;

    public AuthController(JwtTokenProvider tokenProvider, AuthenticationManager authenticationManager) {
        this.tokenProvider = tokenProvider;
        this.authenticationManager = authenticationManager;
    }

    /**
     * 사용자 로그인 처리 및 JWT 토큰 발급
     * 
     * 1. UsernamePasswordAuthenticationToken 생성 (인증 요청 객체)
     * 2. AuthenticationManager에 인증 위임
     *   2.1. AuthenticationManager는 등록된 AuthenticationProvider를 찾음
     *   2.2. DaoAuthenticationProvider가 UserDetailsService를 통해 사용자 정보 조회
     *   2.3. PasswordEncoder를 사용하여 비밀번호 검증
     * 3. 인증 성공 시 Authentication 객체 반환
     * 4. SecurityContext에 인증 객체 저장
     * 5. JWT 토큰 생성 및 반환
     */
    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
        try {
            log.debug("로그인 시도: {}", loginRequest.getUsername());
            
            // 1. UsernamePasswordAuthenticationToken 생성 (인증되지 않은 상태)
            UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
            
            // 2. AuthenticationManager에게 인증 위임
            // - AuthenticationManager는 UserDetailsService를 호출하여 DB에서 사용자 조회
            // - 조회된 사용자 정보와 입력된 비밀번호를 PasswordEncoder로 검증
            Authentication authentication = authenticationManager.authenticate(authenticationToken);
            
            // 3. 인증 성공 시 SecurityContext에 인증 객체 저장
            SecurityContextHolder.getContext().setAuthentication(authentication);
            
            // 4. JWT 토큰 생성
            String jwt = tokenProvider.createToken(authentication);
            log.debug("로그인 성공: {}, 토큰 발급됨", loginRequest.getUsername());
            
            // 5. 응답 생성
            Map<String, Object> response = new HashMap<>();
            response.put("token", jwt);
            response.put("username", authentication.getName());
            
            return ResponseEntity.ok(response);
        } catch (BadCredentialsException e) {
            // 자격 증명 오류 (잘못된 사용자명 또는 비밀번호)
            log.warn("로그인 실패 - 잘못된 자격 증명: {}", loginRequest.getUsername());
            Map<String, String> error = new HashMap<>();
            error.put("error", "Invalid username or password");
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error);
        } catch (AuthenticationException e) {
            // 기타 인증 오류
            log.warn("로그인 실패 - 인증 오류: {}, 원인: {}", loginRequest.getUsername(), e.getMessage());
            Map<String, String> error = new HashMap<>();
            error.put("error", "Authentication failed: " + e.getMessage());
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error);
        }
    }
@Service
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    @Transactional(readOnly = true)
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다: " + username));

        // 사용자 정보를 Spring Security가 이해할 수 있는 UserDetails 객체로 변환
        return org.springframework.security.core.userdetails.User.builder()
                .username(user.getUsername())
                .password(user.getPassword()) // 이미 암호화된 비밀번호
                .disabled(!user.isEnabled())
                .accountExpired(false)
                .accountLocked(false)
                .credentialsExpired(false)
                .authorities(user.getRoles().stream()
                        .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                        .collect(Collectors.toList()))
                .build();
    }
}

 

SecurityConfig에 SecurityFilterChain을 잘 구현하는게 제일 중요 ⭐

권한 부여(Authorization)

권한 부여는 인증된 사용자가 특정 리소스에 접근할 수 있는지 결정하는 과정

URL 패턴, 메소드 호출 등에 대한 접근 제어를 구성할 수 있음.

http
    .authorizeHttpRequests(authorize -> authorize
        .requestMatchers("/", "/home", "/css/**", "/js/**", "/h2-console/**").permitAll()
        .requestMatchers("/api/authenticate").permitAll()
        .requestMatchers("/admin/**").hasRole("ADMIN")
        .anyRequest().authenticated()
    )

보안 컨텍스트(Security Context)

SecurityContext는 현재 인증된 사용자의 정보를 저장함. SecurityContextHolder를 통해 애플리케이션 어디서나 현재 인증된 사용자 정보에 접근할 수 있음.

Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();

보안 관련 코드

CSRF(Cross-Site Request Forgery) 보호

CSRF는 인증된 사용자가 자신도 모르게 공격자가 의도한 행동을 수행하도록 하는 공격입니다. Spring Security는 기본적으로 CSRF 보호 기능을 제공

// CSRF 보호 구성 (H2 콘솔과 API 요청은 제외)
.csrf(csrf -> csrf
    .ignoringRequestMatchers("/h2-console/**", "/api/**")
)

CSRF 보호는 Thymeleaf 템플릿에서 th:action 속성을 사용할 때 자동으로 CSRF 토큰이 포함

<form th:action="@{/login}" method="post">
    <!-- CSRF 토큰이 자동으로 포함됨 -->
</form>

세션 관리

Spring Security는 세션 고정 공격, 동시 세션 제어 등 다양한 세션 관리 기능을 제공

// 세션 관리 설정
.sessionManagement(session -> session
    .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // JWT 사용 시
    // .maximumSessions(1)   // 동시 세션 제한
    // .sessionFixation().migrateSession()  // 세션 고정 공격 방지
)

메소드 레벨 보안

메소드 호출에 대한 보안 설정을 어노테이션으로 구현할 수 있습니다.

@PreAuthorize("hasRole('ADMIN')")
public List<User> getAllUsers() {
    return userRepository.findAll();
}

@PostAuthorize("returnObject.username == authentication.name")
public User getUserById(Long id) {
    return userRepository.findById(id).orElse(null);
}

✅ OAuth2 인증 구현법

OAuth2 의존성 추가

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

OAuth2 설정

// SecurityConfig.java에 추가
.oauth2Login(oauth2 -> oauth2
    .loginPage("/login")
    .defaultSuccessUrl("/dashboard")
    .userInfoEndpoint(userInfo -> userInfo
        .userService(customOAuth2UserService)
    )
)
# application.properties에 추가
spring.security.oauth2.client.registration.google.client-id=your-client-id
spring.security.oauth2.client.registration.google.client-secret=your-client-secret
spring.security.oauth2.client.registration.google.scope=profile,email

spring.security.oauth2.client.registration.github.client-id=your-github-client-id
spring.security.oauth2.client.registration.github.client-secret=your-github-client-secret

💡 TIP!

1️⃣ 개인환경 설정 편하게 (H2 DB사용)

dependency에 H2 DB주입

H2 DB를 사용하면서 가상의 테이블을 만들어서 바로 프로그램이 실행되게 편하게 할수 있음

 

2️⃣ Postman 대신 인텔리제이로 실행

 

 

2025.05.06 - [[내일배움캠프-Sparta]/Spring 6기 ( + TIL )] - ⚓Spring Security ( 스탠다드 )

 

⚓Spring Security ( 스탠다드 )

1️⃣ Spring Security 란?Spring 기반 애플리케이션의 보안을 담당하는 프레임워크인증, 인가, CSRF, 세션 관리, 비밀번호 암호화 등 다양한 기능 제공2️⃣ 동작 흐름기본적으로 Servlet Filter 기반으로

dimenshun.tistory.com

반응형

'[내일배움캠프-Sparta] > Spring 6기' 카테고리의 다른 글

테스트 코드 (단위 테스트) ( 스탠다드 )  (0) 2025.05.11
TIL 53 [ JPA 심화 플러스 과제 ( Lv2 ) ]  (0) 2025.05.09
TIL 51 [ JPA 심화 플러스 과제( Lv 1 ), AWS (입문, IAM) ]  (0) 2025.05.07
테스트 코드 ( 베이직 )  (0) 2025.05.06
⚓Spring Security ( 스탠다드 )  (0) 2025.05.06
'[내일배움캠프-Sparta]/Spring 6기' 카테고리의 다른 글
  • 테스트 코드 (단위 테스트) ( 스탠다드 )
  • TIL 53 [ JPA 심화 플러스 과제 ( Lv2 ) ]
  • TIL 51 [ JPA 심화 플러스 과제( Lv 1 ), AWS (입문, IAM) ]
  • 테스트 코드 ( 베이직 )
dimenshun
dimenshun
한 소년의 개발 일기
    반응형
  • dimenshun
    Dev Life Notes
    dimenshun
  • 전체
    오늘
    어제
    • 분류 전체보기 (268)
      • CS (23)
        • 자료구조 (0)
        • 알고리즘 (0)
        • 컴퓨터 구조 (8)
        • 네트워크 (6)
        • 운영체제 (3)
        • DB ( + SQLD ) (5)
        • SW공학 (1)
      • 프로그래밍 (3)
        • Java (0)
        • Spring (0)
        • HTML,CSS (3)
        • JavaScript (0)
      • 개발 툴 (7)
        • Git(버전관리) (1)
        • Docker (3)
        • AWS (2)
        • JSP (1)
      • 코딩테스트(Algorithm) (125)
        • 백준 (6)
        • 프로그래머스 (119)
      • [내일배움캠프-Sparta] (110)
        • Spring 6기 (106)
        • KPT 회고 (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    docker
    Til
    AWS
    웹
    Python
    OS
    코딩테스트
    운영체제
    SQL
    개발자
    메모리
    내일배움캠프
    It
    spring
    SQLD
    web
    Testcode
    트랜잭션
    KPT
    cs
    배포
    Java
    컴퓨터구조
    알고리즘
    db
    network
    네트워크
    백엔드
    세션
    CPU
  • hELLO· Designed By정상우.v4.10.3
dimenshun
TIL 52 [⚓ Spring Security + 💡 Tip ( H2, Intelij http ) ]
상단으로

티스토리툴바