Skip to content

Commit

Permalink
Security 관련 수정 및 리팩토링 (#339)
Browse files Browse the repository at this point in the history
* use requestMatcher for admin api instead of method security

* create CustomOidcUser for both oidc and mock login

* remove role in db

* replace custom annotation to PreAuthorize

* extract current user functions to Util.kt

* replace is-staff api to general role getter

* remove unneeded code due to CustomOidcUser

* get role parameter for mock-login and add mock-logut api

* remove role from users table

* refactor getLoginUser to UserService for testing

* Feat: 학생회 파일 관련 API 구현 (#338)

* migration: add council_file table

* feat: add council file entity, repository

* feat: add council file handling to attachment

* feat: define key for rule, meetingminute

* feat: define dto for base, rule, meeting minute

* feat: add counfil file service

* feat: define response bodies for council file

* feat: add api for council file

* review: remove nested 'it'

* review: remove verbose use of maps

* CouncilIntro RU api (#337)

* CouncilIntro RU api

* apply upsert and findFirst intro

* ktlint

* use requestMatcher for admin api instead of method security

* create CustomOidcUser for both oidc and mock login

* remove role in db

* replace custom annotation to PreAuthorize

* extract current user functions to Util.kt

* replace is-staff api to general role getter

* remove unneeded code due to CustomOidcUser

* get role parameter for mock-login and add mock-logut api

* remove role from users table

* refactor getLoginUser to UserService for testing

* Feat: 학생회 파일 관련 API 구현 (#338)

* migration: add council_file table

* feat: add council file entity, repository

* feat: add council file handling to attachment

* feat: define key for rule, meetingminute

* feat: define dto for base, rule, meeting minute

* feat: add counfil file service

* feat: define response bodies for council file

* feat: add api for council file

* review: remove nested 'it'

* review: remove verbose use of maps

* always provide new test user

* ktlint

* Rename V4__update_user.sql to V5__update_user.sql

---------

Co-authored-by: 우혁준 (Logan) <whjoon0225@naver.com>
  • Loading branch information
leeeryboy and huGgW authored Feb 23, 2025
1 parent 743e164 commit d427fd1
Show file tree
Hide file tree
Showing 39 changed files with 296 additions and 557 deletions.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.core.Authentication
Expand All @@ -23,6 +24,7 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource
@Profile("!test")
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@EnableConfigurationProperties(EndpointProperties::class)
class SecurityConfig(
private val customOidcUserService: CustomOidcUserService,
Expand Down Expand Up @@ -58,6 +60,7 @@ class SecurityConfig(
.authorizeHttpRequests { auth ->
auth
.requestMatchers("/api/v1/login").authenticated()
.requestMatchers("/api/v2/admin/**").hasRole("STAFF")
.anyRequest().permitAll()
}
.headers { header ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.wafflestudio.csereal.common.mockauth

import com.wafflestudio.csereal.core.user.database.UserEntity
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.oauth2.core.oidc.OidcIdToken
import org.springframework.security.oauth2.core.oidc.OidcUserInfo
import org.springframework.security.oauth2.core.oidc.user.OidcUser

data class CustomOidcUser(
val userEntity: UserEntity,
private val authorities: Collection<GrantedAuthority>,
private val idToken: OidcIdToken,
private val userInfo: OidcUserInfo? = null
) : OidcUser, UserDetails {
override fun getName(): String = idToken.subject
override fun getAttributes(): MutableMap<String, Any> = idToken.claims.toMutableMap()
override fun getAuthorities(): Collection<GrantedAuthority> = authorities
override fun getClaims(): Map<String, Any> = idToken.claims
override fun getUserInfo(): OidcUserInfo? = userInfo
override fun getIdToken(): OidcIdToken = idToken

override fun getPassword(): String? = null
override fun getUsername(): String = userEntity.username
override fun isAccountNonExpired(): Boolean = true
override fun isAccountNonLocked(): Boolean = true
override fun isCredentialsNonExpired(): Boolean = true
override fun isEnabled(): Boolean = true
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,50 +1,62 @@
package com.wafflestudio.csereal.common.mockauth

import com.wafflestudio.csereal.core.user.database.Role
import com.wafflestudio.csereal.core.user.database.UserEntity
import com.wafflestudio.csereal.core.user.database.UserRepository
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.context.annotation.Profile
import org.springframework.http.ResponseEntity
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.oauth2.core.oidc.OidcIdToken
import org.springframework.security.web.context.SecurityContextRepository
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import java.time.Instant

@Profile("!prod")
@RestController
@RequestMapping("/api/v1")
@RequestMapping("/api/v2")
class DevAuthController(
private val authenticationManager: AuthenticationManager,
private val userRepository: UserRepository,
private val securityContextRepository: SecurityContextRepository
private val securityContextRepository: SecurityContextRepository,
private val userRepository: UserRepository
) {

@GetMapping("/mock-login")
fun mockLogin(request: HttpServletRequest, response: HttpServletResponse): ResponseEntity<Any> {
fun mockLogin(
request: HttpServletRequest,
response: HttpServletResponse,
@RequestParam(defaultValue = "ROLE_STAFF") role: String
): ResponseEntity<String> {
val mockUser = userRepository.findByUsername("devUser")
?: userRepository.save(UserEntity("devUser", "Mock", "mock@abc.com", "0000-00000", Role.ROLE_STAFF))
val customPrincipal = CustomPrincipal(mockUser)
val authenticationToken = UsernamePasswordAuthenticationToken(
customPrincipal,
null,
listOf(
SimpleGrantedAuthority("ROLE_STAFF")
)
)

val authentication = authenticationManager.authenticate(authenticationToken)
SecurityContextHolder.getContext().authentication = authentication
?: userRepository.save(UserEntity("devUser", "Mock", "mock@abc.com", "0000-00000"))

val authorities = listOf(SimpleGrantedAuthority(role))

// dummy token creation
val issuedAt = Instant.now()
val expiresAt = issuedAt.plusSeconds(3600)
val claims = mapOf("sub" to mockUser.username)
val dummyIdToken = OidcIdToken("mock-token", issuedAt, expiresAt, claims)

val customOidcUser = CustomOidcUser(mockUser, authorities, dummyIdToken)
val authentication = UsernamePasswordAuthenticationToken(customOidcUser, null, authorities)

SecurityContextHolder.getContext().authentication = authentication
securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response)

request.getSession(true)
return ResponseEntity.ok("Mock login successful with role: $role")
}

return ResponseEntity.ok().body("Mock user authenticated")
@GetMapping("/mock-logout")
fun mockLogout(
request: HttpServletRequest,
response: HttpServletResponse
): ResponseEntity<String> {
request.getSession(false)?.invalidate()
return ResponseEntity.ok("Mock logout successful")
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,11 @@ package com.wafflestudio.csereal.common.mockauth

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.web.context.HttpSessionSecurityContextRepository
import org.springframework.security.web.context.SecurityContextRepository

@Configuration
class MockAuthConfig(
private val devAuthenticationProvider: DevAuthenticationProvider
) {
@Bean
fun authenticationManager(http: HttpSecurity): AuthenticationManager {
http.authenticationProvider(devAuthenticationProvider)
return http.getSharedObject(AuthenticationManagerBuilder::class.java).build()
}
class MockAuthConfig {

@Bean
fun securityContextRepository(): SecurityContextRepository {
Expand Down
24 changes: 9 additions & 15 deletions src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package com.wafflestudio.csereal.common.utils

import com.wafflestudio.csereal.common.CserealException
import com.wafflestudio.csereal.common.mockauth.CustomPrincipal
import org.jsoup.Jsoup
import org.jsoup.parser.Parser
import org.jsoup.safety.Safelist
import org.springframework.security.core.Authentication
import org.springframework.security.oauth2.core.oidc.user.OidcUser
import org.springframework.security.core.context.SecurityContextHolder
import kotlin.math.ceil

fun cleanTextFromHtml(description: String): String {
Expand Down Expand Up @@ -45,18 +42,15 @@ fun exchangeValidPageNum(pageSize: Int, pageNum: Int, total: Long): Int {
}
}

fun getUsername(authentication: Authentication?): String? {
val principal = authentication?.principal
fun startsWithEnglish(name: String): Boolean {
return name.isNotEmpty() && name.first().let { it in 'A'..'Z' || it in 'a'..'z' }
}

return principal?.let {
when (principal) {
is OidcUser -> principal.idToken.getClaim("username")
is CustomPrincipal -> principal.userEntity.username
else -> throw CserealException.Csereal401("Unsupported principal type")
}
}
fun isCurrentUserStaff(): Boolean {
return "ROLE_STAFF" in getCurrentUserRoles()
}

fun startsWithEnglish(name: String): Boolean {
return name.isNotEmpty() && name.first().let { it in 'A'..'Z' || it in 'a'..'z' }
fun getCurrentUserRoles(): List<String> {
val authentication = SecurityContextHolder.getContext().authentication
return authentication.authorities.map { it.authority }
}
Loading

0 comments on commit d427fd1

Please sign in to comment.