타입 안전성
컴파일 타임에 오류 잡아줌. 문자열로 쿼리 짜는 JPQL보다 안전함.
오타, 컬럼 이름 틀림 -> 컴파일 에러로 바로 확인 가능.
IDE 자동완성
Q클래스를 기반으로 작성하니까 IDE가 필드 자동완성해줌.
실수 줄고 개발 속도 상승
가독성과 유지보수성
복잡한 동적 쿼리도 자바 코드처럼 명확하게 표현 가능.
조건문 분기 같은 것도 자연스럽게 if문으로 처리 가능.
동적 쿼리 작성 쉬움
쿼리 재활용 쉬움
기본 세팅이 귀찮음
러닝 커브 존재
코드가 길어짐
복잡한 검색/필터 조건이 많은 경우
회원을 이름, 나이, 주소, 등록일 등으로 조합해서 검색해야 할 경우
if != null 조건이 여러 개 붙는 경우
-> JPQL 이나 Criteria으로 하려면 머리 터짐
동적 쿼리가 자주 필요한 경우
검색 조건이 optional인 경우 (검색창 입력 여부에 따라 필터 적용)
BooleanBuilder 또는 where(x, y, z) 구조로 처리 가능
리포지토리 쿼리 재사용하고 싶은 경우
프론트에서 정렬, 페이징 요청 자주 들어올 때
orderBy, offset, limit 등 DSL로 처리 가능
Pageable과 연동도 쉬움
엔티티 간 join이 많은 경우
// JPQL
String jpql = "SELECT m FROM Member m WHERE m.age > :age";
// QueryDSL
QMember m = QMember.member;
List<Member> result = queryFactory
.selectFrom(m)
.where(m.age.gt(20))
.fetch();
Spring 기반 애플리케이션의 인증(Authentication)과 인가(Authorization)를 처리해주는 보안 프레임워크
핵심 기능
- 인증(Authentication)
사용자가 누구인지 확인 (로그인 처리)
ex) 아이디/비번으로 로그인, 소셜 로그인, JWT 토큰 검증 등- 인가(Authorization)
인증된 사용자가 어떤 리소스에 접근 가능한지 검사
ex) 관리자만 /admin, 점주만 /store/** 접근 가능- 보안 관련 필터 자동 적용
CSRF 방어, 세션 고정 방지, 비밀번호 암호화 등
기존 Filter와 Argument Resolver를 사용하던 코드들을 Spring Security로 변경
implementation 'org.springframework.boot:spring-boot-starter-security'
public String resolveToken(HttpServletRequest request) {
String bearer = request.getHeader("Authorization"); // 토큰값 헤더에서 가져오기
if (StringUtils.hasText(bearer) && bearer.startsWith("bearer ")) { // 값이 있고, bearer 로 시작한다면
return bearer.substring(7); // 앞의 "bearer "(7자리)를 때고 토큰값만 리턴 할것
}
return null;
}
public boolean validateToken(String token) {
try {
extractClaims(token); //토큰에서 클레임을 추출하여 유효성 검사
return true; // 예외 없으면 유효한 토큰으로 판단
} catch (Exception e) {
return false; // 예외 발생 시 유효하지 않은 토큰으로 판단
}
}
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws
ServletException, IOException {
String token = jwtUtil.resolveToken(request); // 요청 헤더에서 토큰값 추출
if (token != null & jwtUtil.validateToken(token)) { // 토큰이 존재하고, 유효성 검증메서드를 통과 할 경우
Claims claims = jwtUtil.extractClaims(token); //토큰에서 클레임(정보) 추출
Long userId = Long.parseLong(claims.getSubject()); // 사용자 ID를 클레임에서 꺼냄
String email = claims.get("email", String.class); // 사용자 email를 클레임에서 꺼냄
String role = claims.get("userRole", String.class); // 사용자 role을 클레임에서 꺼냄
String nickname = claims.get("nickname", String.class); //사용자 nickname을 클레임에서 꺼냄
AuthUser authUser = new AuthUser(userId, email, UserRole.of(role), nickname);
List<GrantedAuthority> authorities = List.of(new SimpleGrantedAuthority("ROLE_" + role)); // Spring Security에서 사용할 권한 정보 생성
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(authUser, null, authorities); // 인증 객체 생성 / 비밀번호는 null
SecurityContextHolder.getContext().setAuthentication(authenticationToken); // SecurityContext에 인증 객체 저장
System.out.println(role);
System.out.println(authorities);
System.out.println(authenticationToken);
}
filterChain.doFilter(request, response); // 다음 필터로 요청 전달
}
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable) // CSRF(사이트 간 위조 요청) 보안 비활성화 - JWT 기반에서는 필요 없음
.sessionManagement(session -> session // 세션을 사용하지 않음 - JWT 사용
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth // 요청별 인가 규칙
.requestMatchers("/auth/**").permitAll() // /auth/** 경로는 누구나 접근 가능
.requestMatchers("/admin/**").hasRole("ADMIN") // /admin/** 경로는 어드민 만 접근 가능
.anyRequest().authenticated() // 그 외 모든 요청은 인증 필요
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // JWT 인증 필터 등록 (기본 인증 필터 앞에 위치)
.formLogin(AbstractHttpConfigurer::disable) // 기본 로그인 폼 사용 안함 - JWT 사용
.httpBasic(AbstractHttpConfigurer::disable); // HTTP Basic 인증 사용 안 함 (ID/PW를 매 요청마다 보내는 방식)
//최종 보안 설정을 적용한 SecurityFilterChain 반환
return http.build();
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class PasswordEncoderConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
여기서 문제
기존의 PasswordEncoder 커스텀 클래스의 처리
수정 했으니 해당 클레스를 사용하는 서비스의 import 수정 필요
1. auth.AuthService
2. user.UserService
//삭제
import org.example.expert.config.PasswordEncoder;
//추가
import org.springframework.security.crypto.password.PasswordEncoder;
JwtFilter.javaFilter 인터페이스 상속해서 토큰 처리하던 클래스JwtAuthenticationFilter로 대체됐으므로 삭제FilterConfig.javaJwtFilter를 등록하던 FilterRegistrationBean 설정 클래스SecurityFilterChain에서 관리하므로 필요 없음AuthUserArgumentResolver.java@Auth 어노테이션 + 커스텀 객체 AuthUser 주입 기능 제공@AuthenticationPrincipal로 대체 가능WebConfig.javaAuthUserArgumentResolver를 등록하던 설정 클래스PasswordEncoder.javaPasswordEncoderConfig로 대체됨, 이름 충돌 및 중복 문제 있음원인 : 컨트롤러 단에서 @Auth 사용함 -> JWT토큰으로 변경하며 AuthUserArgumentResolver 삭제
null 발생
해결: @Auth 에서 @AuthenticationPrincipal으로 변경