Skip to content
Open
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 @@ -3,15 +3,12 @@ package team.aliens.dms.global.filter
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.HttpMethod
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.util.AntPathMatcher
import org.springframework.web.filter.OncePerRequestFilter
import team.aliens.dms.domain.auth.model.Authority
import team.aliens.dms.domain.auth.model.PassportUser
import team.aliens.dms.global.security.SecurityPaths
import team.aliens.dms.global.security.exception.InvalidTokenException
import team.aliens.dms.global.security.principle.GeneralTeacherDetails
import team.aliens.dms.global.security.principle.HeadTeacherDetails
Expand All @@ -24,34 +21,26 @@ class JwtAuthenticationFilter(
private val jwtParser: JwtParser
) : OncePerRequestFilter() {

private val pathMatcher = AntPathMatcher()

override fun shouldNotFilter(request: HttpServletRequest): Boolean {
val path = request.requestURI
val method = HttpMethod.valueOf(request.method)
return SecurityPaths.PERMIT_ALL_PATHS.any { permitPath ->
pathMatcher.match(permitPath.path, path) &&
(permitPath.method == null || permitPath.method == method)
}
}

override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val token = resolveToken(request)
val user = jwtParser.extractUserInfo(token)
val token = resolveTokenOrNull(request)

if (token != null) {
val user = jwtParser.extractUserInfo(token)

SecurityContextHolder.clearContext()
SecurityContextHolder.getContext().authentication = createAuthentication(user)
SecurityContextHolder.clearContext()
SecurityContextHolder.getContext().authentication = createAuthentication(user)
}

filterChain.doFilter(request, response)
}

