[TIL] 230607 <Spring> WebSecurityConfig, JwtAuthorizationFilter, JwtAuthenticationFilter, JwtUtil, UserDetailsServiceImpl
- -
WebSecurityConfig
`WebSecurityConfig` 클래스는 Spring Boot 애플리케이션이 시작될 때 작동됩니다.
이 클래스는 Spring Security의 보안 설정을 정의하고, 애플리케이션 컨텍스트에 설정을 등록하는 역할을 합니다.
Spring Boot는 애플리케이션 시작 시 모든 `@Configuration` 클래스와 `@Bean` 메서드를 검색하고 초기화하므로,
`WebSecurityConfig`도 이 과정에서 함께 초기화됩니다.
< 작동과정 >
1. 애플리케이션 시작
- Spring Boot 애플리케이션이 시작되면, Spring 프레임워크는 애플리케이션 컨텍스트를 초기화합니다.
- 이 과정에서 애플리케이션의 모든 구성 요소가 스캔되고, 필요한 빈들이 생성 및 초기화됩니다.
2. Configuration 클래스 로드와 Bean 초기화
- Spring 프레임워크는 클래스패스를 스캔하여 모든 @Configuration 애너테이션이 붙은 클래스들을 찾습니다.
- @Configuration 애너테이션이 붙은 WebSecurityConfig 클래스도 이 과정에서 발견됩니다.
3. @EnableWebSecurity 애너테이션
- @EnableWebSecurity 애너테이션이 WebSecurityConfig 클래스에 붙어있으면, Spring Security 설정이 활성화됩니다.
- 이 애너테이션은 Spring Security의 설정 클래스를 활성화하고, 기본 보안 설정을 초기화합니다.
4. Bean 초기화
- Spring 프레임워크는 @Configuration 클래스 내의 @Bean 메서드들을 호출하여 필요한 빈들을 생성하고, 애플리케이션 컨텍스트에 등록합니다.
- WebSecurityConfig 클래스의 다음과 같은 @Bean 메서드들이 호출됩니다 :
- passwordEncoder()
- authenticationManager(AuthenticationConfiguration configuration)
- jwtAuthenticationFilter()
- jwtAuthorizationFilter()
5. HTTP 보안 설정 적용
- WebSecurityConfig 클래스의 securityFilterChain(HttpSecurity http) 메서드가 호출됩니다.
- 이 메서드 내에서 HttpSecurity 객체를 사용하여 HTTP 보안 설정이 정의됩니다
ㄴ 이 설정은 CSRF 보호, 세션 관리, 요청 권한, 로그인 페이지, 필터 추가 등을 포함합니다.
6. SecurityFilterChain 생성
- securityFilterChain(HttpSecurity http) 메서드의 마지막 부분에서 http.build()가 호출됩니다.
- http.build()는 설정된 HttpSecurity 객체를 기반으로 SecurityFilterChain을 생성합니다.
- 생성된 SecurityFilterChain은 애플리케이션 컨텍스트에 등록되어, Spring Security가 요청을 처리할 때 사용됩니다.
이 전체 과정은 Spring Boot 애플리케이션이 시작될 때 자동으로 이루어지며, 개발자가 특별히 호출하지 않아도 됩니다.
Spring Boot와 Spring Security가 연동되어 애플리케이션의 보안 설정을 초기화하고 적용합니다.
package com.sparta.myselectshop.config;
import com.sparta.myselectshop.jwt.JwtUtil;
import com.sparta.myselectshop.security.JwtAuthenticationFilter;
import com.sparta.myselectshop.security.JwtAuthorizationFilter;
import com.sparta.myselectshop.security.UserDetailsServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity // Spring Security 지원을 가능하게 함
@RequiredArgsConstructor
public class WebSecurityConfig {
private final JwtUtil jwtUtil;
private final UserDetailsServiceImpl userDetailsService; //사용자 정보를 로드하는 서비스
private final AuthenticationConfiguration authenticationConfiguration; //인증 구성을 위한 클래스.
@Bean
public PasswordEncoder passwordEncoder() { //비밀번호를 암호화하기 위한 PasswordEncoder를 빈으로 정의
return new BCryptPasswordEncoder(); //여기서는 BCryptPasswordEncoder를 사용
}
@Bean
// 인증 관리자를 빈으로 정의
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager(); //authenticationConfiguration을 이용하여 AuthenticationManager를 반환
}
@Bean
// JWT 인증 필터를 빈으로 정의
public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
// 이 필터는 JWT를 사용하여 인증을 처리하며, 인증 관리자를 설정
JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil);
filter.setAuthenticationManager(authenticationManager(authenticationConfiguration));
return filter;
}
@Bean
// JWT 인가(권한 부여) 필터를 빈으로 정의
public JwtAuthorizationFilter jwtAuthorizationFilter() {
// 이 필터는 JWT를 사용하여 권한 부여를 처리
return new JwtAuthorizationFilter(jwtUtil, userDetailsService);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// CSRF 설정
http.csrf((csrf) -> csrf.disable()); //CSRF 보호를 비활성화
// 기본 설정인 Session 방식은 사용하지 않고 JWT 방식을 사용하기 위한 설정
http.sessionManagement((sessionManagement) ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
// 요청 권한 설정: 특정 URL 패턴에 대한 접근 권한을 설정
http.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // resources 접근 허용 설정
.requestMatchers("/").permitAll() // 메인 페이지 요청 허가
.requestMatchers("/api/user/**").permitAll() // '/api/user/'로 시작하는 요청 모두 접근 허가
.anyRequest().authenticated() // 그 외 모든 요청 인증처리
);
// 폼 로그인 설정: 로그인 페이지를 /api/user/login-page로 설정하고 접근을 허용
http.formLogin((formLogin) ->
formLogin
.loginPage("/api/user/login-page").permitAll()
);
// 필터 관리: JWT 인증 필터와 권한 부여 필터를 필터 체인에 추가
http.addFilterBefore(jwtAuthorizationFilter(), JwtAuthenticationFilter.class);
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
// SecurityFilterChain 객체를 생성하고 반환
return http.build();
}
}
필터 체인 동작 순서
- JwtAuthorizationFilter: 각 요청마다 JWT를 확인하고, 유효한 경우 사용자 정보를 설정합니다. 이 필터는 모든 요청에 대해 가장 먼저 실행됩니다.
- JwtAuthenticationFilter: 로그인 요청을 처리하고 JWT를 생성합니다. 이 필터는 사용자가 로그인할 때 호출됩니다.
- UsernamePasswordAuthenticationFilter: 사용자가 로그인 폼을 통해 인증을 시도할 때 가장 먼저 작동하는 필터입니다. 기본적인 사용자 이름과 비밀번호 인증을 처리합니다.
1) JwtAuthorizationFilter
`JwtAuthorizationFilter` 클래스는 Spring Security의 `OncePerRequestFilter`를 확장하여 JWT를 사용한 인증 및 권한 부여를 처리하는 필터입니다.
이 클래스의 역할은 클라이언트로부터 전송된 JWT 토큰을 가져와서 유효성을 검사하고, 토큰에서 추출한 정보를 사용하여 사용자를 인증하고 인증된 사용자에게 적절한 권한을 부여하는 것입니다.
1. JWT 토큰 검증 : 클라이언트로부터 전송된 HTTP 요청의 헤더에서 JWT 토큰을 추출하고, 해당 토큰의 유효성을 검사합니다.
2. JWT 토큰 파싱 : 유효한 JWT 토큰에서 사용자 정보(Claims)를 추출합니다.
3. 사용자 인증 : 추출한 사용자 정보를 기반으로 Spring Security의 UserDetailsService를 사용하여 사용자를 인증합니다.
4. 권한 부여 : 인증된 사용자에 대한 Authentication 객체를 생성하고, 해당 사용자에게 적절한 권한을 부여합니다.
5. Spring Security에 인증 설정 : 생성된 인증 객체를 Spring Security의 SecurityContext에 설정하여 현재 사용자의 인증 상태를 유지합니다.
package com.sparta.myselectshop.security;
import com.sparta.myselectshop.jwt.JwtUtil;
import io.jsonwebtoken.Claims;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Slf4j(topic = "JWT 검증 및 인가")
public class JwtAuthorizationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserDetailsServiceImpl userDetailsService;
public JwtAuthorizationFilter(JwtUtil jwtUtil, UserDetailsServiceImpl userDetailsService) {
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) throws ServletException, IOException {
// HTTP 요청에서 JWT 토큰 추출
String tokenValue = jwtUtil.getJwtFromHeader(req);
if (StringUtils.hasText(tokenValue)) {
// JWT 토큰 유효성 검증
if (!jwtUtil.validateToken(tokenValue)) {
// 유효하지 않은 토큰이면 에러 로깅 후 종료
log.error("Token Error");
return;
}
// JWT 토큰으로부터 사용자 정보(Claims) 추출
Claims info = jwtUtil.getUserInfoFromToken(tokenValue);
try {
// 사용자 인증 처리
setAuthentication(info.getSubject());
} catch (Exception e) {
// 인증 처리 중 예외 발생 시 에러 로깅 후 종료
log.error(e.getMessage());
return;
}
}
// 다음 필터(또는 서블릿)로 요청 전달
filterChain.doFilter(req, res);
}
// 사용자 인증 처리
public void setAuthentication(String username) {
// 빈 SecurityContext 생성
SecurityContext context = SecurityContextHolder.createEmptyContext();
// 사용자의 인증 객체 생성
Authentication authentication = createAuthentication(username);
// SecurityContext에 인증 객체 설정
context.setAuthentication(authentication);
// SecurityContext를 SecurityContextHolder에 설정
SecurityContextHolder.setContext(context);
}
// 사용자의 인증 객체 생성
private Authentication createAuthentication(String username) {
// 사용자 정보 조회
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// UserDetails를 사용하여 인증 토큰 생성
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
}
(1) JwtUtil
JwtUtil 클래스는 JWT 토큰을 생성, 검증하고 사용자 정보를 추출하는 역할을 합니다.
@PostConstruct 어노테이션이 붙은 init() 메서드에서는 Base64로 인코딩된 Secret Key를 디코딩하여 키 객체를 초기화합니다.
JwtAuthorizationFilter의 doFilterInternal()에서
JwtUtil의 getJwtFromHeader(), validateToken(), getUserInfoFromToken()사용!
1. getJwtFromHeader : HTTP 요청의 헤더에서 JWT 토큰을 추출
2. validateToken : 주어진 JWT 토큰이 유효한지 검증
3. getUserInfoFromToken : JWT 토큰에서 사용자 정보를 추출
package com.sparta.myselectshop.jwt;
import com.sparta.myselectshop.entity.UserRoleEnum;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.security.Key;
import java.util.Base64;
import java.util.Date;
@Slf4j(topic = "JwtUtil")
@Component
public class JwtUtil {
// Header KEY 값
public static final String AUTHORIZATION_HEADER = "Authorization";
// 사용자 권한 값의 KEY
public static final String AUTHORIZATION_KEY = "auth";
// Token 식별자
public static final String BEARER_PREFIX = "Bearer ";
// 토큰 만료시간
private final long TOKEN_TIME = 60 * 60 * 1000L; // 60분
@Value("${jwt.secret.key}") // Base64 Encode 한 SecretKey
private String secretKey;
private Key key;
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 객체 초기화
@PostConstruct
public void init() {
// SecretKey를 Base64 디코딩하여 키 초기화
byte[] bytes = Base64.getDecoder().decode(secretKey);
key = Keys.hmacShaKeyFor(bytes);
}
// JwtAuthenticationFilter의 successfulAuthentication()에서 사용됨 (로그인 성공 시)
// 토큰 생성
public String createToken(String username, UserRoleEnum role) {
Date date = new Date();
return BEARER_PREFIX +
Jwts.builder()
.setSubject(username) // 사용자 식별자값(ID)
.claim(AUTHORIZATION_KEY, role) // 사용자 권한
.setExpiration(new Date(date.getTime() + TOKEN_TIME)) // 만료 시간
.setIssuedAt(date) // 발급일
.signWith(key, signatureAlgorithm) // 암호화 알고리즘
.compact(); // 토큰 생성
}
// JwtAuthorizationFilter의 doFilterInternal()에서 사용됨
// HTTP 요청 헤더에서 JWT 추출 메서드
public String getJwtFromHeader(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
// JWT 토큰이 존재하고 "Bearer " 접두사로 시작할 경우 토큰 반환
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) {
return bearerToken.substring(7); // "Bearer " 접두사 제거 후 토큰 반환
}
return null; // 토큰 없을 경우 null 반환
}
// JwtAuthorizationFilter의 doFilterInternal()에서 사용됨
// 토큰 검증
public boolean validateToken(String token) {
try {
// 주어진 토큰의 유효성을 검증하고 유효할 경우 true 반환
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (SecurityException | MalformedJwtException | SignatureException e) {
log.error("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.");
} catch (ExpiredJwtException e) {
log.error("Expired JWT token, 만료된 JWT token 입니다.");
} catch (UnsupportedJwtException e) {
log.error("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.");
} catch (IllegalArgumentException e) {
log.error("JWT claims is empty, 잘못된 JWT 토큰 입니다.");
}
return false; // 유효하지 않은 경우 false 반환
}
// JwtAuthorizationFilter의 doFilterInternal()에서 사용됨
// 토큰에서 사용자 정보 추출 메서드
public Claims getUserInfoFromToken(String token) {
// 주어진 토큰에서 클레임 정보(사용자 정보)를 파싱하여 반환
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}
}
(2) UserDetailsServiceImpl
- UserDetails
- 검증된 UserDetails는 UsernamePasswordAuthenticationToken 타입의 Authentication를 만들 때 사용되며 해당 인증객체는 SecurityContextHolder에 세팅됨
- Custom하여 사용 가능
▼ ▼ ▼ ▼ ▼
- UserDetailsImpl
이 클래스는 Spring Security의 `UserDetails` 인터페이스를 구현하여 사용자 정보를 제공하는 역할을 합니다.
사용자의 이름, 비밀번호, 권한 등의 정보를 제공합니다.
또한 사용자 계정의 상태를 나타내는 여러 메서드를 구현하여 사용자 계정의 유효성을 확인합니다.
package com.sparta.myselectshop.security;
import com.sparta.myselectshop.entity.User;
import com.sparta.myselectshop.entity.UserRoleEnum;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
public class UserDetailsImpl implements UserDetails {
private final User user;
public UserDetailsImpl(User user) {
this.user = user;
}
public User getUser() {
return user;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 사용자의 역할을 가져옴
UserRoleEnum role = user.getRole();
// 사용자의 역할에서 권한(Authority)을 가져옴
String authority = role.getAuthority();
// Spring Security의 SimpleGrantedAuthority를 사용하여 권한 객체를 생성
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authority);
// 권한 객체를 담을 Collection을 생성
Collection<GrantedAuthority> authorities = new ArrayList<>();
// 생성한 권한 객체를 Collection에 추가
authorities.add(simpleGrantedAuthority);
// 생성한 권한 목록을 반환
return authorities;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
- UserDetailsService
- UserDetailsService는 username/password 인증방식을 사용할 때 사용자를 조회하고 검증한 후 UserDetails를 반환
- Custom하여 Bean으로 등록 후 사용 가능
▼ ▼ ▼ ▼ ▼ - UserDetailsServiceImpl
이 클래스는 Spring Security의 `UserDetailsService`를 구현하여 사용자 정보를 로드하는 역할을 합니다.
loadUserByUsername 메서드에서는 주어진 사용자 이름에 해당하는 사용자 정보를 데이터베이스에서 찾아서
UserDetailsImpl 객체로 래핑하여 반환합니다.
사용자를 찾을 수 없는 경우 UsernameNotFoundException을 발생시킵니다.
package com.sparta.myselectshop.security;
import com.sparta.myselectshop.entity.User;
import com.sparta.myselectshop.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 주어진 사용자 이름으로 사용자를 데이터베이스에서 찾음
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("Not Found " + username));
return new UserDetailsImpl(user);
}
}
2) JwtAuthenticationFilter
이 클래스는 사용자의 로그인 인증을 처리하고 JWT 토큰을 생성하는 필터 클래스입니다.
UsernamePasswordAuthenticationFilter를 확장하여 사용자의 로그인 시도를 처리합니다.
- 로그인 요청 처리: /api/user/login URL로 들어오는 로그인 요청을 처리합니다.
- 인증 시도: 클라이언트로부터 받은 사용자명과 비밀번호를 사용하여 인증을 시도합니다.
- 인증 성공 시 JWT 생성: 인증이 성공하면 JWT 토큰을 생성하고 이를 응답 헤더에 추가합니다.
- 인증 실패 시 처리: 인증이 실패하면 401 상태 코드를 반환합니다.
package com.sparta.myselectshop.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.myselectshop.dto.LoginRequestDto;
import com.sparta.myselectshop.entity.UserRoleEnum;
import com.sparta.myselectshop.jwt.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.io.IOException;
@Slf4j(topic = "로그인 및 JWT 생성")
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final JwtUtil jwtUtil;
public JwtAuthenticationFilter(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
// 로그인 처리 URL 설정
setFilterProcessesUrl("/api/user/login");
}
// 로그인 시도 처리
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
// 요청에서 로그인 정보를 읽어와 DTO에 매핑
LoginRequestDto requestDto = new ObjectMapper().readValue(request.getInputStream(), LoginRequestDto.class);
// 인증 매니저를 통해 사용자 인증 시도
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
requestDto.getUsername(), // 사용자명
requestDto.getPassword(), // 비밀번호
null // 권한 목록은 null로 전달
)
);
} catch (IOException e) {
// 예외 발생 시 로그 출력 및 RuntimeException 던지기
log.error(e.getMessage());
throw new RuntimeException(e.getMessage());
}
}
// 로그인 성공 시 처리
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) {
// 사용자명과 역할 정보를 가져옴
String username = ((UserDetailsImpl) authResult.getPrincipal()).getUsername();
UserRoleEnum role = ((UserDetailsImpl) authResult.getPrincipal()).getUser().getRole();
// JWT 토큰 생성
String token = jwtUtil.createToken(username, role);
// 응답 헤더에 JWT 토큰 추가
response.addHeader(JwtUtil.AUTHORIZATION_HEADER, token);
}
// 로그인 실패 시 처리
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
response.setStatus(401);
}
}
(1) JwtUtil
JwtUtil 클래스는 JWT 토큰을 생성, 검증하고 사용자 정보를 추출하는 역할을 합니다.
@PostConstruct 어노테이션이 붙은 init() 메서드에서는 Base64로 인코딩된 Secret Key를 디코딩하여 키 객체를 초기화합니다.
JwtAuthenticationFilter의 successfulAuthentication()에서 JwtUtil의 createToken()사용
ㄴ 토큰 생성(createToken) : 사용자 이름과 권한 정보를 받아 JWT 토큰을 생성합니다.
package com.sparta.myselectshop.jwt;
import com.sparta.myselectshop.entity.UserRoleEnum;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.security.Key;
import java.util.Base64;
import java.util.Date;
@Slf4j(topic = "JwtUtil")
@Component
public class JwtUtil {
// Header KEY 값
public static final String AUTHORIZATION_HEADER = "Authorization";
// 사용자 권한 값의 KEY
public static final String AUTHORIZATION_KEY = "auth";
// Token 식별자
public static final String BEARER_PREFIX = "Bearer ";
// 토큰 만료시간
private final long TOKEN_TIME = 60 * 60 * 1000L; // 60분
@Value("${jwt.secret.key}") // Base64 Encode 한 SecretKey
private String secretKey;
private Key key;
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 객체 초기화
@PostConstruct
public void init() {
// SecretKey를 Base64 디코딩하여 키 초기화
byte[] bytes = Base64.getDecoder().decode(secretKey);
key = Keys.hmacShaKeyFor(bytes);
}
// JwtAuthenticationFilter의 successfulAuthentication()에서 사용됨 (로그인 성공 시)
// 토큰 생성
public String createToken(String username, UserRoleEnum role) {
Date date = new Date();
return BEARER_PREFIX +
Jwts.builder()
.setSubject(username) // 사용자 식별자값(ID)
.claim(AUTHORIZATION_KEY, role) // 사용자 권한
.setExpiration(new Date(date.getTime() + TOKEN_TIME)) // 만료 시간
.setIssuedAt(date) // 발급일
.signWith(key, signatureAlgorithm) // 암호화 알고리즘
.compact(); // 토큰 생성
}
// JwtAuthorizationFilter의 doFilterInternal()에서 사용됨
// HTTP 요청 헤더에서 JWT 추출 메서드
public String getJwtFromHeader(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
// JWT 토큰이 존재하고 "Bearer " 접두사로 시작할 경우 토큰 반환
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) {
return bearerToken.substring(7); // "Bearer " 접두사 제거 후 토큰 반환
}
return null; // 토큰 없을 경우 null 반환
}
// JwtAuthorizationFilter의 doFilterInternal()에서 사용됨
// 토큰 검증
public boolean validateToken(String token) {
try {
// 주어진 토큰의 유효성을 검증하고 유효할 경우 true 반환
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (SecurityException | MalformedJwtException | SignatureException e) {
log.error("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.");
} catch (ExpiredJwtException e) {
log.error("Expired JWT token, 만료된 JWT token 입니다.");
} catch (UnsupportedJwtException e) {
log.error("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.");
} catch (IllegalArgumentException e) {
log.error("JWT claims is empty, 잘못된 JWT 토큰 입니다.");
}
return false; // 유효하지 않은 경우 false 반환
}
// JwtAuthorizationFilter의 doFilterInternal()에서 사용됨
// 토큰에서 사용자 정보 추출 메서드
public Claims getUserInfoFromToken(String token) {
// 주어진 토큰에서 클레임 정보(사용자 정보)를 파싱하여 반환
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}
}
'TIL' 카테고리의 다른 글
[TIL] 230611 <Spring> 팀프로젝트 14조 - E거 I4아이가 KPT 회고 (2) | 2024.06.11 |
---|---|
[TIL] 230610 <Spring> Swagger 연결 시 HTTP ERROR 403 해결 (0) | 2024.06.10 |
[TIL] 230605 <Spring> @EnableJpaAuditing, @EnableScheduling, @Scheduled, Refresh Token 자동 관리 및 Spring Security (1) | 2024.06.05 |
[TIL] 230604 <Spring> Spring Security 동작원리 (1) | 2024.06.04 |
[TIL] 230603 <Spring> 회원가입, 로그인 기능이 있는 투두앱 백엔드 서버 만들기 (3) (1) | 2024.06.03 |
당신이 좋아할만한 콘텐츠
-
[TIL] 230611 <Spring> 팀프로젝트 14조 - E거 I4아이가 KPT 회고 2024.06.11
-
[TIL] 230610 <Spring> Swagger 연결 시 HTTP ERROR 403 해결 2024.06.10
-
[TIL] 230605 <Spring> @EnableJpaAuditing, @EnableScheduling, @Scheduled, Refresh Token 자동 관리 및 Spring Security 2024.06.05
-
[TIL] 230604 <Spring> Spring Security 동작원리 2024.06.04
소중한 공감 감사합니다