관리자만 접근 가능한 인가 설정하기

2025. 9. 26. 16:52·2025/[풀스택]SeSAC 웹개발자 7기
반응형

“관리자만 접근(ROLE_ADMIN)”을 위해서 어디 파일의 어느 부분을 어떻게 바꿔야 하는지를 단계별로 딱 꽂아서 정리해 드릴게요.
핵심은 (1) 토큰에 role을 넣고 (2) 필터에서 role을 꺼내 권한 부여하고 (3) 보안 설정에서 admin 전용 경로를 막는 것 입니다.


(1) TokenProvider: 토큰에 role 클레임 넣기

파일

com.b1a4.cafeOn.security.TokenProvider

바꿀 위치

issueAccessToken(), issueRefreshToken() 빌더 체인에 .claim("role", userEntity.getRole().name()) 추가

private String issueAccessToken(UserEntity userEntity) {
    Date expiryDate \= Date.from(Instant.now().plus(30, ChronoUnit.MINUTES));

    return Jwts.builder()
        .signWith(SignatureAlgorithm.HS512, jwtProperties.getSecretKey())
        .setSubject(String.valueOf(userEntity.getUserId()))
        .setIssuer("cafeOn")
        .setIssuedAt(new Date())
        .setExpiration(expiryDate)
        // 🔽 여기 추가
        .claim("role", userEntity.getRole().name()) // "USER" 또는 "ADMIN"
        .compact();
}

private String issueRefreshToken(UserEntity userEntity) {
    Date expiryDate \= Date.from(Instant.now().plus(14, ChronoUnit.DAYS));

    return Jwts.builder()
        .signWith(SignatureAlgorithm.HS512, jwtProperties.getSecretKey())
        .setSubject(userEntity.getUserId())
        .setIssuer("cafeOn")
        .setIssuedAt(new Date())
        .setExpiration(expiryDate)
        // 🔽 선택(원하면 리프레시에도 넣기)
        .claim("role", userEntity.getRole().name())
        .compact();
}

또한, 필터에서 role을 꺼내기 좋게 validateAndGetUserId()만 쓰지 말고 Claims를 그대로 돌려주는 메서드를 하나 더 추가해 두면 편합니다.

public Claims validateAndGetClaims(String token) {
    return Jwts.parser()
        .setSigningKey(jwtProperties.getSecretKey())
        .parseClaimsJws(token)
        .getBody();
}

(2) JwtAuthenticationFilter: 토큰에서 role 꺼내 권한(Authorities) 부여

파일

com.b1a4.cafeOn.security.JwtAuthenticationFilter

바꿀 위치

doFilterInternal()에서 userId만 쓰던 부분을 claims → role 추출 → 권한 생성으로 확장

@Override
protected void doFilterInternal(
    HttpServletRequest req,
    HttpServletResponse res,
    FilterChain filterChain
) throws ServletException, IOException
{
    try {
        String token \= parseBearerToken(req); log.info("JwtAuthenticationFilter 실행 중...");

        if (token != null && !token.equalsIgnoreCase("null")) {
            // 🔽 변경: userId만 받지 말고 Claims 통째로
            Claims claims \= tokenProvider.validateAndGetClaims(token);

            String userId \= claims.getSubject(); // sub
            String roleFromToken \= (String) claims.get("role"); // "USER" or "ADMIN"
            if (roleFromToken == null || roleFromToken.isBlank()) {
                roleFromToken = "USER"; // 안전장치
            }

            // 🔽 스프링 시큐리티는 권한 문자열에 "ROLE\_" 프리픽스를 기대함
            // hasRole("ADMIN")를 쓰려면 실제 권한 값은 "ROLE\_ADMIN" 이어야 함
            var authorities \= AuthorityUtils.createAuthorityList("ROLE\_" + roleFromToken);

            AbstractAuthenticationToken authentication \= new UsernamePasswordAuthenticationToken(userId, null, authorities);
            SecurityContext securityContext \= SecurityContextHolder.createEmptyContext();
            securityContext.setAuthentication(authentication);
            SecurityContextHolder.setContext(securityContext);

            log.info("Authenticated user id: {}, role: {}", userId, roleFromToken);
        }
    } catch (Exception e) {
        logger.error("Could not set user authentication in security context", e);
    }

    filterChain.doFilter(req, res);
}

왜 "ROLE\_" + roleFromToken?
hasRole("ADMIN")는 내부적으로 "ROLE\_ADMIN" 권한을 찾습니다.
따라서 권한 부여 시 "ROLE\_ADMIN" 형태로 넣어야 hasRole("ADMIN")가 먹힙니다.


(3) SecurityConfig: 관리자 전용 엔드포인트 막기

파일

com.b1a4.cafeOn.configs.SecurityConfig

바꿀 위치

