web:framework:spring:oauth2

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentes Révision précédente
Prochaine révision
Révision précédente
web:framework:spring:oauth2 [2024/04/16 12:27] – [Intégration RSA/Spring] jcheronweb:framework:spring:oauth2 [2024/04/16 13:58] (Version actuelle) jcheron
Ligne 93: Ligne 93:
 Ajouter les 2 clés à **application.properties** : Ajouter les 2 clés à **application.properties** :
  
-<sxh ini;title: application.properties>+<sxh yaml;title: application.properties>
 #JWT #JWT
 rsa.private-key=classpath:certs/private-key-used.pem rsa.private-key=classpath:certs/private-key-used.pem
Ligne 99: Ligne 99:
 </sxh> </sxh>
  
 +==== Services et authentification ====
 +
 +=== AuthUser ===
 +Créer une classe AuthUser encapsulant un **User** et implémentant l'interface UserDetails de spring :
 +
 +<sxh kotlin;title:AuthUser>
 +class AuthUser(user: User) : UserDetails {
 +
 +    val user: User = user
 +
 +    override fun getAuthorities(): MutableCollection<out GrantedAuthority> {
 +        return mutableListOf(SimpleGrantedAuthority("ROLE_USER"))
 +    }
 +
 +    override fun getPassword(): String? = user.password
 +
 +    override fun getUsername(): String? = user.username
 +
 +    override fun isAccountNonExpired(): Boolean = true
 +
 +    override fun isAccountNonLocked(): Boolean = true
 +
 +    override fun isCredentialsNonExpired(): Boolean = true
 +
 +    override fun isEnabled(): Boolean = user.enabled
 +}
 +</sxh>
 +
 +=== UserRepository ===
 +Modifier votre **UserRepository** pour qu'il permette de rechercher un utilisateur par son login/username ou email (à vous de choisir) :
 +
 +<sxh kotlin>
 +@RepositoryRestResource(collectionResourceRel = "users", path = "users")
 +interface UserRepository : JpaRepository<User, UUID> {
 +    fun findByUsernameOrEmail(username: String, email: String): Optional<User>
 +}
 +</sxh>
 +
 +=== UserDetailsService ===
 +
 +<sxh kotlin>
 +import org.springframework.beans.factory.annotation.Autowired
 +import org.springframework.security.core.userdetails.UserDetails
 +import org.springframework.security.core.userdetails.UserDetailsService
 +import org.springframework.security.core.userdetails.UsernameNotFoundException
 +import org.springframework.stereotype.Service
 +
 +
 +@Service
 +class JpaUserDetailsService : UserDetailsService {
 +
 +    @Autowired
 +    lateinit var userRepository: UserRepository
 +
 +    @Throws(UsernameNotFoundException::class)
 +    override fun loadUserByUsername(usernameOrEmail: String): UserDetails {
 +        val user: AuthUser = userRepository
 +            .findByUsernameOrEmail(usernameOrEmail, usernameOrEmail)
 +            .map { AuthUser(it) }
 +            .orElseThrow { UsernameNotFoundException("User name or email not found: $usernameOrEmail") }
 +
 +        return user
 +    }
 +}
 +</sxh>
 +
 +=== AuthService ===
 +
 +<sxh kotlin;title: AuthService>
 +import org.springframework.beans.factory.annotation.Autowired
 +import org.springframework.security.core.Authentication
 +import org.springframework.security.core.GrantedAuthority
 +import org.springframework.security.crypto.password.PasswordEncoder
 +import org.springframework.security.oauth2.jwt.JwtClaimsSet
 +import org.springframework.security.oauth2.jwt.JwtDecoder
 +import org.springframework.security.oauth2.jwt.JwtEncoder
 +import org.springframework.security.oauth2.jwt.JwtEncoderParameters
 +import org.springframework.stereotype.Service
 +import java.time.Instant
 +import java.time.temporal.ChronoUnit
 +import java.util.*
 +import java.util.stream.Collectors
 +
 +
 +@Service
 +class AuthService {
 +
 +    @Autowired
 +    private val jwtEncoder: JwtEncoder? = null
 +
 +    @Autowired
 +    lateinit var JwtDecoder: JwtDecoder
 +
 +    @Autowired
 +    private val passwordEncoder: PasswordEncoder? = null
 +
 +    @Autowired
 +    private val userRepository: UserRepository? = null
 +
 +    fun generateToken(authentication: Authentication): String {
 +        val now = Instant.now()
 +
 +        val scope: String = authentication.getAuthorities()
 +            .stream()
 +            .map { obj: GrantedAuthority -> obj.authority }
 +            .collect(Collectors.joining(" "))
 +
 +        val claims = JwtClaimsSet.builder()
 +            .issuer("self")
 +            .issuedAt(now)
 +            .expiresAt(now.plus(10, ChronoUnit.HOURS))
 +            .subject(authentication.getName())
 +            .claim("scope", scope)
 +            .claim("user_id", (authentication.principal as AuthUser).user.id)
 +            .build()
 +
 +        return jwtEncoder!!.encode(JwtEncoderParameters.from(claims)).tokenValue
 +    }
 +
 +    //Exemple de récupération de données dans le token JWT
 +    fun getActiveUser(token: String): User {
 +        val claims = JwtDecoder.decode(token).claims
 +        val userId = claims["user_id"] as UUID
 +        return userRepository!!.findById(userId).orElseThrow { RuntimeException("User not found") }
 +    }
 +}
 +</sxh>
 +
 +==== Configuration ====
 +
 +<sxh kotlin;title: SecurityConfig>
 +import com.nimbusds.jose.jwk.JWK
 +import com.nimbusds.jose.jwk.JWKSet
 +import com.nimbusds.jose.jwk.RSAKey
 +import com.nimbusds.jose.jwk.source.ImmutableJWKSet
 +import com.nimbusds.jose.jwk.source.JWKSource
 +import com.nimbusds.jose.proc.SecurityContext
 +import fr.zerp.api.security.JpaUserDetailsService
 +import fr.zerp.api.security.RsaKeyConfigProperties
 +import org.slf4j.Logger
 +import org.slf4j.LoggerFactory
 +import org.springframework.beans.factory.annotation.Autowired
 +import org.springframework.context.annotation.Bean
 +import org.springframework.context.annotation.Configuration
 +import org.springframework.security.authentication.AuthenticationManager
 +import org.springframework.security.authentication.ProviderManager
 +import org.springframework.security.authentication.dao.DaoAuthenticationProvider
 +import org.springframework.security.config.Customizer
 +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.config.annotation.web.configurers.CorsConfigurer
 +import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer
 +import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer
 +import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer
 +import org.springframework.security.config.http.SessionCreationPolicy
 +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
 +import org.springframework.security.crypto.password.PasswordEncoder
 +import org.springframework.security.oauth2.jwt.JwtDecoder
 +import org.springframework.security.oauth2.jwt.JwtEncoder
 +import org.springframework.security.oauth2.jwt.NimbusJwtDecoder
 +import org.springframework.security.oauth2.jwt.NimbusJwtEncoder
 +import org.springframework.security.web.SecurityFilterChain
 +import org.springframework.web.servlet.handler.HandlerMappingIntrospector
 +
 +
 +@Configuration
 +@EnableWebSecurity
 +@EnableMethodSecurity
 +class SecurityConfig {
 +
 +    @Autowired
 +    lateinit var rsaKeyConfigProperties: RsaKeyConfigProperties
 +
 +    @Autowired
 +    lateinit var userDetailsService: JpaUserDetailsService
 +
 +
 +    @Bean
 +    fun authManager(): AuthenticationManager {
 +        val authProvider = DaoAuthenticationProvider()
 +        authProvider.setUserDetailsService(userDetailsService)
 +        authProvider.setPasswordEncoder(passwordEncoder())
 +        return ProviderManager(authProvider)
 +    }
 +
 +
 +    @Bean
 +    @Throws(Exception::class)
 +    fun filterChain(http: HttpSecurity, introspector: HandlerMappingIntrospector?): SecurityFilterChain {
 +        return http
 +            .csrf { csrf: CsrfConfigurer<HttpSecurity> ->
 +                csrf.disable()
 +            }
 +            .cors { cors: CorsConfigurer<HttpSecurity> -> cors.disable() }
 +            .authorizeHttpRequests { auth ->
 +                auth.requestMatchers("/error/**").permitAll()
 +                auth.requestMatchers("/api/auth/**").permitAll()
 +                auth.requestMatchers("/h2-console/**").permitAll()
 +                auth.anyRequest().authenticated()
 +            }.headers { headers ->
 +                headers.frameOptions { it.sameOrigin() }
 +            }
 +            .sessionManagement { s: SessionManagementConfigurer<HttpSecurity?> ->
 +                s.sessionCreationPolicy(
 +                    SessionCreationPolicy.STATELESS
 +                )
 +            }
 +            .oauth2ResourceServer { oauth2: OAuth2ResourceServerConfigurer<HttpSecurity?> ->
 +                oauth2.jwt { jwt ->
 +                    jwt.decoder(
 +                        jwtDecoder()
 +                    )
 +                }
 +            }
 +            .userDetailsService(userDetailsService)
 +            .httpBasic(Customizer.withDefaults())
 +            .build()
 +    }
 +
 +    @Bean
 +    fun jwtDecoder(): JwtDecoder {
 +        return NimbusJwtDecoder.withPublicKey(rsaKeyConfigProperties.publicKey).build()
 +    }
 +
 +    @Bean
 +    fun jwtEncoder(): JwtEncoder {
 +        val jwk: JWK =
 +            RSAKey.Builder(rsaKeyConfigProperties.publicKey).privateKey(rsaKeyConfigProperties.privateKey).build()
 +
 +        val jwks: JWKSource<SecurityContext> = ImmutableJWKSet(JWKSet(jwk))
 +        return NimbusJwtEncoder(jwks)
 +    }
 +
 +    @Bean
 +    fun passwordEncoder(): PasswordEncoder {
 +        return BCryptPasswordEncoder()
 +    }
 +
 +    companion object {
 +        private val log: Logger = LoggerFactory.getLogger(SecurityConfig::class.java)
 +    }
 +}
 +</sxh>
 +==== Authentification ====
 +=== DTO ===
 +
 +<sxh kotlin>
 +class AuthDTO {
 +    @JvmRecord
 +    data class LoginRequest(val username: String, val password: String)
 +
 +    @JvmRecord
 +    data class Response(val message: String, val token: String)
 +}
 +</sxh>
 +=== Controller ===
 +
 +<sxh kotlin>
 +@RestController
 +@RequestMapping("/api/auth")
 +@Validated
 +class AuthController {
 +
 +    @Autowired
 +    lateinit var authService: AuthService
 +
 +    @Autowired
 +    lateinit var authenticationManager: AuthenticationManager
 +
 +    @PostMapping("/login")
 +    @Throws(IllegalAccessException::class)
 +    fun login(@RequestBody userLogin: AuthDTO.LoginRequest): ResponseEntity<*> {
 +        val authentication: Authentication =
 +            authenticationManager
 +                .authenticate(
 +                    UsernamePasswordAuthenticationToken(
 +                        userLogin.username,
 +                        userLogin.password
 +                    )
 +                )
 +        SecurityContextHolder.getContext().authentication = authentication
 +        val userDetails = authentication.getPrincipal() as AuthUser
 +        log.info("Token requested for user :{}", authentication.getAuthorities())
 +        val token = authService.generateToken(authentication)
 +        val response: AuthDTO.Response = AuthDTO.Response("User logged in successfully", token)
 +        return ResponseEntity.ok<Any>(response)
 +    }
 +
 +    companion object {
 +        private val log: Logger = LoggerFactory.getLogger(AuthController::class.java)
 +    }
 +}
 +</sxh>
  • web/framework/spring/oauth2.1713263265.txt.gz
  • Dernière modification : il y a 5 semaines
  • de jcheron