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:17] – [Génération des clés] jcheronweb:framework:spring:oauth2 [2024/04/16 13:58] (Version actuelle) jcheron
Ligne 58: Ligne 58:
  
 En résumé, les clés RSA (publique et privée) sont utilisées dans le cadre d'OAuth 2 pour garantir l'authenticité, l'intégrité et la sécurité des échanges de jetons d'accès entre les clients et le serveur OAuth. En résumé, les clés RSA (publique et privée) sont utilisées dans le cadre d'OAuth 2 pour garantir l'authenticité, l'intégrité et la sécurité des échanges de jetons d'accès entre les clients et le serveur OAuth.
 +
 +==== Intégration RSA/Spring ====
 +
 +Créer une classe pour gérer les properties à ajouter pour stocker les 2 clés :
 +
 +Dans un package **security** à créer :
 +
 +<sxh kotlin;title:RsaKeyConfigProperties>
 +import org.springframework.boot.context.properties.ConfigurationProperties
 +import java.security.interfaces.RSAPrivateKey
 +import java.security.interfaces.RSAPublicKey
 +
 +
 +@ConfigurationProperties(prefix = "rsa")
 +@JvmRecord
 +data class RsaKeyConfigProperties(val publicKey: RSAPublicKey, val privateKey: RSAPrivateKey)
 +</sxh>
 +
 +Activer cette classe de propriétés directement sur la classe de votre application Spring :
 +
 +<sxh kotlin;highlight:[8]>
 +import fr.zerp.api.security.RsaKeyConfigProperties
 +import org.springframework.boot.autoconfigure.SpringBootApplication
 +import org.springframework.boot.context.properties.EnableConfigurationProperties
 +import org.springframework.boot.runApplication
 +
 +
 +@SpringBootApplication
 +@EnableConfigurationProperties(RsaKeyConfigProperties::class)
 +class MyApplication
 +</sxh>
 +
 +
 +Ajouter les 2 clés à **application.properties** :
 +
 +<sxh yaml;title: application.properties>
 +#JWT
 +rsa.private-key=classpath:certs/private-key-used.pem
 +rsa.public-key=classpath:certs/public-key.pem
 +</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.1713262622.txt.gz
  • Dernière modification : il y a 5 semaines
  • de jcheron