Skip to content

Commit

Permalink
♻️refactor: 회원가입, 로그인, 토큰 재발급 수정 (#53)
Browse files Browse the repository at this point in the history
  • Loading branch information
xxoznge authored Jun 5, 2024
1 parent 628e14e commit e03eda7
Show file tree
Hide file tree
Showing 16 changed files with 97 additions and 136 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import org.springframework.web.method.support.ModelAndViewContainer;

import com.ddabong.ddabongdotchiBE.domain.security.entity.User;
import com.ddabong.ddabongdotchiBE.domain.security.jwt.exception.SecurityCustomException;
import com.ddabong.ddabongdotchiBE.domain.security.jwt.exception.TokenErrorCode;
import com.ddabong.ddabongdotchiBE.domain.security.jwt.userdetails.CustomUserDetails;
import com.ddabong.ddabongdotchiBE.domain.security.service.UserQueryService;

Expand All @@ -35,6 +37,12 @@ public boolean supportsParameter(MethodParameter parameter) {
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
Object userDetails = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return userQueryService.findByUserName(((CustomUserDetails)userDetails).getUsername());

try {
return userQueryService.findByUserName(((CustomUserDetails)userDetails).getUsername());
} catch (ClassCastException e) {
// 로그아웃된 토큰
throw new SecurityCustomException(TokenErrorCode.UNAUTHORIZED);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ public static CorsConfigurationSource apiConfigurationSource() {
ArrayList<String> allowedHttpMethods = new ArrayList<>();
allowedHttpMethods.add("GET");
allowedHttpMethods.add("POST");
allowedHttpMethods.add("PUT");
allowedHttpMethods.add("DELETE");

configuration.setAllowedOrigins(allowedOriginPatterns);
configuration.setAllowedMethods(allowedHttpMethods);
configuration.setAllowCredentials(true); // 내 서버가 응답을 할 때 응답해준 json을 자바스크립트에서 처리할 수 있게 할지를 설정
configuration.setAllowedOrigins(allowedOriginPatterns); // 응답 허용할 uri
configuration.setAllowedMethods(allowedHttpMethods); // 응답 허용할 HTTP method
configuration.addAllowedHeader("*"); // 응답 허용할 header

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
source.registerCorsConfiguration("/**", configuration); // /** -> 모든 요청들 configuration 설정을 따르도록 등록

return source;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ public class RedisConfig {
@Value("${redis.port}")
private int redisPort;

// Redis CrudRepository 설정
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisHost, redisPort);
}

// RedisTemplate 설정
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@
public class SecurityConfig {

private final String[] swaggerUrls = {"/swagger-ui/**", "/v3/**"};
private final String[] authUrls = {"/", "/api/v1/user/join/**", "/api/v1/user/login/**", "/api/v1/user/reissue/**",
"/api/v1/s3/**"};
private final String[] authUrls = {"/", "/api/v1/user/join/**", "/api/v1/user/login/**", "/api/v1/redis/**"};
private final String[] allowedUrls = Stream.concat(Arrays.stream(swaggerUrls), Arrays.stream(authUrls))
.toArray(String[]::new);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,35 @@

@Configuration
public class SwaggerConfig {
// url : http://localhost:8080/swagger-ui/index.html#/
private static final String SECURITY_SCHEME_NAME = "bearerAuth";

// url: http://localhost:8080/swagger-ui/index.html#/
@Bean
public OpenAPI api() {
public OpenAPI getOpenApi() {
Server server = new Server().url("/");

return new OpenAPI()
.addSecurityItem(new SecurityRequirement().addList(SECURITY_SCHEME_NAME))
.components(authSetting())
.info(getSwaggerInfo())
.addServersItem(server);
.components(authSetting())
.addServersItem(server)
.addSecurityItem(new SecurityRequirement().addList("access-token"));
}

private Info getSwaggerInfo() {
License license = new License();
license.setName("Ddabongdotchi");
license.setName("{Application}");

return new Info()
.title("Ddabongdotchi API Document")
.description("Ddabongdotchi의 API 문서 입니다.")
.title("{Application} API Document")
.description("This is {Application}'s API document.")
.version("v0.0.1")
.license(license);
}

private Components authSetting() {

return new Components()
.addSecuritySchemes(SECURITY_SCHEME_NAME,
.addSecuritySchemes(
"access-token",
new SecurityScheme()
.name(SECURITY_SCHEME_NAME)
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {
log.warn("Access Denied: ", accessDeniedException);
log.warn("[*] Access Denied: ", accessDeniedException);

HttpResponseUtil.setErrorResponse(response,
HttpStatus.FORBIDDEN,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public void commence(HttpServletRequest request, HttpServletResponse response,
HttpStatus httpStatus;
ApiResponse<String> errorResponse;

log.error(">>>>>> AuthenticationException: ", authException);
log.error("[*] AuthenticationException: ", authException);
httpStatus = HttpStatus.UNAUTHORIZED;
errorResponse = ApiResponse.onFailure(
TokenErrorCode.INVALID_TOKEN.getCode(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1 @@
// package umc.springumc.security.jwt.exception;
//
// import com.fasterxml.jackson.databind.ObjectMapper;
//
// import jakarta.servlet.http.HttpServletResponse;
// import lombok.extern.slf4j.Slf4j;
// import umc.springumc.apiPayload.global.ApiResponse;
// import umc.springumc.apiPayload.global.BaseErrorCode;
//
// @Slf4j
// public class JwtFilterExceptionHandler {
// public JwtFilterExceptionHandler(HttpServletResponse response, BaseErrorCode error) {
// response.setStatus(Integer.parseInt(error.getCode()));
// response.setContentType("application/json");
// response.setCharacterEncoding("UTF-8");
// try {
// String json = new ObjectMapper().writeValueAsString(
// ApiResponse.onFailure(error.getCode(), error.getMessage()));
// response.getWriter().write(json);
// } catch (Exception e) {
// log.warn(e.getMessage());
// }
// }
// }
package com.ddabong.ddabongdotchiBE.domain.security.jwt.exception;
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,7 @@ protected void unsuccessfulAuthentication(
response, HttpStatus.UNAUTHORIZED,
ApiResponse.onFailure(
HttpStatus.BAD_REQUEST.name(),
errorMessage,
null
errorMessage
)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ public void logout(HttpServletRequest request, HttpServletResponse response, Aut
// throw new SecurityCustomException(TokenErrorCode.INVALID_TOKEN);
// }

redisUtil.save(
redisUtil.saveAsValue(
accessToken,
"logout",
jwtUtil.getExpTime(accessToken),
TimeUnit.MILLISECONDS
);

redisUtil.delete(
jwtUtil.getUsername(accessToken)
jwtUtil.getUsername(accessToken) + "_refresh_token"
);

} catch (ExpiredJwtException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.io.IOException;

import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
Expand Down Expand Up @@ -49,10 +50,7 @@ protected void doFilterInternal(
// logout 처리된 accessToken
if (redisUtil.get(accessToken) != null && redisUtil.get(accessToken).equals("logout")) {
log.info("[*] Logout accessToken");
// TODO InsufficientAuthenticationException 예외 처리
log.info("==================");
filterChain.doFilter(request, response);
log.info("==================");
return;
}

Expand All @@ -61,14 +59,14 @@ protected void doFilterInternal(
filterChain.doFilter(request, response);
} catch (ExpiredJwtException e) {
log.warn("[*] case : accessToken Expired");
throw new SecurityCustomException(TokenErrorCode.TOKEN_EXPIRED);
} catch (InsufficientAuthenticationException e) {
log.warn("[*] case : FORBIDDEN");
throw new SecurityCustomException(TokenErrorCode.FORBIDDEN);
}
}

private void authenticateAccessToken(String accessToken) {

if (jwtUtil.isExpired(accessToken))
throw new SecurityCustomException(TokenErrorCode.INVALID_TOKEN);

CustomUserDetails userDetails = new CustomUserDetails(
jwtUtil.getUsername(accessToken),
null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ protected void doFilterInternal(
try {
filterChain.doFilter(request, response);
} catch (SecurityCustomException e) {
log.warn(">>>>> SecurityCustomException : ", e);
log.warn("[*] SecurityCustomException : ", e);
BaseErrorCode errorCode = e.getErrorCode();
ApiResponse<String> errorResponse = ApiResponse.onFailure(
errorCode.getCode(),
Expand All @@ -41,7 +41,7 @@ protected void doFilterInternal(
errorResponse
);
} catch (Exception e) {
log.error(">>>>> Exception : ", e);
log.error("[*] Exception : ", e);
ApiResponse<String> errorResponse = ApiResponse.onFailure(
TokenErrorCode.INTERNAL_SECURITY_ERROR.getCode(),
TokenErrorCode.INTERNAL_SECURITY_ERROR.getMessage(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@

public class CustomUserDetails implements UserDetails {

private final String username;
private final String email;
private final String password;
private final String isStaff;

public CustomUserDetails(String username, String password, String isStaff) {
this.username = username;
public CustomUserDetails(String email, String password, String isStaff) {
this.email = email;
this.password = password;
this.isStaff = isStaff;
}
Expand All @@ -42,26 +42,35 @@ public String getPassword() {

@Override
public String getUsername() {
return username;
return email;
}

// 계정 만료되지 않음
@Override
public boolean isAccountNonExpired() {
return true;
}

// 계정 잠겨있지 않음
@Override
public boolean isAccountNonLocked() {
return true;
}

// 비밀번호 변경 기간
@Override
public boolean isCredentialsNonExpired() {
return true;
}

// 1년 간 로그인 없을 시 "휴면"으로
// (현재 시간 - loginDate) > 1년 -> return false; 로 설정
// 활성화 되어있음
@Override
public boolean isEnabled() {
// 사이트에서 1년 동안 회원이 로그인을 안하면 -> 휴면 계정으로 전환하는 로직이 있다고 치자
// user entity 의 field 에 "Timestamp loginDate"를 하나 만들어주고
// (현재 시간 - loginDate) > 1년 -> return false; 로 설정
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,4 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx

return new CustomUserDetails(user.getUsername(), user.getPassword(), user.getRoleType().toString());
}

public User userDetailsToUser(UserDetails userDetails) {
return userRepository.findByUsername(userDetails.getUsername())
.orElseThrow(() -> new UserExceptionHandler(UserErrorCode.USER_NOT_FOUND));
}
}
Loading

0 comments on commit e03eda7

Please sign in to comment.