-
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.
Merge pull request #9 from YAPP-Github/feature/TNT-28
[TNT-28] feat: Authentication Filter ๊ตฌํ
- Loading branch information
Showing
21 changed files
with
937 additions
and
35 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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
<code_scheme name="Naver-coding-convention-v1.2 custom" version="173"> | ||
<option name="LINE_SEPARATOR" value=" "/> | ||
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="5"/> | ||
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="3"/> | ||
<option name="IMPORT_LAYOUT_TABLE"> | ||
<value> | ||
<package name="" withSubpackages="true" static="false"/> | ||
<emptyLine/> | ||
<package name="javax" withSubpackages="true" static="false"/> | ||
<package name="java" withSubpackages="true" static="false"/> | ||
<emptyLine/> | ||
<package name="" withSubpackages="true" static="true"/> | ||
</value> | ||
</option> | ||
<option name="RIGHT_MARGIN" value="120"/> | ||
<option name="ENABLE_JAVADOC_FORMATTING" value="true"/> | ||
<option name="FORMATTER_TAGS_ENABLED" value="true"/> | ||
<JavaCodeStyleSettings> | ||
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99"/> | ||
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="1"/> | ||
<option name="IMPORT_LAYOUT_TABLE"> | ||
<value> | ||
<emptyLine/> | ||
<package name="" withSubpackages="true" static="true"/> | ||
<emptyLine/> | ||
<package name="java" withSubpackages="true" static="false"/> | ||
<emptyLine/> | ||
<package name="javax" withSubpackages="true" static="false"/> | ||
<emptyLine/> | ||
<package name="org" withSubpackages="true" static="false"/> | ||
<emptyLine/> | ||
<package name="net" withSubpackages="true" static="false"/> | ||
<emptyLine/> | ||
<package name="com" withSubpackages="true" static="false"/> | ||
<emptyLine/> | ||
<package name="" withSubpackages="true" static="false"/> | ||
<emptyLine/> | ||
<package name="com.nhncorp" withSubpackages="true" static="false"/> | ||
<emptyLine/> | ||
<package name="com.navercorp" withSubpackages="true" static="false"/> | ||
<emptyLine/> | ||
<package name="com.naver" withSubpackages="true" static="false"/> | ||
<emptyLine/> | ||
</value> | ||
</option> | ||
<option name="ENABLE_JAVADOC_FORMATTING" value="false"/> | ||
</JavaCodeStyleSettings> | ||
<codeStyleSettings language="JAVA"> | ||
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false"/> | ||
<option name="LINE_COMMENT_ADD_SPACE" value="true"/> | ||
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false"/> | ||
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false"/> | ||
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1"/> | ||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1"/> | ||
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="1"/> | ||
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1"/> | ||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false"/> | ||
<option name="SPACE_AFTER_TYPE_CAST" value="false"/> | ||
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true"/> | ||
<option name="CALL_PARAMETERS_WRAP" value="1"/> | ||
<option name="METHOD_PARAMETERS_WRAP" value="1"/> | ||
<option name="EXTENDS_LIST_WRAP" value="1"/> | ||
<option name="THROWS_LIST_WRAP" value="5"/> | ||
<option name="EXTENDS_KEYWORD_WRAP" value="1"/> | ||
<option name="METHOD_CALL_CHAIN_WRAP" value="5"/> | ||
<option name="BINARY_OPERATION_WRAP" value="1"/> | ||
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true"/> | ||
<option name="TERNARY_OPERATION_WRAP" value="1"/> | ||
<option name="ARRAY_INITIALIZER_WRAP" value="1"/> | ||
<indentOptions> | ||
<option name="CONTINUATION_INDENT_SIZE" value="4"/> | ||
<option name="USE_TAB_CHARACTER" value="true"/> | ||
</indentOptions> | ||
</codeStyleSettings> | ||
</code_scheme> |
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
63 changes: 63 additions & 0 deletions
63
src/main/java/com/tnt/application/auth/SessionService.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,63 @@ | ||
package com.tnt.application.auth; | ||
|
||
import static io.micrometer.common.util.StringUtils.*; | ||
import static java.util.Objects.*; | ||
|
||
import java.time.LocalDateTime; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
import org.springframework.data.redis.core.RedisTemplate; | ||
import org.springframework.stereotype.Service; | ||
|
||
import com.tnt.domain.auth.SessionValue; | ||
import com.tnt.global.error.exception.UnauthorizedException; | ||
|
||
import jakarta.servlet.http.HttpServletRequest; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
@Slf4j | ||
@Service | ||
@RequiredArgsConstructor | ||
public class SessionService { | ||
|
||
static final long SESSION_DURATION = 2L * 24 * 60 * 60; // 48์๊ฐ | ||
private static final String AUTHORIZATION_HEADER = "Authorization"; | ||
private static final String SESSION_ID_PREFIX = "SESSION-ID "; | ||
private final RedisTemplate<String, SessionValue> redisTemplate; | ||
|
||
public String authenticate(HttpServletRequest request) { | ||
String authHeader = request.getHeader(AUTHORIZATION_HEADER); | ||
|
||
if (isBlank(authHeader) || !authHeader.startsWith(SESSION_ID_PREFIX)) { | ||
log.error("Authorization ํค๋๊ฐ ์กด์ฌํ์ง ์๊ฑฐ๋ ์ฌ๋ฐ๋ฅด์ง ์์ ํ์์ ๋๋ค."); | ||
|
||
throw new UnauthorizedException("์ธ๊ฐ ์ธ์ ์ด ์กด์ฌํ์ง ์์ต๋๋ค."); | ||
} | ||
|
||
String sessionId = authHeader.substring(SESSION_ID_PREFIX.length()); | ||
|
||
requireNonNull(redisTemplate.opsForValue().get(sessionId), "์ธ์ ์คํ ๋ฆฌ์ง์ ์ธ์ ์ด ์กด์ฌํ์ง ์์ต๋๋ค."); | ||
|
||
return sessionId; | ||
} | ||
|
||
public void createSession(String memberId, HttpServletRequest request) { | ||
SessionValue sessionValue = SessionValue.builder() | ||
.lastAccessTime(LocalDateTime.now()) | ||
.userAgent(request.getHeader("User-Agent")) | ||
.clientIp(request.getRemoteAddr()) | ||
.build(); | ||
|
||
redisTemplate.opsForValue().set( | ||
memberId, | ||
sessionValue, | ||
SESSION_DURATION, | ||
TimeUnit.SECONDS | ||
); | ||
} | ||
|
||
public void removeSession(String sessionId) { | ||
redisTemplate.delete(sessionId); | ||
} | ||
} |
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,15 @@ | ||
package com.tnt.domain.auth; | ||
|
||
import java.time.LocalDateTime; | ||
|
||
import lombok.Builder; | ||
import lombok.Getter; | ||
|
||
@Getter | ||
@Builder | ||
public class SessionValue { | ||
|
||
private LocalDateTime lastAccessTime; | ||
private String userAgent; | ||
private String clientIp; | ||
} |
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,62 @@ | ||
package com.tnt.domain.member; | ||
|
||
import java.time.LocalDateTime; | ||
|
||
import com.tnt.global.entity.BaseTimeEntity; | ||
|
||
import io.hypersistence.utils.hibernate.id.Tsid; | ||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.EnumType; | ||
import jakarta.persistence.Enumerated; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.Table; | ||
import lombok.AccessLevel; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Entity | ||
@Getter | ||
@Table(name = "member") | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
public class Member extends BaseTimeEntity { | ||
|
||
@Id | ||
@Tsid | ||
@Column(name = "id", nullable = false, unique = true) | ||
private Long id; | ||
|
||
@Column(name = "social_id", nullable = false, unique = true) | ||
private String socialId; | ||
|
||
@Column(name = "email", nullable = false, length = 100) | ||
private String email; | ||
|
||
@Column(name = "name", nullable = false, length = 50) | ||
private String name; | ||
|
||
@Column(name = "age", nullable = false) | ||
private int age; | ||
|
||
@Column(name = "profile", nullable = false) | ||
private String profile; | ||
|
||
@Column(name = "deleted_at") | ||
private LocalDateTime deletedAt; | ||
|
||
@Enumerated(EnumType.STRING) | ||
@Column(name = "social_type", nullable = false) | ||
private SocialType socialType; | ||
|
||
@Builder | ||
public Member(Long id, String socialId, String email, String name, int age, SocialType socialType) { | ||
this.id = id; | ||
this.socialId = socialId; | ||
this.email = email; | ||
this.name = name; | ||
this.age = age; | ||
this.profile = ""; | ||
this.socialType = socialType; | ||
} | ||
} |
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,7 @@ | ||
package com.tnt.domain.member; | ||
|
||
public enum SocialType { | ||
KAKAO, | ||
GOOGLE, | ||
APPLE | ||
} |
11 changes: 11 additions & 0 deletions
11
src/main/java/com/tnt/domain/member/repository/MemberRepository.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,11 @@ | ||
package com.tnt.domain.member.repository; | ||
|
||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.stereotype.Repository; | ||
|
||
import com.tnt.domain.member.Member; | ||
|
||
@Repository | ||
public interface MemberRepository extends JpaRepository<Member, Long> { | ||
|
||
} |
106 changes: 106 additions & 0 deletions
106
src/main/java/com/tnt/global/auth/SessionAuthenticationFilter.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,106 @@ | ||
package com.tnt.global.auth; | ||
|
||
import java.io.IOException; | ||
import java.util.List; | ||
|
||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; | ||
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.security.core.userdetails.User; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
import org.springframework.util.AntPathMatcher; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
|
||
import com.tnt.application.auth.SessionService; | ||
|
||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
@Slf4j | ||
@RequiredArgsConstructor | ||
public class SessionAuthenticationFilter extends OncePerRequestFilter { | ||
|
||
private final GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); | ||
private final AntPathMatcher pathMatcher = new AntPathMatcher(); | ||
private final List<String> allowedUris; | ||
private final SessionService sessionService; | ||
|
||
@Override | ||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, | ||
FilterChain filterChain) throws ServletException, IOException { | ||
String requestUri = request.getRequestURI(); | ||
String queryString = request.getQueryString(); | ||
|
||
log.info("๋ค์ด์จ ์์ฒญ - URI: {}, Query: {}, Method: {}", requestUri, queryString != null ? queryString : "์ฟผ๋ฆฌ ์คํธ๋ง ์์", | ||
request.getMethod()); | ||
|
||
if (isAllowedUri(requestUri)) { | ||
log.info("{} ํ์ฉ URI. ์ธ์ ์ ํจ์ฑ ๊ฒ์ฌ ์คํต.", requestUri); | ||
|
||
filterChain.doFilter(request, response); | ||
return; | ||
} | ||
|
||
try { | ||
checkSessionAndAuthentication(request); | ||
} catch (RuntimeException e) { | ||
log.error("์ธ์ฆ ์ฒ๋ฆฌ ์ค ์๋ฌ ๋ฐ์: ", e); | ||
|
||
handleUnauthorizedException(response, e); | ||
return; | ||
} | ||
|
||
filterChain.doFilter(request, response); | ||
} | ||
|
||
private boolean isAllowedUri(String requestUri) { | ||
boolean allowed = false; | ||
|
||
for (String pattern : allowedUris) { | ||
if (pathMatcher.match(pattern, requestUri)) { | ||
allowed = true; | ||
break; | ||
} | ||
} | ||
|
||
log.info("URI {} is {}allowed", requestUri, allowed ? "" : "not "); | ||
|
||
return allowed; | ||
} | ||
|
||
private void checkSessionAndAuthentication(HttpServletRequest request) { | ||
String sessionId = sessionService.authenticate(request); | ||
|
||
saveAuthentication(Long.parseLong(sessionId)); | ||
} | ||
|
||
private void handleUnauthorizedException(HttpServletResponse response, RuntimeException exception) throws | ||
IOException { | ||
log.error("์ธ์ฆ ์คํจ: ", exception); | ||
|
||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); | ||
response.setContentType("application/json;charset=UTF-8"); | ||
response.getWriter().write(exception.getMessage()); | ||
} | ||
|
||
private void saveAuthentication(Long sessionId) { | ||
UserDetails userDetails = User.builder() | ||
.username(String.valueOf(sessionId)) | ||
.password("") | ||
.roles("USER") | ||
.build(); | ||
|
||
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, | ||
authoritiesMapper.mapAuthorities(userDetails.getAuthorities())); | ||
|
||
SecurityContextHolder.getContext().setAuthentication(authentication); | ||
|
||
log.info("์ํ๋ฆฌํฐ ์ปจํ ์คํธ์ ์ธ์ฆ ์ ๋ณด ์ ์ฅ ์๋ฃ - SessionId: {}", sessionId); | ||
} | ||
} |
Oops, something went wrong.