Skip to content

Commit

Permalink
Checkpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
Isti01 committed Dec 17, 2024
1 parent f35c632 commit 23458f6
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 21 deletions.
1 change: 1 addition & 0 deletions backend/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ dependencies {
implementation("jakarta.xml.bind:jakarta.xml.bind-api:4.0.2")
api("org.springframework.boot:spring-boot-configuration-processor")
api("org.springframework.boot:spring-boot-starter-data-jpa")
api("org.springframework.session:spring-session-jdbc")
api("org.springframework.boot:spring-boot-starter-oauth2-client")
api("org.springframework.boot:spring-boot-starter-security")
api("org.springframework.boot:spring-boot-starter-web")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ class AuthschLoginController(
}

@GetMapping("/control/logout")
fun logout(auth: Authentication?, httpResponse: HttpServletResponse): String {
fun logout(auth: Authentication?, response: HttpServletResponse): String {
log.info("Logging out from user {}", auth?.getUserOrNull()?.internalId ?: "n/a")

try {
httpResponse.addCookie(createJwtCookie(null).apply { maxAge = 0 })
createJwtCookies(null).forEach { response.addCookie(it) }
SecurityContextHolder.getContext().authentication = null
} catch (e: Exception) {
// It should be logged out anyway
Expand All @@ -70,8 +70,7 @@ class AuthschLoginController(
return "redirect:${applicationComponent.siteUrl.getValue()}?error=cannot-generate-jwt"
}
val jwtToken = jwtTokenProvider.createToken(auth.principal as CmschUser)

response.addCookie(createJwtCookie(jwtToken))
createJwtCookies(jwtToken).forEach { response.addCookie(it) }

return "redirect:${applicationComponent.siteUrl.getValue()}"
}
Expand All @@ -84,7 +83,7 @@ class AuthschLoginController(
if (auth == null)
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()

response.addCookie(createJwtCookie(jwtTokenProvider.refreshToken(auth)))
createJwtCookies(jwtTokenProvider.refreshToken(auth)).forEach { response.addCookie(it) }

return ResponseEntity.ok().build()
}
Expand All @@ -93,13 +92,22 @@ class AuthschLoginController(
return URI(url).host
}

private fun createJwtCookie(value: String?): Cookie {
return Cookie("jwt", value).apply {
isHttpOnly = true
path = "/"
maxAge = startupPropertyConfig.sessionValidityInMilliseconds.toInt() / 1000
secure = true
domain = getDomainFromUrl(applicationComponent.siteUrl.getValue())
}
private fun createJwtCookies(value: String?): List<Cookie> {
return listOf(
Cookie("jwt", value).apply {
isHttpOnly = true
path = "/"
maxAge = startupPropertyConfig.sessionValidityInMilliseconds.toInt() / 1000
secure = true
domain = getDomainFromUrl(applicationComponent.siteUrl.getValue())
},
Cookie("jwt", value).apply {
isHttpOnly = true
path = "/"
maxAge = startupPropertyConfig.sessionValidityInMilliseconds.toInt() / 1000
secure = true
domain = getDomainFromUrl(applicationComponent.adminSiteUrl.getValue())
},
)
}
}
30 changes: 23 additions & 7 deletions backend/src/main/kotlin/hu/bme/sch/cmsch/config/SecurityConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,29 @@ import org.springframework.context.annotation.Configuration
import org.springframework.core.Ordered
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.retry.annotation.EnableRetry
import org.springframework.security.config.Customizer
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.oauth2.client.JdbcOAuth2AuthorizedClientService
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser
import org.springframework.security.oauth2.core.user.DefaultOAuth2User
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher
import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession
import org.springframework.web.reactive.function.client.WebClient
import java.util.*

@EnableWebSecurity
@Configuration
@EnableJdbcHttpSession
@EnableWebSecurity
@EnableRetry(order = Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnBean(LoginComponent::class)
open class SecurityConfig(
Expand Down Expand Up @@ -66,8 +71,18 @@ open class SecurityConfig(
.defaultHeader(HttpHeaders.USER_AGENT, "AuthSchKotlinAPI")
.build()


@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
fun authorizedClientService(
jdbcTemplate: JdbcTemplate,
clientRegistrationRepository: ClientRegistrationRepository
): OAuth2AuthorizedClientService = JdbcOAuth2AuthorizedClientService(jdbcTemplate, clientRegistrationRepository)

@Bean
fun securityFilterChain(
http: HttpSecurity,
authorizedClientService: OAuth2AuthorizedClientService
): SecurityFilterChain {
http.authorizeHttpRequests {
it.requestMatchers(
antMatcher("/"),
Expand Down Expand Up @@ -130,7 +145,6 @@ open class SecurityConfig(
RoleType.ADMIN.name,
RoleType.SUPERUSER.name
)

it.requestMatchers(
antMatcher("/admin/**"),
antMatcher("/cdn/**")
Expand All @@ -143,16 +157,18 @@ open class SecurityConfig(
http.formLogin { it.disable() }
http.exceptionHandling { it.accessDeniedPage("/403") }
http.with(JwtConfigurer(jwtTokenProvider), Customizer.withDefaults())
http.sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
http.oauth2Login { oauth2 ->
oauth2.loginPage("/oauth2/authorization")
oauth2
.loginPage("/oauth2/authorization")
.authorizationEndpoint {
it.authorizationRequestResolver(
CustomAuthorizationRequestResolver(
clientRegistrationRepository, "/oauth2/authorization", loginComponent
)
)
}.userInfoEndpoint { userInfo ->
}
.authorizedClientService(authorizedClientService)
.userInfoEndpoint { userInfo ->
userInfo
.oidcUserService {
if (it.clientRegistration.clientId.contains("google")) {
Expand Down Expand Up @@ -212,7 +228,7 @@ open class SecurityConfig(
private fun resolveKeycloakUser(request: OidcUserRequest): DefaultOidcUser {
val decodedPayload = String(Base64.getDecoder().decode(request.accessToken.tokenValue.split(".")[1]))
val profile: KeycloakUserInfoResponse = objectMapper.readerFor(KeycloakUserInfoResponse::class.java)
.readValue(decodedPayload)
.readValue(decodedPayload)
val userEntity = authschLoginService.fetchKeycloakUserEntity(profile)

auditLogService.login(userEntity, "keycloak user login g:${userEntity.group} r:${userEntity.role}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class JwtTokenFilter(
val httpRequest = req as HttpServletRequest
val token: String? = jwtTokenProvider.resolveToken(httpRequest)
try {
if (token != null && jwtTokenProvider.validateToken(token)) {
if (!token.isNullOrBlank() && jwtTokenProvider.validateToken(token)) {
val auth: Authentication = jwtTokenProvider.getAuthentication(token)
SecurityContextHolder.getContext().authentication = auth
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package hu.bme.sch.cmsch.model

import jakarta.persistence.*
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.Size
import org.hibernate.annotations.ColumnDefault
import java.time.Instant

/**
* Created for JdbcOAuth2AuthorizedClientService
*/
@Entity
@Table(name = "OAUTH2_AUTHORIZED_CLIENT")
open class Oauth2AuthorizedClient {
@EmbeddedId
open var id: Oauth2AuthorizedClientId? = null

@Size(max = 100)
@NotNull
@Column(name = "ACCESS_TOKEN_TYPE", nullable = false, length = 100)
open var accessTokenType: String? = null

@NotNull
@Column(name = "ACCESS_TOKEN_ISSUED_AT", nullable = false)
open var accessTokenIssuedAt: Instant? = null

@NotNull
@Column(name = "ACCESS_TOKEN_EXPIRES_AT", nullable = false)
open var accessTokenExpiresAt: Instant? = null

@Size(max = 1000)
@ColumnDefault("NULL")
@Column(name = "ACCESS_TOKEN_SCOPES", length = 1000)
open var accessTokenScopes: String? = null

@ColumnDefault("NULL")
@Column(name = "REFRESH_TOKEN_ISSUED_AT")
open var refreshTokenIssuedAt: Instant? = null

@NotNull
@ColumnDefault("CURRENT_TIMESTAMP")
@Column(name = "CREATED_AT", nullable = false)
open var createdAt: Instant? = null

@Column(name = "ACCESS_TOKEN_VALUE", length = Integer.MAX_VALUE)
open var accessTokenValue: ByteArray? = null

@ColumnDefault("NULL")
@Column(name = "REFRESH_TOKEN_VALUE", length = Integer.MAX_VALUE)
open var refreshTokenValue: ByteArray? = null

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package hu.bme.sch.cmsch.model

import jakarta.persistence.Column
import jakarta.persistence.Embeddable
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.Size
import org.hibernate.Hibernate
import java.io.Serializable
import java.util.*

/**
* Created for JdbcOAuth2AuthorizedClientService
*/
@Embeddable
open class Oauth2AuthorizedClientId : Serializable {
@Size(max = 100)
@NotNull
@Column(name = "CLIENT_REGISTRATION_ID", nullable = false, length = 100)
open var clientRegistrationId: String? = null

@Size(max = 200)
@NotNull
@Column(name = "PRINCIPAL_NAME", nullable = false, length = 200)
open var principalName: String? = null
override fun hashCode(): Int = Objects.hash(clientRegistrationId, principalName)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || Hibernate.getClass(this) != Hibernate.getClass(other)) return false

other as Oauth2AuthorizedClientId

return clientRegistrationId == other.clientRegistrationId &&
principalName == other.principalName
}

companion object {
private const val serialVersionUID = 0L
}
}
42 changes: 42 additions & 0 deletions backend/src/main/kotlin/hu/bme/sch/cmsch/model/SpringSession.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package hu.bme.sch.cmsch.model

import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.Id
import jakarta.persistence.Table
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.Size

@Entity
@Table(name = "SPRING_SESSION")
open class SpringSession {
@Id
@Size(max = 36)
@Column(name = "PRIMARY_ID", nullable = false, length = 36)
open var primaryId: String? = null

@Size(max = 36)
@NotNull
@Column(name = "SESSION_ID", nullable = false, length = 36)
open var sessionId: String? = null

@NotNull
@Column(name = "CREATION_TIME", nullable = false)
open var creationTime: Long? = null

@NotNull
@Column(name = "LAST_ACCESS_TIME", nullable = false)
open var lastAccessTime: Long? = null

@NotNull
@Column(name = "MAX_INACTIVE_INTERVAL", nullable = false)
open var maxInactiveInterval: Int? = null

@NotNull
@Column(name = "EXPIRY_TIME", nullable = false)
open var expiryTime: Long? = null

@Size(max = 100)
@Column(name = "PRINCIPAL_NAME", length = 100)
open var principalName: String? = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package hu.bme.sch.cmsch.model

import jakarta.persistence.*
import jakarta.validation.constraints.NotNull
import org.hibernate.annotations.OnDelete
import org.hibernate.annotations.OnDeleteAction

@Entity
@Table(name = "SPRING_SESSION_ATTRIBUTES")
open class SpringSessionAttribute {
@EmbeddedId
open var id: SpringSessionAttributeId? = null

@MapsId("sessionPrimaryId")
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@OnDelete(action = OnDeleteAction.CASCADE)
@JoinColumn(name = "SESSION_PRIMARY_ID", nullable = false)
open var sessionPrimary: SpringSession? = null

@NotNull
@Column(name = "ATTRIBUTE_BYTES", nullable = false, length = Integer.MAX_VALUE)
open var attributeBytes: ByteArray? = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package hu.bme.sch.cmsch.model

import jakarta.persistence.Column
import jakarta.persistence.Embeddable
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.Size
import org.hibernate.Hibernate
import java.io.Serializable
import java.util.*

@Embeddable
open class SpringSessionAttributeId : Serializable {
@Size(max = 36)
@NotNull
@Column(name = "SESSION_PRIMARY_ID", nullable = false, length = 36)
open var sessionPrimaryId: String? = null

@Size(max = 200)
@NotNull
@Column(name = "ATTRIBUTE_NAME", nullable = false, length = 200)
open var attributeName: String? = null
override fun hashCode(): Int = Objects.hash(sessionPrimaryId, attributeName)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || Hibernate.getClass(this) != Hibernate.getClass(other)) return false

other as SpringSessionAttributeId

return sessionPrimaryId == other.sessionPrimaryId &&
attributeName == other.attributeName
}

companion object {
private const val serialVersionUID = 0L
}
}

0 comments on commit 23458f6

Please sign in to comment.