Skip to content

Commit

Permalink
✨ added jwtAuthenticationFilter, UserPrincipal, and edited UserAccess…
Browse files Browse the repository at this point in the history
…TokenUtil, UserService, and SecurityConfig to correctly authenticate social users via jwt
  • Loading branch information
jafacode committed Jan 8, 2025
1 parent 0371bea commit 5b9ba59
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 6 deletions.
33 changes: 27 additions & 6 deletions src/main/kotlin/com/toyProject7/karrot/SecurityConfig.kt
Original file line number Diff line number Diff line change
@@ -1,27 +1,48 @@
package com.toyProject7.karrot

import com.toyProject7.karrot.security.JwtAuthenticationFilter
import com.toyProject7.karrot.socialLogin.handler.CustomAuthenticationSuccessHandler
import com.toyProject7.karrot.socialLogin.service.SocialLoginUserService
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
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.web.SecurityFilterChain
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter

@Configuration
class SecurityConfig {
@EnableWebSecurity
class SecurityConfig(
private val socialLoginUserService: SocialLoginUserService,
private val customAuthenticationSuccessHandler: CustomAuthenticationSuccessHandler,
private val jwtAuthenticationFilter: JwtAuthenticationFilter
) {

@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
return http
.csrf { csrf -> csrf.disable() }
.sessionManagement { session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
.authorizeHttpRequests { registry ->
registry.requestMatchers("/", "/login").permitAll()
registry.anyRequest().authenticated()
registry
.requestMatchers(
"/", "/login", "/css/**", "/js/**", "/images/**", "/oauth2/**"
).permitAll()
.anyRequest().authenticated()
}
.oauth2Login { oauth2login ->
oauth2login
.loginPage("/login")
.successHandler { request, response, authentication ->
response.sendRedirect("/profile")
.userInfoEndpoint { userInfo ->
userInfo.userService(socialLoginUserService)
}
.successHandler(customAuthenticationSuccessHandler)
.failureUrl("/login?error=true")
}
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)
.build()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.toyProject7.karrot.security

import com.toyProject7.karrot.user.UserAccessTokenUtil
import com.toyProject7.karrot.user.service.UserService
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource
import org.springframework.stereotype.Component
import org.springframework.web.filter.OncePerRequestFilter

@Component
class JwtAuthenticationFilter(
private val userService: UserService
) : OncePerRequestFilter() {

override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val authHeader = request.getHeader("Authorization")

if (authHeader != null && authHeader.startsWith("Bearer ")) {
val token = authHeader.substring(7)

try {
// Validate the token
if (UserAccessTokenUtil.validateToken(token)) {
// Get user ID from token
val userId = UserAccessTokenUtil.getUserIdFromToken(token)

// Load user details
val userDetails = userService.loadSocialUserById(userId)

// Create authentication token
val authentication = UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.authorities
)
authentication.details = WebAuthenticationDetailsSource().buildDetails(request)

// Set the authentication in the context
SecurityContextHolder.getContext().authentication = authentication
}
} catch (e: Exception) {
// Handle exceptions (e.g., log them)
println("Failed to authenticate user: ${e.message}")
}
}

filterChain.doFilter(request, response)
}
}
20 changes: 20 additions & 0 deletions src/main/kotlin/com/toyProject7/karrot/user/UserAccessTokenUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,25 @@ object UserAccessTokenUtil {
null
}
}

fun getUserIdFromToken(token: String): String {
val claims = Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(token)
return claims.body.subject
}

fun validateToken(token: String): Boolean {
return try {
val claims = Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(token)
!claims.body.expiration.before(Date())
} catch (e: Exception) {
false
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.toyProject7.karrot.user.persistence

import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.UserDetails

data class UserPrincipal(
val id: String,
private val email: String,
private val password: String?,
private val authorities: Collection<GrantedAuthority>
) : UserDetails {

companion object {
fun create(user: UserEntity): UserPrincipal {
val authorities = listOf(SimpleGrantedAuthority("ROLE_USER"))

return UserPrincipal(
id = user.id!!,
email = user.email,
password = null, // Password can be null for social login
authorities = authorities
)
}
}

override fun getAuthorities(): Collection<GrantedAuthority> = authorities
override fun getPassword(): String? = password
override fun getUsername(): String = email
override fun isAccountNonExpired(): Boolean = true
override fun isAccountNonLocked(): Boolean = true
override fun isCredentialsNonExpired(): Boolean = true
override fun isEnabled(): Boolean = true
}
16 changes: 16 additions & 0 deletions src/main/kotlin/com/toyProject7/karrot/user/service/UserService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import com.toyProject7.karrot.user.persistence.NormalUser
import com.toyProject7.karrot.user.persistence.NormalUserRepository
import com.toyProject7.karrot.user.persistence.SocialUser
import com.toyProject7.karrot.user.persistence.UserEntity
import com.toyProject7.karrot.user.persistence.UserPrincipal
import com.toyProject7.karrot.user.persistence.UserRepository
import org.mindrot.jbcrypt.BCrypt
import org.springframework.data.repository.findByIdOrNull
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

Expand Down Expand Up @@ -108,5 +110,19 @@ class UserService(
User.fromEntity(savedUser) // Convert and return as User DTO
}
}

fun loadSocialUserByUsername(email: String): UserPrincipal {
val user = userRepository.findSocialUserByEmail(email)
?: throw UsernameNotFoundException("User not found with email: $email")
return UserPrincipal.create(user)
}

fun loadSocialUserById(id: String): UserPrincipal {
val user = userRepository.findById(id)
.orElseThrow { UsernameNotFoundException("User not found with id: $id") }
return UserPrincipal.create(user)
}


}

0 comments on commit 5b9ba59

Please sign in to comment.