Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;

@RestController
@RequestMapping("auth")
@RequiredArgsConstructor
Expand All @@ -32,4 +34,10 @@ public CustomResponse signUp(@RequestBody SignUpRequestDto signUpRequestDto) {
boolean status = authService.signUp(signUpRequestDto);
return new StatusResponse(status);
}

@PostMapping("/sign-out")
public CustomResponse signOut(Principal principal) {
boolean status = authService.signOut(principal.getName());
return new StatusResponse(status);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import com.example.solidconnection.auth.dto.SignUpRequestDto;
import com.example.solidconnection.config.token.TokenService;
import com.example.solidconnection.config.token.TokenType;
import com.example.solidconnection.config.token.TokenValidator;
import com.example.solidconnection.country.CountryRepository;
import com.example.solidconnection.country.InterestedCountyRepository;
Expand All @@ -15,19 +16,23 @@
import com.example.solidconnection.type.RegionCode;
import com.example.solidconnection.type.Role;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.example.solidconnection.custom.exception.ErrorCode.*;

@Service
@RequiredArgsConstructor
public class AuthService {

private final RedisTemplate<String, String> redisTemplate;
private final TokenValidator tokenValidator;
private final TokenService tokenService;
private final SiteUserRepository siteUserRepository;
Expand All @@ -50,6 +55,16 @@ public boolean signUp(SignUpRequestDto signUpRequestDto) {
return true;
}

public boolean signOut(String email){
redisTemplate.opsForValue().set(
TokenType.REFRESH.getPrefix() + email,
"signOut",
TokenType.REFRESH.getExpireTime(),
TimeUnit.MILLISECONDS
);
return true;
}

private void validateUserNotDuplicated(SignUpRequestDto signUpRequestDto){
String email = tokenService.getEmail(signUpRequestDto.getKakaoOauthToken());
if(siteUserRepository.existsByEmail(email)){
Expand Down Expand Up @@ -113,5 +128,4 @@ private void saveInterestedRegion(SignUpRequestDto signUpRequestDto, SiteUser sa
.collect(Collectors.toList());
interestedRegionRepository.saveAll(interestedRegions);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.solidconnection.config.security;

import com.example.solidconnection.custom.exception.CustomException;
import com.example.solidconnection.custom.response.ErrorResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
Expand All @@ -21,11 +22,20 @@ public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
AuthenticationException authException) throws IOException {
ErrorResponse errorResponse = new ErrorResponse(AUTHENTICATION_FAILED, authException.getMessage());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
}

public void customCommence(HttpServletRequest request, HttpServletResponse response,
CustomException customException) throws IOException {
ErrorResponse errorResponse = new ErrorResponse(customException);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.example.solidconnection.config.token.TokenService;
import com.example.solidconnection.config.token.TokenValidator;
import com.example.solidconnection.custom.exception.CustomException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -48,6 +49,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
filterChain.doFilter(request, response); // 다음 필터로 요청과 응답 전달
} catch (AuthenticationException e) {
jwtAuthenticationEntryPoint.commence(request, response, e);
} catch (CustomException e){
jwtAuthenticationEntryPoint.customCommence(request, response, e);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.example.solidconnection.config.token;

import com.example.solidconnection.custom.exception.CustomException;
import com.example.solidconnection.custom.userdetails.CustomUserDetailsService;
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
Expand All @@ -16,13 +16,12 @@
import java.util.Date;
import java.util.concurrent.TimeUnit;

import static com.example.solidconnection.custom.exception.ErrorCode.EMAIL_NOT_FOUND;

@Component
@RequiredArgsConstructor
public class TokenService {
private final RedisTemplate<String, String> redisTemplate;
private final SiteUserRepository siteUserRepository;
private final CustomUserDetailsService customUserDetailsService;

@Value("${jwt.secret}")
private String secretKey;
Expand Down Expand Up @@ -50,8 +49,7 @@ public void saveToken(String token, TokenType tokenType) {

public Authentication getAuthentication(String token) {
String email = getClaim(token).getSubject();
UserDetails userDetails = (UserDetails) siteUserRepository.findByEmail(email)
.orElseThrow(() -> new CustomException(EMAIL_NOT_FOUND));
UserDetails userDetails = customUserDetailsService.loadUserByUsername(email);
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,24 @@ public class TokenValidator {
public void validateAccessToken(String token) {
validateTokenNotEmpty(token);
validateTokenNotExpired(token, TokenType.ACCESS);
validateNotSignOut(token);
validateRefreshToken(token);
// TODO : validateNotLogOut 함수 생성 및 추가
}

private void validateRefreshToken(String token) {
String email = getClaim(token).getSubject();
if (redisTemplate.opsForValue().get(TokenType.REFRESH.getPrefix() + email) != null) {
if (redisTemplate.opsForValue().get(TokenType.REFRESH.getPrefix() + email) == null) {
throw new CustomException(REFRESH_TOKEN_EXPIRED);
}
}

private void validateNotSignOut(String token) {
String email = getClaim(token).getSubject();
if ("signOut".equals(redisTemplate.opsForValue().get(TokenType.REFRESH.getPrefix() + email))) {
throw new CustomException(USER_ALREADY_SIGN_OUT);
}
}

public void validateKakaoToken(String token) {
validateTokenNotEmpty(token);
validateTokenNotExpired(token, TokenType.KAKAO_OAUTH);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
@Getter
@AllArgsConstructor
public enum ErrorCode {
USER_ALREADY_SIGN_OUT(HttpStatus.UNAUTHORIZED.value(), "로그아웃 되었습니다."),
USER_ALREADY_EXISTED(HttpStatus.CONFLICT.value(), "이미 존재하는 회원입니다."),
JSON_PARSING_FAILED(HttpStatus.BAD_REQUEST.value(), "JSON 파싱 에러"),
INVALID_REGION_NAME(HttpStatus.BAD_REQUEST.value(), "지원하지 않는 지역명입니다."),
Expand All @@ -15,7 +16,7 @@ public enum ErrorCode {
NICKNAME_ALREADY_EXISTED(HttpStatus.CONFLICT.value(), "이미 존재하는 닉네임입니다."),
INVALID_TOKEN(HttpStatus.UNAUTHORIZED.value(), "토큰이 필요한 경로에 빈 토큰으로 요청했습니다."),
AUTHENTICATION_FAILED(HttpStatus.UNAUTHORIZED.value(), "인증이 필요한 접근입니다."),
EMAIL_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "회원 정보를 찾을 수 없습니다."),
USER_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "회원 정보를 찾을 수 없습니다."),
INVALID_KAKAO_AUTH_CODE(HttpStatus.BAD_REQUEST.value(),"사용할 수 없는 카카오 인증 코드입니다. 카카오 인증 코드는 일회용이며, 인증 만료 시간은 10분입니다."),
KAKAO_USER_INFO_FAIL(HttpStatus.BAD_REQUEST.value(),"카카오 사용자 정보 조회에 실패했습니다."),
ACCESS_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED.value(),"액세스 토큰이 만료되었습니다. 재발급 api를 호출해주세요."),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.example.solidconnection.custom.userdetails;

import com.example.solidconnection.entity.SiteUser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

public class CustomUserDetails implements UserDetails {
private final SiteUser siteUser;

public CustomUserDetails(SiteUser siteUser) {
this.siteUser = siteUser;
}

public String getEmail(){
return siteUser.getEmail();
}

@Override
public String getUsername() {
return siteUser.getEmail();
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}

@Override
public String getPassword() {
return null;
}

@Override
public boolean isAccountNonExpired() {
return false;
}

@Override
public boolean isAccountNonLocked() {
return false;
}

@Override
public boolean isCredentialsNonExpired() {
return false;
}

@Override
public boolean isEnabled() {
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.example.solidconnection.custom.userdetails;

import com.example.solidconnection.custom.exception.CustomException;
import com.example.solidconnection.entity.SiteUser;
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;

import static com.example.solidconnection.custom.exception.ErrorCode.USER_NOT_FOUND;

@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
private final SiteUserRepository siteUserRepository;

@Override
public UserDetails loadUserByUsername(String username) {
SiteUser siteUser = siteUserRepository.findByEmail(username)
.orElseThrow(() -> new CustomException(USER_NOT_FOUND, username));
return new CustomUserDetails(siteUser);
}
}