Ceci est une ancienne révision du document !
Spring security
Spring Sécurity est un framework permettant d'ajouter aux applications Spring authentification et contrôle d'accès.
Spring security ajoute au Dispatcher servlet de Spring MVC un ensemble de filtres (servlet filters) qualifié de filter chain.
Intégration
Ajouter les dépendances suivantes à pom.xml :
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> </dependency>
Authentification par défaut
L'intégration de Spring security met en place une authentification basique par défaut, avec un utilisateur de non user, ayant le rôle USER,
dont le password s'affiche dans la console de démarrage de Spring, utilisable en mode développement :
Il est possible de modifier l'utilisateur par défaut dans application.properties :
spring.security.user.name=boris spring.security.user.password=boris-password spring.security.user.roles=manager
Configuration
La configuration se fait au travers d'une classe de configuration activant la sécurité :
@Configuration
@EnableWebSecurity
class WebSecurityConfig {
@Bean
@Throws(Exception::class)
fun configure(http: HttpSecurity): SecurityFilterChain {
http
.csrf().disable().authorizeHttpRequests()
.requestMatchers("/master/**").hasRole("manager")
.anyRequest().authenticated()
.and()
.formLogin()
return http.build()
}
}
Authentification depuis une BDD
Entity
Créer une entity User pour gérer les utilisateur et leur rôle :
@Entity
open class User() {
constructor(username:String):this(){
this.username=username
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
open var id:Int=0
@Column(length = 65, nullable = false)
open lateinit var username:String
@Column(length = 256)
open var password:String?=null
@Column(unique = true, length = 115)
open var email:String?=null
@Column(nullable = false)
open lateinit var role:String
}
Créer le repository associé et ajouter une méthode permettant de rechercher un User par son username ou son email :
interface UserRepository : JpaRepository<User, Int> {
@Query("SELECT u FROM User u WHERE u.username=:usernameOrEmail OR u.email=:usernameOrEmail")
fun findByUsernameOrEmail(usernameOrEmail: String?): User?
}
UserDetailsService
Créer un Service implémentant UserDetailsService :
Ce service retourne depuis la base de données un utilisateur recherché par son nom ou son email et retourne une instance de UserDetails :
class DbUserService:UserDetailsService {
@Autowired
lateinit var userRepository: UserRepository
@Autowired
lateinit var passwordEncoder: PasswordEncoder
override fun loadUserByUsername(username: String?): UserDetails {
val user= userRepository.findByUsernameOrEmail(username!!) ?: throw UsernameNotFoundException("User not found")
return org.springframework.security.core.userdetails.User(user.username,user.password, getGrantedAuthorities(user))
}
private fun getGrantedAuthorities(user: User): List<GrantedAuthority>? {
val authorities: MutableList<GrantedAuthority> = ArrayList()
authorities.add(SimpleGrantedAuthority(user.role))
return authorities
}
fun encodePassword(user: User) {
user.password=passwordEncoder.encode(user.password)
}
}
Password encoder
Modifier la configuration de spring security pour définir le hashage du mot de passe (Bcrypt) :
@Configuration
@EnableWebSecurity
class WebSecurityConfig {
@Bean
@Throws(Exception::class)
fun configure(http: HttpSecurity): SecurityFilterChain {
...
@Bean
fun userDetailsService(): UserDetailsService? {
return DbUserService()
}
@Bean
fun bCryptPasswordEncoder(): BCryptPasswordEncoder? {
return BCryptPasswordEncoder()
}
}
Création d'utilisateur
Ne pas oublier de hasher le password User avant création :
@Controller
class InitController {
@Autowired
lateinit var dbUserService: UserDetailsService
@Autowired
lateinit var userRepository: UserRepository
@Autowired
lateinit var roleRepository: RoleRepository
@RequestMapping("/init/{username}")
fun createUser(@PathVariable username:String):String {
val user=User()
user.username=username
user.email=username.lowercase()+"@gmail.com"
user.role="MANAGER"
user.password="1234"
(dbUserService as DbUserService).encodePassword(user)
userRepository.save(user)
return "redirect:/"
}
}
Login form personnalisée
Créer un template pour le formulaire de connexion :
<form method="post" action="./login">
<input type="text" name="username" placeholder="E-mail address or username">
<input type="password" name="password" placeholder="Password">
<button type="submit">Login</button>
</form>
Ajouter une route pour le login
class AppConfig : WebMvcConfigurer {
override fun addViewControllers(registry: ViewControllerRegistry) {
registry.addViewController("/login").setViewName("loginForm")
}
}
Modifier la configuration dans WebSecurityConfig :
@Configuration
@EnableWebSecurity
class WebSecurityConfig {
@Bean
@Throws(Exception::class)
fun configure(http: HttpSecurity): SecurityFilterChain {
...
http.formLogin().loginPage("/login")
return http.build()
}
Connexion à la console H2
Pour autoriser l'accès à la console h2 :
- Autoriser la route vers la console /h2-console
- Autoriser l'utilisation des iframes pour l'adresse locale
- Désactiver la protection csrf
@Configuration
@EnableWebSecurity
class WebSecurityConfig {
@Bean
@Throws(Exception::class)
fun configure(http: HttpSecurity): SecurityFilterChain {
http
....
.authorizeHttpRequests()
.requestMatchers(PathRequest.toH2Console()).permitAll() // (1)
.and()
.headers().frameOptions().sameOrigin() // (2)
.and()
.csrf().disable() // (3)
return http.build()
}
}
Rôles et authorities
Spring Security distingue les authorities, accessible par une simple chaîne (encapsulée ensuite dans un objet de type GrantedAuthority) : USER, ADMIN…
et le rôle équivalent, authority préfixée par ROLE_ : ROLE_USER, ROLE_ADMIN….
Il est possible de faire référence à l'un ou à l'autre indiféremment.
Le rôle des utilisateurs pourra dons être stocké en base de données, sous la forme d'une chaîne, ou d'une liste de chaînes, et il appartient ensuite à UserDetailsService de retourner un User avec une liste de GrantedAuthority :
private fun getGrantedAuthorities(user: User): List<GrantedAuthority>? {
val authorities: MutableList<GrantedAuthority> = ArrayList()
val role: Role = user.role
authorities.add(SimpleGrantedAuthority(role.name))
return authorities
}
Hiérarchie des authorities
Il est possible de définir les authorities de manière hiérarchique :
@Bean
fun roleHierarchy(): RoleHierarchyImpl? {
val roleHierarchy = RoleHierarchyImpl()
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF > ROLE_USER > ROLE_GUEST")
return roleHierarchy
}
@Bean
fun expressionHandler(): DefaultWebSecurityExpressionHandler? {
val expressionHandler = DefaultWebSecurityExpressionHandler()
expressionHandler.setRoleHierarchy(roleHierarchy())
return expressionHandler
}
Les utilisateurs peuvent dans ce cas n'avoir qu'une seule authority, qui leur en confère plusieurs avec la hiérarchie des rôles.
Configuration des rôles
@Configuration
@EnableWebSecurity
class WebSecurityConfig {
@Bean
@Throws(Exception::class)
fun configure(http: HttpSecurity): SecurityFilterChain {
http.authorizeHttpRequests { authorizeHttpRequests ->
authorizeHttpRequests.requestMatchers(AntPathRequestMatcher("/css/**"), AntPathRequestMatcher("/assets/**"), AntPathRequestMatcher("/login/**")).permitAll() // (1)
authorizeHttpRequests.requestMatchers(AntPathRequestMatcher("/admin/**")).hasRole("ROLE_ADMIN") // (2)
authorizeHttpRequests.requestMatchers(AntPathRequestMatcher("/user/**")).hasAuthority("USER") // (3)
authorizeHttpRequests.requestMatchers(AntPathRequestMatcher("/staff/**")).hasAnyAuthority("USER","ADMIN","MANAGER") // (4)
authorizeHttpRequests.anyRequest().authenticated() // (5)
}
return http.build()
}
}
| Méthode | Description |
|---|---|
| 1 - permitAll | Pas de sécurisation sur les urls commençant par /css, /assets, /login |
| 2 - hasRole | Les utilisateurs ayant le rôle ROLE_ADMIN peuvent accéder aux urls commençant par /admin |

