Différences
Ci-dessous, les différences entre deux révisions de la page.
| 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:33] – [Intégration RSA/Spring] jcheron | web:framework:spring:oauth2 [2024/04/16 13:58] (Version actuelle) – jcheron | ||
|---|---|---|---|
| Ligne 127: | Ligne 127: | ||
| </ | </ | ||
| + | === UserRepository === | ||
| + | Modifier votre **UserRepository** pour qu'il permette de rechercher un utilisateur par son login/ | ||
| + | |||
| + | <sxh kotlin> | ||
| + | @RepositoryRestResource(collectionResourceRel = " | ||
| + | interface UserRepository : JpaRepository< | ||
| + | fun findByUsernameOrEmail(username: | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | === 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: | ||
| + | |||
| + | @Throws(UsernameNotFoundException:: | ||
| + | override fun loadUserByUsername(usernameOrEmail: | ||
| + | val user: AuthUser = userRepository | ||
| + | .findByUsernameOrEmail(usernameOrEmail, | ||
| + | .map { AuthUser(it) } | ||
| + | .orElseThrow { UsernameNotFoundException(" | ||
| + | |||
| + | return user | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | === AuthService === | ||
| + | |||
| + | <sxh kotlin; | ||
| + | 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: | ||
| + | |||
| + | @Autowired | ||
| + | private val userRepository: | ||
| + | |||
| + | fun generateToken(authentication: | ||
| + | val now = Instant.now() | ||
| + | |||
| + | val scope: String = authentication.getAuthorities() | ||
| + | .stream() | ||
| + | .map { obj: GrantedAuthority -> obj.authority } | ||
| + | .collect(Collectors.joining(" | ||
| + | |||
| + | val claims = JwtClaimsSet.builder() | ||
| + | .issuer(" | ||
| + | .issuedAt(now) | ||
| + | .expiresAt(now.plus(10, | ||
| + | .subject(authentication.getName()) | ||
| + | .claim(" | ||
| + | .claim(" | ||
| + | .build() | ||
| + | |||
| + | return jwtEncoder!!.encode(JwtEncoderParameters.from(claims)).tokenValue | ||
| + | } | ||
| + | |||
| + | //Exemple de récupération de données dans le token JWT | ||
| + | fun getActiveUser(token: | ||
| + | val claims = JwtDecoder.decode(token).claims | ||
| + | val userId = claims[" | ||
| + | return userRepository!!.findById(userId).orElseThrow { RuntimeException(" | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ==== Configuration ==== | ||
| + | |||
| + | <sxh kotlin; | ||
| + | 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: | ||
| + | |||
| + | @Autowired | ||
| + | lateinit var userDetailsService: | ||
| + | |||
| + | |||
| + | @Bean | ||
| + | fun authManager(): | ||
| + | val authProvider = DaoAuthenticationProvider() | ||
| + | authProvider.setUserDetailsService(userDetailsService) | ||
| + | authProvider.setPasswordEncoder(passwordEncoder()) | ||
| + | return ProviderManager(authProvider) | ||
| + | } | ||
| + | |||
| + | |||
| + | @Bean | ||
| + | @Throws(Exception:: | ||
| + | fun filterChain(http: | ||
| + | return http | ||
| + | .csrf { csrf: CsrfConfigurer< | ||
| + | csrf.disable() | ||
| + | } | ||
| + | .cors { cors: CorsConfigurer< | ||
| + | .authorizeHttpRequests { auth -> | ||
| + | auth.requestMatchers("/ | ||
| + | auth.requestMatchers("/ | ||
| + | auth.requestMatchers("/ | ||
| + | auth.anyRequest().authenticated() | ||
| + | }.headers { headers -> | ||
| + | headers.frameOptions { it.sameOrigin() } | ||
| + | } | ||
| + | .sessionManagement { s: SessionManagementConfigurer< | ||
| + | s.sessionCreationPolicy( | ||
| + | SessionCreationPolicy.STATELESS | ||
| + | ) | ||
| + | } | ||
| + | .oauth2ResourceServer { oauth2: OAuth2ResourceServerConfigurer< | ||
| + | oauth2.jwt { jwt -> | ||
| + | jwt.decoder( | ||
| + | jwtDecoder() | ||
| + | ) | ||
| + | } | ||
| + | } | ||
| + | .userDetailsService(userDetailsService) | ||
| + | .httpBasic(Customizer.withDefaults()) | ||
| + | .build() | ||
| + | } | ||
| + | |||
| + | @Bean | ||
| + | fun jwtDecoder(): | ||
| + | return NimbusJwtDecoder.withPublicKey(rsaKeyConfigProperties.publicKey).build() | ||
| + | } | ||
| + | |||
| + | @Bean | ||
| + | fun jwtEncoder(): | ||
| + | val jwk: JWK = | ||
| + | RSAKey.Builder(rsaKeyConfigProperties.publicKey).privateKey(rsaKeyConfigProperties.privateKey).build() | ||
| + | |||
| + | val jwks: JWKSource< | ||
| + | return NimbusJwtEncoder(jwks) | ||
| + | } | ||
| + | |||
| + | @Bean | ||
| + | fun passwordEncoder(): | ||
| + | return BCryptPasswordEncoder() | ||
| + | } | ||
| + | |||
| + | companion object { | ||
| + | private val log: Logger = LoggerFactory.getLogger(SecurityConfig:: | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | ==== 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) | ||
| + | } | ||
| + | </ | ||
| + | === Controller === | ||
| + | |||
| + | <sxh kotlin> | ||
| + | @RestController | ||
| + | @RequestMapping("/ | ||
| + | @Validated | ||
| + | class AuthController { | ||
| + | |||
| + | @Autowired | ||
| + | lateinit var authService: | ||
| + | |||
| + | @Autowired | ||
| + | lateinit var authenticationManager: | ||
| + | |||
| + | @PostMapping("/ | ||
| + | @Throws(IllegalAccessException:: | ||
| + | fun login(@RequestBody userLogin: AuthDTO.LoginRequest): | ||
| + | val authentication: | ||
| + | authenticationManager | ||
| + | .authenticate( | ||
| + | UsernamePasswordAuthenticationToken( | ||
| + | userLogin.username, | ||
| + | userLogin.password | ||
| + | ) | ||
| + | ) | ||
| + | SecurityContextHolder.getContext().authentication = authentication | ||
| + | val userDetails = authentication.getPrincipal() as AuthUser | ||
| + | log.info(" | ||
| + | val token = authService.generateToken(authentication) | ||
| + | val response: AuthDTO.Response = AuthDTO.Response(" | ||
| + | return ResponseEntity.ok< | ||
| + | } | ||
| + | |||
| + | companion object { | ||
| + | private val log: Logger = LoggerFactory.getLogger(AuthController:: | ||
| + | } | ||
| + | } | ||
| + | </ | ||