-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b29864e
commit d7b64d9
Showing
6 changed files
with
317 additions
and
0 deletions.
There are no files selected for viewing
21 changes: 21 additions & 0 deletions
21
src/main/java/com/smunity/petition/domain/account/jwt/exception/SecurityCustomException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package com.smunity.petition.domain.account.jwt.exception; | ||
|
||
import com.smunity.petition.global.common.code.BaseErrorCode; | ||
import com.smunity.petition.global.common.exception.GeneralException; | ||
import lombok.Getter; | ||
|
||
@Getter | ||
public class SecurityCustomException extends GeneralException { | ||
|
||
private final Throwable cause; | ||
|
||
public SecurityCustomException(BaseErrorCode errorCode) { | ||
super(errorCode); | ||
this.cause = null; | ||
} | ||
|
||
public SecurityCustomException(BaseErrorCode errorCode, Throwable cause) { | ||
super(errorCode); | ||
this.cause = cause; | ||
} | ||
} |
30 changes: 30 additions & 0 deletions
30
src/main/java/com/smunity/petition/domain/account/jwt/exception/TokenErrorCode.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package com.smunity.petition.domain.account.jwt.exception; | ||
|
||
import com.smunity.petition.global.common.ApiResponse; | ||
import com.smunity.petition.global.common.code.BaseErrorCode; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import org.springframework.http.HttpStatus; | ||
|
||
@Getter | ||
@AllArgsConstructor | ||
public enum TokenErrorCode implements BaseErrorCode { | ||
|
||
INVALID_TOKEN(HttpStatus.BAD_REQUEST, "SEC4001", "잘못된 형식의 토큰입니다."), | ||
UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "SEC4010", "인증이 필요합니다."), | ||
TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "SEC4011", "토큰이 만료되었습니다."), | ||
TOKEN_SIGNATURE_ERROR(HttpStatus.UNAUTHORIZED, "SEC4012", "토큰이 위조되었거나 손상되었습니다."), | ||
FORBIDDEN(HttpStatus.FORBIDDEN, "SEC4030", "권한이 없습니다."), | ||
TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "SEC4041", "토큰이 존재하지 않습니다."), | ||
INTERNAL_SECURITY_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "SEC5000", "인증 처리 중 서버 에러가 발생했습니다."), | ||
INTERNAL_TOKEN_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "SEC5001", "토큰 처리 중 서버 에러가 발생했습니다."); | ||
|
||
private final HttpStatus httpStatus; | ||
private final String code; | ||
private final String message; | ||
|
||
@Override | ||
public ApiResponse<Void> getErrorResponse() { | ||
return ApiResponse.onFailure(code, message); | ||
} | ||
} |
127 changes: 127 additions & 0 deletions
127
src/main/java/com/smunity/petition/domain/account/jwt/filter/CustomLoginFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package com.smunity.petition.domain.account.jwt.filter; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.smunity.petition.domain.account.jwt.dto.JwtDto; | ||
import com.smunity.petition.domain.account.jwt.userdetails.CustomUserDetails; | ||
import com.smunity.petition.domain.account.jwt.util.HttpResponseUtil; | ||
import com.smunity.petition.domain.account.jwt.util.JwtUtil; | ||
import com.smunity.petition.global.common.ApiResponse; | ||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.NonNull; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.security.authentication.*; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.core.AuthenticationException; | ||
import org.springframework.security.core.userdetails.UsernameNotFoundException; | ||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.IOException; | ||
import java.util.Map; | ||
|
||
@Slf4j | ||
@RequiredArgsConstructor | ||
public class CustomLoginFilter extends UsernamePasswordAuthenticationFilter { | ||
|
||
private final AuthenticationManager authenticationManager; | ||
private final JwtUtil jwtUtil; | ||
|
||
@Override | ||
public Authentication attemptAuthentication( | ||
@NonNull HttpServletRequest request, | ||
@NonNull HttpServletResponse response | ||
) throws AuthenticationException { | ||
log.info("[*] Login Filter"); | ||
|
||
Map<String, Object> requestBody; | ||
try { | ||
requestBody = getBody(request); | ||
} catch (IOException e) { | ||
throw new AuthenticationServiceException("Error of request body."); | ||
} | ||
|
||
log.info("[*] Request Body : " + requestBody); | ||
|
||
String username = (String) requestBody.get("username"); | ||
String password = (String) requestBody.get("password"); | ||
|
||
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, password, | ||
null); | ||
|
||
return authenticationManager.authenticate(authToken); | ||
} | ||
|
||
@Override | ||
protected void successfulAuthentication( | ||
@NonNull HttpServletRequest request, | ||
@NonNull HttpServletResponse response, | ||
@NonNull FilterChain chain, | ||
@NonNull Authentication authentication) throws IOException { | ||
log.info("[*] Login Success"); | ||
|
||
CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal(); | ||
|
||
log.info("[*] Login with " + customUserDetails.getUsername()); | ||
|
||
JwtDto jwtDto = new JwtDto( | ||
jwtUtil.createJwtAccessToken(customUserDetails), | ||
jwtUtil.createJwtRefreshToken(customUserDetails) | ||
); | ||
|
||
HttpResponseUtil.setSuccessResponse(response, HttpStatus.CREATED, jwtDto); | ||
} | ||
|
||
@Override | ||
protected void unsuccessfulAuthentication( | ||
@NonNull HttpServletRequest request, | ||
@NonNull HttpServletResponse response, | ||
@NonNull AuthenticationException failed) throws IOException { | ||
|
||
logger.info("[*] Login Fail"); | ||
|
||
String errorMessage; | ||
if (failed instanceof BadCredentialsException) { | ||
errorMessage = "Bad credentials"; | ||
} else if (failed instanceof LockedException) { | ||
errorMessage = "Account is locked"; | ||
} else if (failed instanceof DisabledException) { | ||
errorMessage = "Account is disabled"; | ||
} else if (failed instanceof UsernameNotFoundException) { | ||
errorMessage = "Account not found"; | ||
} else if (failed instanceof AuthenticationServiceException) { | ||
errorMessage = "Error occurred while parsing request body"; | ||
} else { | ||
errorMessage = "Authentication failed"; | ||
} | ||
HttpResponseUtil.setErrorResponse( | ||
response, HttpStatus.UNAUTHORIZED, | ||
ApiResponse.onFailure( | ||
HttpStatus.BAD_REQUEST.name(), | ||
errorMessage, | ||
null | ||
) | ||
); | ||
} | ||
|
||
private Map<String, Object> getBody(HttpServletRequest request) throws IOException { | ||
|
||
StringBuilder stringBuilder = new StringBuilder(); | ||
String line; | ||
|
||
try (BufferedReader bufferedReader = request.getReader()) { | ||
while ((line = bufferedReader.readLine()) != null) { | ||
stringBuilder.append(line); | ||
} | ||
} | ||
|
||
String requestBody = stringBuilder.toString(); | ||
ObjectMapper objectMapper = new ObjectMapper(); | ||
|
||
return objectMapper.readValue(requestBody, Map.class); | ||
} | ||
} | ||
|
65 changes: 65 additions & 0 deletions
65
src/main/java/com/smunity/petition/domain/account/jwt/userdetails/CustomUserDetails.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package com.smunity.petition.domain.account.jwt.userdetails; | ||
|
||
import com.smunity.petition.domain.account.entity.User; | ||
import org.springframework.security.core.GrantedAuthority; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
|
||
import java.util.Collection; | ||
|
||
public class CustomUserDetails implements UserDetails { | ||
|
||
private final String username; | ||
private final String password; | ||
private final Boolean isStaff; | ||
|
||
public CustomUserDetails(User user) { | ||
username = user.getUserName(); | ||
password = user.getPassword(); | ||
isStaff = user.getIsStaff(); | ||
} | ||
|
||
public CustomUserDetails(String username, String password, Boolean isStaff) { | ||
this.username = username; | ||
this.password = password; | ||
this.isStaff = isStaff; | ||
} | ||
|
||
public Boolean getStaff() { | ||
return isStaff; | ||
} | ||
|
||
@Override | ||
public Collection<? extends GrantedAuthority> getAuthorities() { | ||
return null; | ||
} | ||
|
||
@Override | ||
public String getPassword() { | ||
return password; | ||
} | ||
|
||
@Override | ||
public String getUsername() { | ||
return username; | ||
} | ||
|
||
@Override | ||
public boolean isAccountNonExpired() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public boolean isAccountNonLocked() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public boolean isCredentialsNonExpired() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public boolean isEnabled() { | ||
return true; | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
...in/java/com/smunity/petition/domain/account/jwt/userdetails/CustomUserDetailsService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package com.smunity.petition.domain.account.jwt.userdetails; | ||
|
||
import com.smunity.petition.domain.account.entity.User; | ||
import com.smunity.petition.domain.account.exception.AccountsExceptionHandler; | ||
import com.smunity.petition.domain.account.repository.user.UserRepository; | ||
import com.smunity.petition.global.common.code.status.ErrorCode; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
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 | ||
@Slf4j | ||
public class CustomUserDetailsService implements UserDetailsService { | ||
|
||
private final UserRepository userRepository; | ||
|
||
@Override | ||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { | ||
User user = userRepository.findByUserName(username) | ||
.orElseThrow(() -> new AccountsExceptionHandler(ErrorCode.USER_NOT_FOUND)); | ||
|
||
log.info("[*] User found : " + user.getUserName()); | ||
|
||
return new CustomUserDetails(user); | ||
} | ||
|
||
public User userDetailsToUser(UserDetails userDetails) { | ||
return userRepository.findByUserName(userDetails.getUsername()) | ||
.orElseThrow(() -> new AccountsExceptionHandler(ErrorCode.USER_NOT_FOUND)); | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
src/main/java/com/smunity/petition/domain/account/jwt/util/HttpResponseUtil.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package com.smunity.petition.domain.account.jwt.util; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.smunity.petition.global.common.ApiResponse; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.AccessLevel; | ||
import lombok.NoArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.MediaType; | ||
|
||
import java.io.IOException; | ||
|
||
@Slf4j | ||
@NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
public class HttpResponseUtil { | ||
|
||
private static final ObjectMapper objectMapper = new ObjectMapper(); | ||
|
||
public static void setSuccessResponse(HttpServletResponse response, HttpStatus httpStatus, Object body) throws | ||
IOException { | ||
log.info("[*] Success Response"); | ||
String responseBody = objectMapper.writeValueAsString(ApiResponse.onSuccess(body)); | ||
response.setContentType(MediaType.APPLICATION_JSON_VALUE); | ||
response.setStatus(httpStatus.value()); | ||
response.setCharacterEncoding("UTF-8"); | ||
response.getWriter().write(responseBody); | ||
} | ||
|
||
public static void setErrorResponse(HttpServletResponse response, HttpStatus httpStatus, Object body) throws | ||
IOException { | ||
log.info("[*] Failure Response"); | ||
response.setContentType(MediaType.APPLICATION_JSON_VALUE); | ||
response.setStatus(httpStatus.value()); | ||
response.setCharacterEncoding("UTF-8"); | ||
objectMapper.writeValue(response.getOutputStream(), body); | ||
} | ||
|
||
} |