“관리자만 접근(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 |