From 5b9ba596a87aecb82c52c515a986fdf8c1bfa0af Mon Sep 17 00:00:00 2001 From: Junghoon Kim Date: Thu, 9 Jan 2025 00:10:05 +0900 Subject: [PATCH] :sparkles: added jwtAuthenticationFilter, UserPrincipal, and edited UserAccessTokenUtil, UserService, and SecurityConfig to correctly authenticate social users via jwt --- .../com/toyProject7/karrot/SecurityConfig.kt | 33 +++++++++-- .../karrot/security/JwtAuthenticationClass.kt | 55 +++++++++++++++++++ .../karrot/user/UserAccessTokenUtil.kt | 20 +++++++ .../karrot/user/persistence/UserPrincipal.kt | 34 ++++++++++++ .../karrot/user/service/UserService.kt | 16 ++++++ 5 files changed, 152 insertions(+), 6 deletions(-) create mode 100644 src/main/kotlin/com/toyProject7/karrot/security/JwtAuthenticationClass.kt create mode 100644 src/main/kotlin/com/toyProject7/karrot/user/persistence/UserPrincipal.kt diff --git a/src/main/kotlin/com/toyProject7/karrot/SecurityConfig.kt b/src/main/kotlin/com/toyProject7/karrot/SecurityConfig.kt index 529c9a4..c6cfe15 100644 --- a/src/main/kotlin/com/toyProject7/karrot/SecurityConfig.kt +++ b/src/main/kotlin/com/toyProject7/karrot/SecurityConfig.kt @@ -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() } -} +} \ No newline at end of file diff --git a/src/main/kotlin/com/toyProject7/karrot/security/JwtAuthenticationClass.kt b/src/main/kotlin/com/toyProject7/karrot/security/JwtAuthenticationClass.kt new file mode 100644 index 0000000..3009153 --- /dev/null +++ b/src/main/kotlin/com/toyProject7/karrot/security/JwtAuthenticationClass.kt @@ -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) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/toyProject7/karrot/user/UserAccessTokenUtil.kt b/src/main/kotlin/com/toyProject7/karrot/user/UserAccessTokenUtil.kt index 3305272..c83cc52 100644 --- a/src/main/kotlin/com/toyProject7/karrot/user/UserAccessTokenUtil.kt +++ b/src/main/kotlin/com/toyProject7/karrot/user/UserAccessTokenUtil.kt @@ -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 + } + } } diff --git a/src/main/kotlin/com/toyProject7/karrot/user/persistence/UserPrincipal.kt b/src/main/kotlin/com/toyProject7/karrot/user/persistence/UserPrincipal.kt new file mode 100644 index 0000000..697483a --- /dev/null +++ b/src/main/kotlin/com/toyProject7/karrot/user/persistence/UserPrincipal.kt @@ -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 +) : 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 = 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 +} \ No newline at end of file diff --git a/src/main/kotlin/com/toyProject7/karrot/user/service/UserService.kt b/src/main/kotlin/com/toyProject7/karrot/user/service/UserService.kt index e9ae4fb..460f3d5 100644 --- a/src/main/kotlin/com/toyProject7/karrot/user/service/UserService.kt +++ b/src/main/kotlin/com/toyProject7/karrot/user/service/UserService.kt @@ -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 @@ -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) + } + + }