인증/인가도 구현을 했다. 로그인/회원가입과 같이 ,
JwtConfig
jwt secret key는 환경변수로 설정해놨다.
@Getter
@Component
public class JwtConfig {
@Value("${jwt.secret.key}")
private String secretKey;
private Key key;
@PostConstruct
public void init() {
byte[] bytes = Base64.getDecoder().decode(secretKey);
key = Keys.hmacShaKeyFor(bytes);
}
}
WebSecurityConfig이다.
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
private final JwtUtil jwtUtil;
private final UserDetailsService userDetailsService;
private final AuthenticationConfiguration authenticationConfiguration;
private final UserRepository userRepository;
private final BlockedUserRepository blockedUserRepository;
public WebSecurityConfig(JwtUtil jwtUtil, UserDetailsServiceImpl userDetailsService, AuthenticationConfiguration authenticationConfiguration, UserRepository userRepository,
BlockedUserRepository blockedUserRepository) {
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
this.authenticationConfiguration = authenticationConfiguration;
this.userRepository = userRepository;
// this.customAuthenticationEntryPoint = customAuthenticationEntryPoint;
this.blockedUserRepository = blockedUserRepository;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil, userRepository, blockedUserRepository);
filter.setAuthenticationManager(authenticationManager(authenticationConfiguration));
return filter;
}
@Bean
public JwtAuthorizationFilter jwtAuthorizationFilter() {
return new JwtAuthorizationFilter(jwtUtil, userDetailsService);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf( (csrf) -> csrf.disable());
http.sessionManagement( (sessionManagement) -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http.authorizeHttpRequests( (authorizeHttpRequests) ->
authorizeHttpRequests
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.requestMatchers("/users/login", "/users/signup", "/api/users/refresh", "/users/login/kakao/**").permitAll()
.requestMatchers(HttpMethod.GET, "/orders", "/stores").permitAll()
.requestMatchers("/admin/**").hasAuthority("ADMIN")
.anyRequest().authenticated()
);
// http.exceptionHandling( (exceptionHandling) -> {
// exceptionHandling.authenticationEntryPoint(customAuthenticationEntryPoint);
// });
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
http.addFilterAfter(jwtAuthorizationFilter(), JwtAuthenticationFilter.class);
return http.build();
}
}
TokenResponseDto
/**
* 토큰 응답 DTO
* 액세스 토큰과 리프레시 토큰을 포함하는 데이터 전송 객체
*/
@Getter
public class TokenResponseDto {
private String accessToken;
private String refreshToken;
public TokenResponseDto(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
}
JwtAuthenticationFilter이다.
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final JwtUtil jwtUtil;
private final UserRepository userRepository;
private final BlockedUserRepository blockedUserRepository;
public JwtAuthenticationFilter(JwtUtil jwtUtil, UserRepository userRepository,
BlockedUserRepository blockedUserRepository) {
this.jwtUtil = jwtUtil;
this.userRepository = userRepository;
this.blockedUserRepository = blockedUserRepository;
setFilterProcessesUrl("/users/login");
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("잘못된 http 요청입니다.");
}
try {
LoginRequestDto requestDto = new ObjectMapper().readValue(request.getInputStream(),
LoginRequestDto.class);
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(requestDto.getUserId(),
requestDto.getPassword(), null));
} catch (IOException e) {
throw new RuntimeException(e.getMessage());
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
String userId = ((UserDetailsImpl) authResult.getPrincipal()).getUsername();
String userName = ((UserDetailsImpl) authResult.getPrincipal()).getUser().getUserName();
UserAuth userAuth = ((UserDetailsImpl) authResult.getPrincipal()).getUser().getUserAuth();
Optional<User> user = userRepository.findByUserId(userId);
if (user.isEmpty() || user.get().getUserStatus().equals(UserStatus.WITHDRAWN)) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.setContentType("text/plain;charset=UTF-8");
response.getWriter().write("아이디, 비밀번호를 확인해주세요.");
return;
}
Optional<BlockedUser> blockedUser = blockedUserRepository.findByUserId(user.get().getId());
if (blockedUser.isPresent()) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("text/plain;charset=UTF-8");
response.getWriter().write("해당 사용자는 차단되었습니다.");
return;
}
String accessToken = jwtUtil.generateAccessToken(userId, userName, userAuth);
String refreshToken = jwtUtil.generateRefreshToken(userId, userName, userAuth);
ResponseCookie refreshTokenCookie = jwtUtil.generateRefreshTokenCookie(refreshToken);
user.get().updateRefreshToken(refreshToken);
userRepository.save(user.get());
StatusCommonResponse commonResponse = new StatusCommonResponse(200, "로그인 성공");
response.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);
response.addHeader(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString());
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(new ObjectMapper().writeValueAsString(commonResponse));
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.setContentType("text/plain;charset=UTF-8");
response.getWriter().write("아이디, 비밀번호를 확인해주세요.");
}
}
@Getter
public class UserDetailsImpl implements UserDetails {
private final User user;
public UserDetailsImpl(User user) {
this.user = user;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUserId();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singleton(new SimpleGrantedAuthority(user.getUserAuth().toString()));
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
public UserDetailsServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException {
User user = userRepository.findByUserId(userId).orElseThrow( () -> new UsernameNotFoundException("아이디, 비밀번호를 확인해주세요."));
return new UserDetailsImpl(user);
}
}
JwtUtil
/**
* JwtUtil: JWT 토큰을 생성하고 검증하는 유틸리티 클래스
*/
@Component
public class JwtUtil {
@Value("${jwt.token.expiration}")
private long ACCESS_TOKEN_TIME;
@Value("${jwt.refresh.token.expiration}")
private long REFRESH_TOKEN_TIME;
public static final String BEARER_PREFIX = "Bearer ";
public static final String AUTHORIZATION_KEY = "auth";
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
private final JwtConfig jwtConfig;
private final Key key;
public JwtUtil(JwtConfig jwtConfig) {
this.jwtConfig = jwtConfig;
this.key = jwtConfig.getKey();
}
public String generateToken(String userId, String userName, UserAuth userAuth, long tokenTime) {
Date date = new Date();
return Jwts.builder()
.setSubject(userId)
.claim("userName", userName)
.claim(AUTHORIZATION_KEY, userAuth.toString())
.setExpiration(new Date(date.getTime() + tokenTime))
.setIssuedAt(date)
.signWith(key, signatureAlgorithm)
.compact();
}
public String generateAccessToken(String userId, String userName, UserAuth userAuth) {
return generateToken(userId, userName, userAuth, ACCESS_TOKEN_TIME);
}
public String generateRefreshToken(String userId, String userName, UserAuth userAuth) {
return generateToken(userId, userName, userAuth, REFRESH_TOKEN_TIME);
}
public String generateNewRefreshToken(String userId, String userName, UserAuth userAuth, Date expirationDate) {
Date date = new Date();
return Jwts.builder()
.setSubject(userId)
.claim("userName", userName)
.claim(AUTHORIZATION_KEY, userAuth.toString())
.setExpiration(expirationDate)
.setIssuedAt(date)
.signWith(key, signatureAlgorithm)
.compact();
}
public ResponseCookie generateRefreshTokenCookie(String refreshToken) {
return ResponseCookie.from("refreshToken", refreshToken)
.httpOnly(true)
.maxAge(REFRESH_TOKEN_TIME / 1000)
.path("/")
.sameSite("Strict")
.build();
}
public ResponseCookie generateNewRefreshTokenCookie(String refreshToken, Date expirationDate) {
long maxAge = (expirationDate.getTime() - new Date().getTime()) / 1000;
return ResponseCookie.from("refreshToken", refreshToken)
.httpOnly(true)
.maxAge(maxAge)
.path("/")
.sameSite("Strict")
.build();
}
public String getAccessTokenFromHeader(HttpServletRequest request) {
String authorizationHeader = request.getHeader("Authorization");
if (StringUtils.hasText(authorizationHeader) && authorizationHeader.startsWith(BEARER_PREFIX)) {
return authorizationHeader.substring(7);
}
return null;
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
public void checkTokenExpiration(String token) throws TokenExpiredException {
try {
Claims claims = getClaimsFromToken(token);
Date date = claims.getExpiration();
Date now = new Date();
if (date != null && date.before(now)) {
throw new TokenExpiredException("토큰이 만료되었습니다.");
}
} catch (ExpiredJwtException e) {
throw new TokenExpiredException("토큰이 만료되었습니다.");
}
}
public Claims getClaimsFromToken(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}
}
'TIL' 카테고리의 다른 글
TIL - 2024/06/24 (0) | 2024.06.24 |
---|---|
TIL - 2024/06/21 (0) | 2024.06.21 |
TIL - 2024/06/19 (0) | 2024.06.19 |
TIL - 2024/06/18 (0) | 2024.06.18 |
TIL - 2024/06/17 (0) | 2024.06.17 |