private fun resolveToken(request: HttpServletRequest): String {
private fun resolveTokenOrNull(request: HttpServletRequest): String? {
val authorizationHeader = request.getHeader(JwtProperties.HEADER)
?: throw InvalidTokenException
?: return null

if (!authorizationHeader.startsWith(JwtProperties.PREFIX)) {
throw InvalidTokenException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class CustomAuthenticationEntryPoint(
authException: AuthenticationException?
) {

val errorCode = SecurityErrorCode.FORBIDDEN
val errorCode = SecurityErrorCode.INVALID_TOKEN

response?.apply {
contentType = "application/json"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class SecurityConfig(

http
.authorizeHttpRequests { authorize ->
authorize
.requestMatchers("/").permitAll()

Comment on lines +36 to +38

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# SecurityConfig 및 인증 진입점의 통합 테스트 존재 여부를 확인합니다.
fd -a -t f '.*(SecurityConfig|CustomAuthenticationEntryPoint).*Test\.kt$' dms-main || true
rg -n -C 4 'SecurityConfig|CustomAuthenticationEntryPoint|INVALID_TOKEN|MockMvc|WebTestClient|"/"' \
  dms-main -g '*Test.kt'

Repository: team-aliens/DMS-Backend

Length of output: 161


🏁 Script executed:

#!/bin/bash
set -u

printf 'Files matching Security/Endpoint/SecurityConfig:\n'
fd -i 'SecurityConfig|AuthenticationEntryPoint|Jwt|Security|Test\.kt$' dms-main 2>/dev/null | sed -n '1,120p' || true

printf '\nSecurityConfig location/content context:\n'
fd -i 'SecurityConfig\.kt$' dms-main | while read -r f; do
  echo "FILE $f"
  wc -l "$f"
  sed -n '1,140p' "$f"
done

printf '\nCustomAuthenticationEntryPoint matches:\n'
rg -n -C 5 'CustomAuthenticationEntryPoint|AuthenticationEntryPoint|INVALID_TOKEN|permitAll|authorize|jwtAuthenticationFilter' dms-main -g '*.kt' || true

printf '\nTest files mentioning MockMvc/WebTestClient/Jwt/Security:\n'
rg -n -C 4 'MockMvc|WebTestClient|JwtAuthentication|Authorization|INVALID_TOKEN|authorizeHttpRequests|permitAll|CustomAuthenticationEntryPoint' dms-main -g '*Test.kt' -g '*.kt' || true

Repository: team-aliens/DMS-Backend

Length of output: 50379


🏁 Script executed:

#!/bin/bash
set -u

printf 'Test files under main-infrastructure/src/test:\n'
fd -a -t f '%Test\.kt$' dms-main/main-infrastructure/src/test 2>/dev/null | sort | sed -n '1,120p'

printf '\nTests mentioning security authorization or paths:\n'
rg -n -C 3 'authorizeHttpRequests|permitAll|hasAuthority|Authorization|"/"|/students/profile|/students|CustomAuthenticationEntryPoint|INVALID_TOKEN|MockMvc|WebTestClient' \
  dms-main/main-infrastructure/src/test -g '*Test.kt' || true

printf '\nInspect JwtAuthenticationFilterTest:\n'
fd -a -t f 'JwtAuthenticationFilterTest.kt' dms-main | while read -r f; do
  echo "FILE $f"
  wc -l "$f"
  sed -n '1,220p' "$f"
done

Repository: team-aliens/DMS-Backend

Length of output: 9720


보안 체인 통합 테스트를 추가하세요.

SecurityConfig"/" permitAll 변경은 JwtAuthenticationFilterTest로는 검증되지 않습니다. 보안 체인 전체를 실행하는 테스트에서 Authorization 없이 필요한 경로에 요청하면 CustomAuthenticationEntryPoint가 401 및 INVALID_TOKEN 응답을 반환하는지 확인하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@dms-main/main-infrastructure/src/main/kotlin/team/aliens/dms/global/security/SecurityConfig.kt`
around lines 36 - 38, 보안 체인 전체를 실행하는 통합 테스트를 추가해 SecurityConfig의 루트 경로 permitAll
설정을 검증하세요. Authorization 헤더 없이 보호된 경로에 요청하고 CustomAuthenticationEntryPoint를 통해
HTTP 401과 INVALID_TOKEN 응답이 반환되는지 확인하며, JwtAuthenticationFilterTest만 수정하지 마세요.

authorize
// /auth
.requestMatchers(HttpMethod.GET, "/auth/account-id").permitAll()
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -49,45 +49,25 @@ class JwtAuthenticationFilterTest : DescribeSpec({
val filterChain = mockk<FilterChain>(relaxed = true)

describe("doFilter") {
context("permitAll 경로면") {
val request = mockRequest(uri = "/auth/tokens", method = "POST", authorizationHeader = null)

it("JWT 검증 없이 다음 필터로 넘긴다") {
filter.doFilter(request, response, filterChain)

verify(exactly = 0) { jwtParser.extractUserInfo(any()) }
verify(exactly = 1) { filterChain.doFilter(request, response) }
}
}

context("permitAll 경로여도 등록된 메서드가 아니면 (/schools/code)") {
it("GET은 JWT 검증 없이 다음 필터로 넘긴다") {
val request = mockRequest(uri = "/schools/code", method = "GET", authorizationHeader = null)

filter.doFilter(request, response, filterChain)

verify(exactly = 0) { jwtParser.extractUserInfo(any()) }
verify(exactly = 1) { filterChain.doFilter(request, response) }
}
context("Authorization 헤더가 없으면") {
it("경로·메서드와 무관하게 인증을 세우지 않고 다음 필터로 넘긴다") {
forAll(
row("/auth/tokens", "POST"),
row("/schools/code", "GET"),
row("/schools/code", "PATCH"),
row("/students", "GET"),
) { uri: String, method: String ->

it("PATCH는 JWT 검증을 한다") {
val request = mockRequest(uri = "/schools/code", method = "PATCH", authorizationHeader = null)
val request = mockRequest(uri = uri, method = method, authorizationHeader = null)

shouldThrow<InvalidTokenException> {
filter.doFilter(request, response, filterChain)
}
verify(exactly = 0) { filterChain.doFilter(any(), any()) }
}
}

context("인증이 필요한 경로인데 Authorization 헤더가 없으면") {
val request = mockRequest(uri = "/students", authorizationHeader = null)
verify(exactly = 0) { jwtParser.extractUserInfo(any()) }
verify(exactly = 1) { filterChain.doFilter(request, response) }
SecurityContextHolder.getContext().authentication shouldBe null

it("InvalidTokenException을 던지고 다음 필터로 넘어가지 않는다") {
shouldThrow<InvalidTokenException> {
filter.doFilter(request, response, filterChain)
SecurityContextHolder.clearContext()
}
verify(exactly = 0) { filterChain.doFilter(any(), any()) }
}
}

Expand Down
Loading