-
Notifications
You must be signed in to change notification settings - Fork 0
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
12b21bf
commit 9e41d9a
Showing
22 changed files
with
567 additions
and
14 deletions.
There are no files selected for viewing
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 |
---|---|---|
|
@@ -35,3 +35,5 @@ out/ | |
|
||
### VS Code ### | ||
.vscode/ | ||
|
||
src/main/resources/application.yml |
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
26 changes: 26 additions & 0 deletions
26
practice/src/main/java/org/sopt/practice/auth/PrincipalHandler.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,26 @@ | ||
package org.sopt.practice.auth; | ||
|
||
import org.sopt.practice.common.dto.ErrorMessage; | ||
import org.sopt.practice.exception.UnauthorizedException; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
public class PrincipalHandler { | ||
|
||
private static final String ANONYMOUS_USER = "anonymousUser"; | ||
|
||
public Long getUserIdFromPrincipal() { | ||
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); | ||
isPrincipalNull(principal); | ||
return Long.valueOf(principal.toString()); | ||
} | ||
|
||
public void isPrincipalNull( | ||
final Object principal | ||
) { | ||
if (principal.toString().equals(ANONYMOUS_USER)) { | ||
throw new UnauthorizedException(ErrorMessage.JWT_UNAUTHORIZED_EXCEPTION); | ||
} | ||
} | ||
} |
47 changes: 47 additions & 0 deletions
47
practice/src/main/java/org/sopt/practice/auth/SecurityConfig.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,47 @@ | ||
package org.sopt.practice.auth; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.sopt.practice.auth.filter.CustomAccessDeniedHandler; | ||
import org.sopt.practice.auth.filter.CustomJwtAuthenticationEntryPoint; | ||
import org.sopt.practice.auth.filter.JwtAuthenticationFilter; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; | ||
import org.springframework.security.web.SecurityFilterChain; | ||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | ||
import org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer; | ||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; | ||
|
||
@Configuration | ||
@RequiredArgsConstructor | ||
@EnableWebSecurity //web Security를 사용할 수 있게 | ||
public class SecurityConfig { | ||
private final JwtAuthenticationFilter jwtAuthenticationFilter; | ||
private final CustomJwtAuthenticationEntryPoint customJwtAuthenticationEntryPoint; | ||
private final CustomAccessDeniedHandler customAccessDeniedHandler; | ||
|
||
|
||
private static final String[] AUTH_WHITE_LIST = {"/api/v1/auth/sign-up, /api/v1/auth/sign-in"}; | ||
|
||
@Bean | ||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { | ||
http.csrf(AbstractHttpConfigurer::disable) | ||
.formLogin(AbstractHttpConfigurer::disable) | ||
.requestCache(RequestCacheConfigurer::disable) | ||
.httpBasic(AbstractHttpConfigurer::disable) | ||
.exceptionHandling(exception -> | ||
{ | ||
exception.authenticationEntryPoint(customJwtAuthenticationEntryPoint); | ||
exception.accessDeniedHandler(customAccessDeniedHandler); | ||
}); | ||
|
||
http.authorizeHttpRequests(auth -> { | ||
auth.requestMatchers(AUTH_WHITE_LIST).permitAll(); | ||
auth.anyRequest().authenticated(); | ||
}) | ||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); | ||
|
||
return http.build(); | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
practice/src/main/java/org/sopt/practice/auth/UserAuthentication.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,18 @@ | ||
package org.sopt.practice.auth; | ||
|
||
|
||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
import org.springframework.security.core.GrantedAuthority; | ||
|
||
import java.util.Collection; | ||
|
||
public class UserAuthentication extends UsernamePasswordAuthenticationToken { | ||
|
||
public UserAuthentication(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { | ||
super(principal, credentials, authorities); | ||
} | ||
|
||
public static UserAuthentication createUserAuthentication(Long userId) { | ||
return new UserAuthentication(userId, null, null); | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
practice/src/main/java/org/sopt/practice/auth/filter/CustomAccessDeniedHandler.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,22 @@ | ||
package org.sopt.practice.auth.filter; | ||
|
||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import org.springframework.security.access.AccessDeniedException; | ||
import org.springframework.security.web.access.AccessDeniedHandler; | ||
import org.springframework.stereotype.Component; | ||
|
||
import java.io.IOException; | ||
|
||
@Component | ||
public class CustomAccessDeniedHandler implements AccessDeniedHandler { | ||
@Override | ||
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { | ||
setResponse(response); | ||
} | ||
|
||
private void setResponse(HttpServletResponse response) { | ||
response.setStatus(HttpServletResponse.SC_FORBIDDEN); | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
practice/src/main/java/org/sopt/practice/auth/filter/CustomJwtAuthenticationEntryPoint.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,36 @@ | ||
package org.sopt.practice.auth.filter; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import org.sopt.practice.common.dto.ErrorMessage; | ||
import org.sopt.practice.common.dto.ErrorResponse; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.security.core.AuthenticationException; | ||
import org.springframework.security.web.AuthenticationEntryPoint; | ||
import org.springframework.stereotype.Component; | ||
|
||
import java.io.IOException; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
public class CustomJwtAuthenticationEntryPoint implements AuthenticationEntryPoint { | ||
|
||
private final ObjectMapper objectMapper; | ||
|
||
@Override | ||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { | ||
setResponse(response); | ||
} | ||
|
||
private void setResponse(HttpServletResponse response) throws IOException { | ||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); | ||
response.setContentType(MediaType.APPLICATION_JSON_VALUE); | ||
response.setCharacterEncoding("UTF-8"); | ||
response.getWriter() | ||
.write(objectMapper.writeValueAsString( | ||
ErrorResponse.of(ErrorMessage.JWT_UNAUTHORIZED_EXCEPTION.getStatus(), | ||
ErrorMessage.JWT_UNAUTHORIZED_EXCEPTION.getMessage()))); | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
practice/src/main/java/org/sopt/practice/auth/filter/JwtAuthenticationFilter.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,46 @@ | ||
package org.sopt.practice.auth.filter; | ||
|
||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.NonNull; | ||
import lombok.RequiredArgsConstructor; | ||
import org.sopt.practice.auth.UserAuthentication; | ||
import org.sopt.practice.common.dto.ErrorMessage; | ||
import org.sopt.practice.common.jwt.JwtTokenProvider; | ||
import org.sopt.practice.exception.UnauthorizedException; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
|
||
import java.io.IOException; | ||
|
||
import static org.sopt.practice.common.jwt.JwtValidationType.VALID_JWT; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
public class JwtAuthenticationFilter extends OncePerRequestFilter { | ||
|
||
private final JwtTokenProvider jwtTokenProvider; | ||
|
||
@Override | ||
protected void doFilterInternal(@NonNull HttpServletRequest request, | ||
@NonNull HttpServletResponse response, | ||
@NonNull FilterChain filterChain) throws ServletException, IOException { | ||
try { | ||
final String token = jwtTokenProvider.getJwtFromRequest(request); | ||
if (jwtTokenProvider.validateToken(token) == VALID_JWT) { | ||
Long memberId = jwtTokenProvider.getUserFromJwt(token); | ||
UserAuthentication authentication = UserAuthentication.createUserAuthentication(memberId); | ||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); | ||
SecurityContextHolder.getContext().setAuthentication(authentication); | ||
} | ||
} catch (Exception exception) { | ||
throw new UnauthorizedException(ErrorMessage.JWT_UNAUTHORIZED_EXCEPTION); | ||
} | ||
filterChain.doFilter(request, response); | ||
} | ||
|
||
} |
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
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
93 changes: 93 additions & 0 deletions
93
practice/src/main/java/org/sopt/practice/common/jwt/JwtTokenProvider.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,93 @@ | ||
package org.sopt.practice.common.jwt; | ||
|
||
import io.jsonwebtoken.*; | ||
import io.jsonwebtoken.security.Keys; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.util.StringUtils; | ||
|
||
import javax.crypto.SecretKey; | ||
import java.util.Base64; | ||
import java.util.Date; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
public class JwtTokenProvider { | ||
|
||
private static final String USER_ID = "userId"; | ||
|
||
private static final Long ACCESS_TOKEN_EXPIRATION_TIME = 24 * 60 * 60 * 1000L; | ||
private static final Long REFRESH_TOKEN_EXPIRATION_TIME = 24 * 60 * 60 * 1000L * 14; | ||
|
||
@Value("${jwt.secret}") | ||
private String JWT_SECRET; | ||
|
||
|
||
public String issueAccessToken(final Authentication authentication) { | ||
return generateToken(authentication, ACCESS_TOKEN_EXPIRATION_TIME); | ||
} | ||
|
||
public String issueRefreshToken(final Authentication authentication) { | ||
return generateToken(authentication, REFRESH_TOKEN_EXPIRATION_TIME); | ||
} | ||
|
||
|
||
public String generateToken(Authentication authentication, Long tokenExpirationTime) { | ||
final Date now = new Date(); | ||
final Claims claims = Jwts.claims() | ||
.setIssuedAt(now) | ||
.setExpiration(new Date(now.getTime() + tokenExpirationTime)); // 만료 시간 | ||
|
||
claims.put(USER_ID, authentication.getPrincipal()); | ||
|
||
return Jwts.builder() | ||
.setHeaderParam(Header.TYPE, Header.JWT_TYPE) // Header | ||
.setClaims(claims) // Claim | ||
.signWith(getSigningKey()) // Signature | ||
.compact(); | ||
} | ||
|
||
private SecretKey getSigningKey() { | ||
String encodedKey = Base64.getEncoder().encodeToString(JWT_SECRET.getBytes()); //SecretKey 통해 서명 생성 | ||
return Keys.hmacShaKeyFor(encodedKey.getBytes()); //일반적으로 HMAC (Hash-based Message Authentication Code) 알고리즘 사용 | ||
} | ||
|
||
public JwtValidationType validateToken(String token) { | ||
try { | ||
final Claims claims = getBody(token); | ||
return JwtValidationType.VALID_JWT; | ||
} catch (MalformedJwtException ex) { | ||
return JwtValidationType.INVALID_JWT_TOKEN; | ||
} catch (ExpiredJwtException ex) { | ||
return JwtValidationType.EXPIRED_JWT_TOKEN; | ||
} catch (UnsupportedJwtException ex) { | ||
return JwtValidationType.UNSUPPORTED_JWT_TOKEN; | ||
} catch (IllegalArgumentException ex) { | ||
return JwtValidationType.EMPTY_JWT; | ||
} | ||
} | ||
|
||
private Claims getBody(final String token) { | ||
return Jwts.parserBuilder() | ||
.setSigningKey(getSigningKey()) | ||
.build() | ||
.parseClaimsJws(token) | ||
.getBody(); | ||
} | ||
|
||
public Long getUserFromJwt(String token) { | ||
Claims claims = getBody(token); | ||
return Long.valueOf(claims.get(USER_ID).toString()); | ||
} | ||
|
||
public String getJwtFromRequest(HttpServletRequest request) { | ||
String bearerToken = request.getHeader("Authorization"); | ||
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { | ||
return bearerToken.substring("Bearer ".length()); | ||
} | ||
return null; | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
practice/src/main/java/org/sopt/practice/common/jwt/JwtValidationType.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,10 @@ | ||
package org.sopt.practice.common.jwt; | ||
|
||
public enum JwtValidationType { | ||
VALID_JWT, // 유효한 JWT | ||
INVALID_JWT_SIGNATURE, // 유효하지 않은 서명 | ||
INVALID_JWT_TOKEN, // 유효하지 않은 토큰 | ||
EXPIRED_JWT_TOKEN, // 만료된 토큰 | ||
UNSUPPORTED_JWT_TOKEN, // 지원하지 않는 형식의 토큰 | ||
EMPTY_JWT // 빈 JWT | ||
} |
Oops, something went wrong.