본문 바로가기

Spring Boot/유저 관리 기능

인가와 권한 관리 : 세션/토큰 기반 인증에서의 인가 구현

- 세션 기반 인증에서 권한 정보가 어떻게 저장되고 활용되는가

- 토큰 기반 인증(JWT)에서 권한 정보가 포함되는 방식

- 세션과 토큰 기반 인가 구현의 장단점

- 실제 애플리케이션에서 권한 정보를 안전하게 관리하는 방법

- 사례를 통해 실무에서 발생할 수 있는 문제 예방법


세션 기반 인가 구현

1. 세션에 권한 정보 저장

세션 기반 인증에서는 사용자가 로그인하면 서버는 세션 객체(Session Object)를 생성함

이 객체 안에는 사용자의 정보와 함께 권한(Role/Permission) 정보도 함께 저장할 수 있다

  • 서버 메모리 또는 Redis같은 세션 저장소에 보관함
  • 클라이언트는 JSESSIONID 같은 세션ID 쿠키만 전달한다
  • 서버는 세션ID를 바탕으로 사용자와 권한 정보를 복원함

세션 저장 구조 예시

  • SessionStore
    • sessionId: abc123
      • userId: B1uffer
      • roles: ["USER", "EDITOR"]
      • lastAccessTime: ...

로그인시 만들어지는 세션 객체에 사용자의 정보, 권한 정보도 들어갈 수 있다

 

 

2. 예제(세션 기반)

    // HttpSession을 활용한 권한 확인 예시
    public void updatePost(HttpSession session, Post post, String newContent) {
        // HttpSession에서 가져옴
        User user = (User) session.getAttribute("user");
        if(!post.getOwner().equals(user.getUsername()) && !user.getRoles().contains("ADMIN")) {
            throw new SecurityException("수정 권한이 없습니다.");
        }
        post.setContent(newContent);
    }
    
    // 비교를 위해
        public void update(Post post, String newContent) {
        // 인증 로직
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();

        // 작성자 본인이거나 관리자일 때 업데이트 가능
        if(!post.getOwner().equals(principal.getUsername()) || !principal.isAdmin()) {
            log.info("게시글 수정 실패");
            throw new IllegalArgumentException("수정 권한이 없습니다.");
        }
        post.setContent(newContent);
        log.info("게시글 수정 완료");
    }

 

세션에 저장된 사용자 정보에서 권한을 조회하고 검증한다

 

* 세션은 서버 자원을 소모하므로 확장성(Scalability)이 떨어질 수 있음

* 세션 저장소를 외부 서버 캐시(Redis, Memcached)로 구성하면 확장성 문제를 완화할 수 있음

* 세션이 만료되면 자동으로 권한도 무효화되게끔 해야한다

 

3. 정리

특징 설명
저장 위치 서버 메모리 또는 외부 세션 저장소
전달 방식 클라이언트는 세션 ID 쿠키만 전달함
장점 구현이 단순함, 서버에서 중앙 집중 관리가 가능함
단점 서버 확장시 세션 동기화 필요

 


토큰 기반 인가 구현(JWT)

1. JWT에 권한 정보 포함하기

JWT(Json Web Token)는 인증 정보를 클라이언트가 직접 보관하는 방식임

서버는 JWT를 검증하기만 하면 되므로 확장성이 높다

JWT 구조는 Header.Payload.Signature로 이루어져 있으며 Payload에 관한 정보(roles)를 포함시킬 수 있다

{
  "sub": "B1uffer",
  "roles": ["USER", "EDITOR"],
  "iat": 1737072000,
  "exp": 1737075600
}

 

 

 

2. 예제(JWT 기반)

build.gradle에 의존성 추가

    // JWT 의존성 추가
    implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5'

 

JwtUtils.java

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;

import javax.crypto.SecretKey;

public class JwtUtils {
    private static final String SECRET = "mysecretkey123";
    private static final SecretKey KEY = Keys.hmacShaKeyFor(SECRET.getBytes());

    public static Claims parseToken(String token) {
        return Jwts.parser()
                .verifyWith(KEY)
                .build()
                .parseSignedClaims(token)
                .getPayload();
    }
}

 

JwtAuthorizationService.java

import com.b1uffer.sessiontest.entity.Post;
import com.b1uffer.sessiontest.security.jwt.JwtUtils;
import io.jsonwebtoken.Claims;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.List;

@Slf4j
@Service
@NoArgsConstructor
public class JwtAuthorizationService {
    // JWT를 활용한 인가 구현 예제
    public void updateContent(String jwtToken, Post post, String newContent) {
        Claims claims = JwtUtils.parseToken(jwtToken);
        String userId = claims.getSubject();
        List<String> roles = claims.get("roles", List.class);

        if(!post.getOwner().equals(userId) && !roles.contains("ADMIN")) {
            throw new SecurityException("수정 권한이 없습니다.");
        }
        post.setContent(newContent);
    }
}

 

서버는 토큰을 해석해서 권한을 확인하고, 별도의 세션 저장소는 사용하지 않는다

 

 

 

3. JWT 동작 흐름 다이어그램

로그인 요청시 토큰을 발급해주고, API 요청이 들어오면 권한만 확인해서 응답해줌

 

 

* JWT는 탈취되면 만료 전까지 무효화할 방법이 없다. 따라서 짧은 만료시간을 두고 Refresh Token과 함께 사용해야한다

* Payload는 Base64로 인코딩된 것이므로, 누구나 내용을 볼 수 있다. 민감한 정보는 절대 넣지 않기!

* 서버는 무상태성(stateless) 구조를 유지할 수 있기 때문에, 대규모 서비스에 적합하다

 

 

 

4. 정리

특징 설명
저장 위치 클라이언트(JWT 자체)
전달 방식 HTTP Header(Authorization: Bearer)
장점 서버 확장에 유리, 무상태 유지 가능
단점 탈취시 위험, 만료전 무효화 어려움

 


권한 정보 관리의 모범 사례

1. 최소 권한 원칙(Principle of Least Privilege)

사용자가 업무 수행에 꼭 필요한 최소한의 권한만 가지도록 설계해야함

예 : 일반 사용자는 자기 글만 수정 가능, 관리자는 모든 글 수정 가능

 

 

2. 중앙 집중 관리

권한 정책은 코드 여러곳에 흩어지지 않고, 중앙 관리 모듈을 통해 관리해야함

예 : AuthorizationService 같은 클래스에서 모든 권한 검증을 수행함

 

 

 

3. 로그와 모니터링

권한 검증 실패는 보안 이벤트로 기록해야함

예 : 관리자 페이지 접근시 권한 부족으로 거절함 -> 보안 로그에 남기기

 

 

4. 계층적 권한 설계

USER < EDITOR < ADMIN 처럼 계층적 구조를 도입하면 권한 관리가 단순해짐

권한 설계를 초기에 잘못하면 유지보수가 어려워짐

 

 

정리

모범 사례 설명
최소 권한 원칙 꼭 필요한 권한만 부여
중앙 집중 관리 권한 검증 코드 일원화
로그와 모니터링 권한 실패 이벤트 기록
계층적 권한 설계 유지보수 용이성 확보