filterChain(HttpSecurity http)의 authorizeHttpRequests 블록에 관리자 전용 경로를 명시

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.cors(withDefaults())
        .csrf(CsrfConfigurer::disable)
        .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .authorizeHttpRequests(authorize -> authorize
            .requestMatchers("/", "/api/auth/\*\*", "/swagger-ui/\*\*", "/v3/api-docs/\*\*", "/api-docs", "/api-docs-json")
            .permitAll()
            // 🔽 관리자 전용 API 예시(원하는 경로로 교체/추가)
            .requestMatchers("/api/admin/\*\*").hasRole("ADMIN")
            .anyRequest().authenticated()
        );

    // 🔽 필터 위치: CORS 뒤, 혹은 UsernamePasswordAuthenticationFilter 앞
    http.addFilterAfter(jwtAuthenticationFilter, CorsFilter.class);
    // 또는:
    // http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

    return http.build();
}

팁) 컨트롤러 메서드 단위로도 막고 싶으면 메서드 보안을 켜서 @PreAuthorize 사용:

(1) 설정 클래스에 추가
@EnableWebSecurity 옆이나 별도 Config에 @EnableMethodSecurity(prePostEnabled = true)

(2) 컨트롤러 메서드에
@PreAuthorize("hasRole('ADMIN')")


(4) Enum과 저장 값 매칭 팁

  • UserRole이 USER, ADMIN이라면 토큰에는 그대로 "USER", "ADMIN"를 넣었습니다.
  • 필터에서 권한 만들 때만 "ROLE\_"를 붙여 스프링 시큐리티 규칙을 만족시킵니다.
  • DB/Entity → Enum → JWT claim → Filter → Authorities → hasRole("ADMIN") 흐름이 딱 맞물립니다.

(5) 요약 체크리스트

(1) TokenProvider

  • issueAccessToken()/issueRefreshToken()에 .claim("role", userEntity.getRole().name())
  • validateAndGetClaims(String token) 추가

(2) JwtAuthenticationFilter

  • validateAndGetClaims()로 Claims 얻기
  • role 꺼내 "ROLE\_" + role로 authorities 생성
  • UsernamePasswordAuthenticationToken(userId, null, authorities)

(3) SecurityConfig

  • requestMatchers("/api/admin/\*\*").hasRole("ADMIN")
  • 필터 체인에 jwtAuthenticationFilter 등록

(4) (선택) 메서드 보안

  • @EnableMethodSecurity + @PreAuthorize("hasRole('ADMIN')")

이 순서대로 넣으시면, “나중에 관리자만 접근할 수 있는 기능”을 바로 걸 수 있는 준비가 끝납니다.
원하시면 **현재 프로젝트 구조에 맞춘 실제 경로( /api/admin/\*\* 대신 원하는 경로)**로 바로 적용한 패치 버전도 만들어 드릴게요! 😊

저작자표시 비영리 변경금지 (새창열림)

'2025 > [풀스택]SeSAC 웹개발자 7기' 카테고리의 다른 글

API 기반 크롤링?  (0) 2025.10.01
python 크롤링?  (1) 2025.10.01
[b1a4 팀프로젝트 TIL] 250922월(day2) 배느실  (0) 2025.09.22
[BookTalk 팀프로젝트 회고모음] 험난했던 1달반의 여정을 마치며... KPT, TIL, CSS, ARR 회고 모음  (0) 2025.09.10
[TIL] DTO vs VO?  (0) 2025.08.25
'2025/[풀스택]SeSAC 웹개발자 7기' 카테고리의 다른 글
  • API 기반 크롤링?
  • python 크롤링?
  • [b1a4 팀프로젝트 TIL] 250922월(day2) 배느실
  • [BookTalk 팀프로젝트 회고모음] 험난했던 1달반의 여정을 마치며... KPT, TIL, CSS, ARR 회고 모음
d0yclub
d0yclub
2024.06.17 open
  • d0yclub
    개발꿈나무 김도이
    d0yclub
  • 전체
    오늘
    어제
    • 분류 전체보기 (77)
      • 서재 (0)
      • 2024 (13)
        • [FE]Next.js2기 (6)
        • [FE]우아한테크코스7기-프리코스 (6)
      • 개발공부 (25)
        • HTML CSS (6)
        • JavaScript (2)
        • React.js (4)
        • DB - mySQL (0)
        • error.log (8)
      • 2025 (32)
        • 멋쟁이사자처럼 프론트엔드스쿨플러스 4기 (1)
        • [풀스택]SeSAC 웹개발자 7기 (29)
        • 개인프로젝트 (1)
        • 팀프로젝트 (1)
      • 자료구조&알고리즘 풀이 (3)
        • 프로그래머스 (2)
      • 2026 (0)
        • [AI]SeSAC microsoft AI엔지니어 .. (0)
        • 개인프로젝트 (0)
        • 팀프로젝트 (0)
  • 블로그 메뉴

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

  • 공지사항

  • 인기 글

  • 태그

    RDS
    팀프로젝트
    스나이퍼팩토리
    프로젝트캠프
    배느실
    프론트엔드개발자양성과정
    DTO
    Next.js
    EC2
    트러블슈팅
    udemy
    KPT
    TIL
    미래내일일경험
    웅진씽크빅
    회고
    유데미
    GCP
    KPT회고
    부트캠프
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
d0yclub
관리자만 접근 가능한 인가 설정하기
상단으로

티스토리툴바