전엔 JWT 인증 방법을 쓰지 않고 UserDetails에 대해서 기록했다.
JWT를 추가한 전체 코드를 기록하려고 한다.
1) build.gradle
필요한 Spring Security, JWT 관련 의존성을 추가한다.
이 단계는 프로젝트의 모든 보안 및 인증 관련 기능을 사용하기 위해 필수적이다.
주요 작업 : spring-boot-starter-security, jjwt 라이브러리 추가
ext {
springSecurityVersion = '6.1.9'
}
dependencies {
implementation "org.springframework.boot:spring-boot-starter-security"
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
}
2) CustomUserDetails
Spring Security에서 사용자의 인증 정보를 담고 있는 클래스이다.
UserDetails 인터페이스를 구현하며, 사용자 객체를 기반으로 사용자명, 비밀번호, 권한 등을 제공한다.
주요 작업 : 사용자 정보 제공 메소드 정의
@Getter
public class CustomUserDetails implements UserDetails {
private final User user; // 이 객체를 통해 사용자 정보에 접근
public CustomUserDetails(User user) {
this.user = user;
}
// 사용자가 가진 권한 반환
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole().name()));
}
// 사용자 비밀번호 반환
@Override
public String getPassword() {
return user.getPassword();
}
// username(이메일) 반환
@Override
public String getUsername() {
return user.getEmail();
}
// 사용자 이름 반환
public String getUserName() {
return user.getUserName();
}
/*
* 계정의 상태를 확인하는 용도로 사용된다.
* 모두 true를 반환하므로,
* 사용자 계정은 만료되지 않고,
* 잠기지 않은 상태이며,
* 자격 증명이 만료되지 않고
* 활성화된 상태로 처리
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
3) CustomUserDetailService
UserDetailsService 인터페이스를 수현하여 사용자 정보를 로드하는 서비스이다.
Spring Security가 인증 시 사용자 정보를 확인할 때 사용된다.
주요 작업 : 사용자 정보 로드, UserDetails 반환
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("찾을 수 없는 이메일: " + email));
return new CustomUserDetails(user);
}
}
loadUserByUsername() 메소드
- 이메일을 사용하여 UserRepository에서 사용자 정보를 조회한다.
- 만약 해당 이메일로 사용자를 찾지 못하면, UsernameNotFoundException을 던져 Spring Security에게 사용자가 없음을 알린다.
- 사용자가 존재하면, 조회된 User 객체를 CustomUserDetails 객체로 변환하여 반환한다.
- 반환된 CustomUserDetails 객체는 Spring Security가 사용자 인증 및 권한 부여를 처리하는 데 사용된다.
4) JwtTokenProvider
JWT 토큰을 생성, 파싱, 검증하는 역할을 담당하는 클래스이다.
인증이 성공하면 이 클래스에서 JWT 토큰을 발급하고 요청이 들어올 때 토큰을 검증한다.
주요 작업 : JWT 토크느 생성, 파싱, 검증 메소드 구현
@Component
public class JwtTokenProvider {
// JWT 토큰 유효 기간 : 24시간
private static final long JWT_EXPIRATION_MS = 86400000L;
// JWT를 서명하기 위한 비밀키 (SignatureAlogrithm.HS512 알고리즘 사용하여 생성된 키)
private final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS512);
// 인증된 username(email)을 가져와 생성된 JWT 토큰 반환
public String generateToken(Authentication authentication) {
String username = authentication.getName();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + JWT_EXPIRATION_MS);
return Jwts.builder()
.setSubject(username) // 이메일
.setIssuedAt(now) // 발행시간
.setExpiration(expiryDate) // 만료시간
.signWith(key) // JWT 토큰
.compact();
}
// JWT 토큰을 가져와 추출된 username(email) 반환
public String getUsernameFromJWT(String token) {
Claims claims = Jwts.parserBuilder() // JWT 토큰을 파싱하여 토큰의 클레임을 가져온다
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
return claims.getSubject(); // 클레임에서 subject 필드(username) 반환
}
// JWT 토큰이 유효한지 여부 반환
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
}
}
5) JwtAuthenticationFilter
JWT 토큰을 사용하여 인증을 처리하는 필터이다.
이 필터는 모든 요청에 대해 JWT 토큰을 확인하고, 유효한 경우 인증 객체를 설정한다.
주요 작업 : JWT 토큰 검증, SecurityContext에 인증 정보 설정
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider tokenProvider;
private final UserDetailsService userDetailsService;
public JwtAuthenticationFilter(JwtTokenProvider tokenProvider, UserDetailsService userDetailsService) {
this.tokenProvider = tokenProvider;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String jwt = getJWTFromRequest(request);
if (jwt != null && tokenProvider.validateToken(jwt)) {
String username = tokenProvider.getUsernameFromJWT(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String getJWTFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
다음 포스팅
https://chaereemee.tistory.com/25
Spring Security 기록 (최종 직전)
먼저 JWT(JSON Web Token)에 대해 알아보자.Spring Security는 여러 가지 인증 방법을 지원하며, JWT는 그 중 하나일 뿐이다.- 그 외 인증 방식 : 세션 기반 인증(상태 저장 방식) 등 JWT의 구조Header (헤더)토
chaereemee.tistory.com
'팀프로젝트_PetHarmony' 카테고리의 다른 글
🌈 임시 비밀번호 발급 _SMTP (0) | 2024.08.14 |
---|---|
🌈 아이디 찾기_SMS API(coolsms) (0) | 2024.08.12 |
🌈 CORS -> CSRF -> JWT (0) | 2024.08.09 |
🌈 Service 코드 파라미터 어떻게 써야할까? (0) | 2024.08.09 |
Spring Security 기록 (1) (0) | 2024.08.09 |