Skip to content

Commit

Permalink
Merge pull request #182 from MaeumGaGym/BAC-516-Authentication에서-Auth…
Browse files Browse the repository at this point in the history
…Details-제거

PR :: Authentication에서 UserDetails 제거
  • Loading branch information
maeumgagym-ci-robot-app[bot] authored Mar 21, 2024
2 parents 079d7c0 + ccf1825 commit 3c57fef
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 133 deletions.
Original file line number Diff line number Diff line change
@@ -1,39 +1,37 @@
package com.info.maeumgagym.auth

import com.info.maeumgagym.auth.port.out.ReadCurrentUserPort
import com.info.maeumgagym.common.exception.AuthenticationException
import com.info.maeumgagym.security.principle.CustomUserDetails
import com.info.maeumgagym.security.jwt.AuthenticationProvider
import com.info.maeumgagym.security.jwt.JwtFilter
import com.info.maeumgagym.user.model.User
import com.info.maeumgagym.user.port.out.ReadUserPort
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.stereotype.Component

@Component
internal class ReadCurrentUserAdapter(
private val readUserPort: ReadUserPort
private val readUserPort: ReadUserPort,
private val authenticationProvider: AuthenticationProvider
) : ReadCurrentUserPort {

override fun readCurrentUser(): User {
// User를 찾기 위한 정보가 담겨 있는 Authentication 로드
val authentication = SecurityContextHolder.getContext().authentication

// jwt filter에서 생성한 authDetail를 context holder에서 불러옴
val authDetails = authentication.principal as CustomUserDetails

// Lazy Loading으로 Nullable인 User를 확인하고, null인 경우 User를 Load 및 입력
if (authDetails.getUser() == null) {
authDetails.fillUser(
readUserPort.readByOAuthId(authDetails.username)
// authDetails에 담긴 username = oauthId는 로직상 무조건 유저가 존재해야하므로 AuthenticationException throw
?: throw AuthenticationException(401, "User Not Found In ReadCurrentUserPort")
)
JwtFilter.run {
// Lazy Loading으로 Nullable인 User를 확인
if (this.authenticatedUser?.get() == null ||
this.authenticatedUser?.get()?.oauthId != authentication!!.principal
) {
// null인 경우 User를 Load 및 SecurityContext, authenticatedUser에 입력
SecurityContextHolder.getContext().authentication =
authenticationProvider.getAuthentication(
authentication.principal as String
)
}
}

// Loading된 User를 Authentication에도 반영
SecurityContextHolder.getContext().authentication =
UsernamePasswordAuthenticationToken(authDetails, null, authDetails.authorities)

// User 반환
return authDetails.getUser()!!
return JwtFilter.authenticatedUser!!.get()
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.info.maeumgagym.security.config

import com.info.maeumgagym.user.model.Role
import org.springframework.http.HttpMethod
import org.springframework.security.config.annotation.SecurityConfigurerAdapter
import org.springframework.security.config.annotation.web.builders.HttpSecurity
Expand All @@ -12,41 +13,42 @@ import org.springframework.web.cors.CorsUtils
class RequestPermitConfig : SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>() {

internal companion object {
val permittedURI = mapOf<HttpMethod, String>(
Pair(HttpMethod.POST, "/**/signup"),
Pair(HttpMethod.GET, "/**/login"),
Pair(HttpMethod.PUT, "/**/recovery"),
Pair(HttpMethod.GET, "/auth/nickname/*"),
Pair(HttpMethod.GET, "/auth/re-issue"),
Pair(HttpMethod.GET, "/public/csrf"),
Pair(HttpMethod.GET, "/actuator/health")
val permittedURIs: Map<String, HttpMethod> = mapOf(
Pair("/**/signup", HttpMethod.POST),
Pair("/**/login", HttpMethod.GET),
Pair("/**/recovery", HttpMethod.PUT),
Pair("/auth/nickname/*", HttpMethod.GET),
Pair("/auth/re-issue", HttpMethod.GET),
Pair("/public/csrf", HttpMethod.GET),
Pair("/actuator/health", HttpMethod.GET)
)

val needAdminRoleURI = mapOf<HttpMethod, String>(
Pair(HttpMethod.GET, "/report")
val needAdminRoleURIs: Map<String, HttpMethod> = mapOf(
Pair("/report", HttpMethod.GET)
)
}

override fun configure(builder: HttpSecurity) {
builder.authorizeRequests().run {
requestMatchers(CorsUtils::isCorsRequest).permitAll()
permittedURIConfigure()
needAdminRoleURIConfigure()
anyRequest().authenticated()
}
builder.authorizeRequests()
.requestMatchers(CorsUtils::isCorsRequest).permitAll()
.permittedURIConfigure()
.needAdminRoleURIConfigure()
.anyRequest().authenticated()
}

private fun ExpressionUrlAuthorizationConfigurer<HttpSecurity>
.ExpressionInterceptUrlRegistry.permittedURIConfigure() {
permittedURI.forEach {
antMatchers(it.key, it.value).permitAll()
.ExpressionInterceptUrlRegistry.permittedURIConfigure() =
this.apply {
permittedURIs.forEach {
antMatchers(it.value, it.key).permitAll()
}
}
}

private fun ExpressionUrlAuthorizationConfigurer<HttpSecurity>
.ExpressionInterceptUrlRegistry.needAdminRoleURIConfigure() {
permittedURI.forEach {
antMatchers(it.key, it.value).permitAll()
.ExpressionInterceptUrlRegistry.needAdminRoleURIConfigure() =
this.apply {
needAdminRoleURIs.forEach {
antMatchers(it.value, it.key).hasRole(Role.ADMIN.name)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.info.maeumgagym.security.jwt

import com.info.maeumgagym.security.config.RequestPermitConfig
import com.info.maeumgagym.security.jwt.env.JwtProperties
import com.info.maeumgagym.user.model.User
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.stereotype.Component
import org.springframework.util.AntPathMatcher
Expand All @@ -24,37 +25,47 @@ class JwtFilter(
private val jwtProperties: JwtProperties
) : OncePerRequestFilter() {

companion object {
// 현재 요청에서 인증한 User를 전역적으로 저장, 필요 여부에 따라 Nullable
internal var authenticatedUser: ThreadLocal<User>? = null
}

private var antPathMatcher: AntPathMatcher = AntPathMatcher()

override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
// 헤더에 토큰이 존재하는지 확인
val header = request.getHeader(jwtProperties.header)
try {
// 헤더에 토큰이 존재하는지 확인
val header = request.getHeader(jwtProperties.header)

if (header != null) {
// 토큰이 유효한지 확인, 유효하다면 ->
jwtResolver(header)?.let {
// security context holder에 Authentication 저장
SecurityContextHolder.getContext().authentication =
if (needRole(request)) { // Role 인증이 필요하다면 User Eager Loading
authenticationProvider.getAuthentication(it)
} else { // 필요하지 않다면 User Lazy Loading
authenticationProvider.getEmptyAuthentication(it)
}
if (header != null) {
// 토큰이 유효한지 확인, 유효하다면 ->
jwtResolver(header)?.let {
// security context holder에 Authentication 저장
SecurityContextHolder.getContext().authentication =
if (needRole(request)) { // Role 인증이 필요하다면 User Loading
authenticationProvider.getAuthentication(it)
} else { // 필요하지 않다면 User Lazy Loading
authenticationProvider.getEmptyAuthentication(it)
}
}
}
}

// 다음 필터로 넘기기
filterChain.doFilter(request, response)
// 다음 필터로 넘기기
filterChain.doFilter(request, response)
} finally {
// Filter가 종료되면 User 정보를 초기화
authenticatedUser = null
}
}

private fun needRole(request: HttpServletRequest): Boolean {
RequestPermitConfig.needAdminRoleURI.forEach {
if (request.method == it.key.name &&
antPathMatcher.match(it.value, request.requestURI)
RequestPermitConfig.needAdminRoleURIs.forEach {
if (request.method == it.value.name &&
antPathMatcher.match(it.key, request.requestURI)
) {
return true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package com.info.maeumgagym.security.jwt.impl

import com.info.maeumgagym.common.exception.CriticalException
import com.info.maeumgagym.security.jwt.AuthenticationProvider
import com.info.maeumgagym.security.principle.CustomUserDetails
import com.info.maeumgagym.security.jwt.JwtFilter
import com.info.maeumgagym.user.port.out.ReadUserPort
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.stereotype.Component

/**
Expand All @@ -16,27 +16,28 @@ import org.springframework.stereotype.Component
*/
@Component
class AuthenticationProviderImpl(
private val userDetailsService: UserDetailsService,
private val readUserPort: ReadUserPort
) : AuthenticationProvider {

override fun getAuthentication(subject: String): UsernamePasswordAuthenticationToken {
// UserDetails 생성
val authDetails = userDetailsService.loadUserByUsername(subject) as CustomUserDetails

val user = readUserPort.readByOAuthId(subject)
?: throw CriticalException(500, "User Not Found In AuthenticationProvider")

authDetails.fillUser(user)

// Authentication발급
return UsernamePasswordAuthenticationToken(authDetails, null, authDetails.authorities)
// User가 필요한 경우 불러와 전역적으로 저장
JwtFilter.authenticatedUser = ThreadLocal.withInitial {
readUserPort.readByOAuthId(subject)
?: throw CriticalException(500, "User Not Found In AuthenticationProvider")
}

// Authentication에 subject를 넣어 반환
return UsernamePasswordAuthenticationToken(
subject,
null,
JwtFilter.authenticatedUser?.get()?.roles?.map {
SimpleGrantedAuthority(it.name)
}
)
}

override fun getEmptyAuthentication(subject: String): UsernamePasswordAuthenticationToken {
// UserDetails 생성
val authDetails = userDetailsService.loadUserByUsername(subject) as CustomUserDetails

return UsernamePasswordAuthenticationToken(authDetails, null, null)
// Authentication에 subject를 넣어 반환
return UsernamePasswordAuthenticationToken(subject, null, null)
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package com.info.maeumgagym.domain.auth

import com.info.maeumgagym.domain.user.entity.UserJpaEntity
import com.info.maeumgagym.domain.user.mapper.UserMapper
import com.info.maeumgagym.security.principle.CustomUserDetails
import com.info.maeumgagym.security.jwt.JwtFilter
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.context.SecurityContextHolder

internal object AuthTestModule {
Expand All @@ -13,10 +14,15 @@ internal object AuthTestModule {

fun UserJpaEntity.saveInContext(userMapper: UserMapper): UserJpaEntity =
apply {
JwtFilter.authenticatedUser = null
SecurityContextHolder.getContext().authentication =
CustomUserDetails(userMapper.toDomain(this), this.oauthId).run {
UsernamePasswordAuthenticationToken(this, null, this.authorities)
}
UsernamePasswordAuthenticationToken(
this.oauthId,
null,
this.roles.map {
SimpleGrantedAuthority(it.name)
}
)
}

/**
Expand Down

0 comments on commit 3c57fef

Please sign in to comment.