IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Services Web Java Discussion :

Configuration Spring Security commune pour plusieurs applis


Sujet :

Services Web Java

  1. #1
    Membre confirmé
    Profil pro
    Inscrit en
    Décembre 2009
    Messages
    173
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2009
    Messages : 173
    Par défaut Configuration Spring Security commune pour plusieurs applis
    Bonjour,
    Je suis en train de faire un projet Spring Boot en version 2.7.4 qui me permettra de faire un jar qui sera ensuite importé dans plusieurs applications.
    Le but étant d'avoir une configuration de sécurité commune pour toutes ces applications plutôt qu'elles fassent chacune leur propre configuration car la base des utilisateurs pour ces applications sera commune et on veut gérer de la même manière la sécurité.

    Voici ce que j'ai initialisé (non encore fini):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
     
    @Configuration("personalSecurityConfig")
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig {
        private static final Logger LOGGER = LoggerFactory.getLogger(SecurityConfig.class);
     
        private static final String[] IGNORING_LIST = {
                // Swagger ui
                "/api/v2/api-docs", "/api/swagger-ui.html", "/api/webjars/springfox-swagger-ui/**",
                "/api/swagger-resources/**",
                // URLs without authentication needed
                "/api/no-security" };
     
        @Value("${jwt.secret}")
        private String jwtSecret;
     
        @Autowired
        private PersoSecurityProperties persoSecurityProperties;
     
        @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            // Do not allow anonymous access to the application (AnonymousAuthenticationFilter will not be set)
            http.anonymous().disable();
     
            http.addFilter(jwtAuthenticationFilter());
            http.addFilter(jwtAuthorizationFilter());
     
            // TODO
     
            // The CSRF token must be set in a cookie so that angular can read it
            http.csrf().csrfTokenRepository(getCsrfTokenRepository());
     
            return http.build();
        }
     
        @Bean
        @Order(99)
        SecurityFilterChain configurePublicEndpoints(HttpSecurity http) throws Exception {
            List<String> publicResources = getPublicResources();
     
            try {
                http.requestMatchers(matcher -> matcher.antMatchers(publicResources.toArray(new String[0])))
                        .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll()).csrf().disable()
                        .requestCache().disable().securityContext().disable().sessionManagement().disable();
            } catch (Exception ex) {
                LOGGER.error("Error applying custom security configuration for public endpoints '{}' ", publicResources,
                        ex);
            }
            return http.build();
        }
     
        /**
         * Permit to indicate to Spring security to not secure the given URI
         * @return
         */
        @Bean
        public WebSecurityCustomizer ignoringCustomizer() {
            return (web) -> web.ignoring().antMatchers(IGNORING_LIST);
        }
     
        @Bean
        public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
            JwtAuthenticationFilter lJwtAuthenticationFilter = new JwtAuthenticationFilter(authManager());
            return lJwtAuthenticationFilter;
        }
     
        @Bean
        public JwtAuthorizationFilter jwtAuthorizationFilter() throws Exception {
            JwtAuthorizationFilter lJwtAuthenticationFilter = new JwtAuthorizationFilter(authManager(), jwtSecret);
            return lJwtAuthenticationFilter;
        }
     
        /**
         * Bean for personal authentication user detail service.
         * @return
         */
        @Bean
        public AuthenticationUserDetailsService<UsernamePasswordAuthenticationToken> personalAuthenticationUserDetailsService() {
            return new PersonalAuthenticationUserDetailsService();
        }
     
        @Autowired
        public void configureAuthenticationProvider(AuthenticationManagerBuilder auth) throws Exception {
            // TODO
        }
     
        /**
         * Custom message resource in order not to be poisoned if application is also defining one.
         * @return
         */
        @Bean("messageResourceSecurityPerso")
        public MessageSource messageResource() {
            ResourceBundleMessageSource messageBundleResrc = new ResourceBundleMessageSource();
            messageBundleResrc.setBasename("messages");
            messageBundleResrc.setDefaultEncoding(StandardCharsets.UTF_8.name());
            return messageBundleResrc;
        }
     
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
     
        /**
         * Permit to have have all URL patters for public resources.
         * @return URL patters for public resources
         */
        private List<String> getPublicResources() {
            List<String> defaultPublicPatters = Arrays.asList("/**/*.css", "/**/*.gif", "/**/*.png", "/**/*.jpeg",
                    "/**/*.jpg");
     
            List<String> publicEnpointsProperties = this.persoSecurityProperties.getPublicEndpoints();
     
            StringJoiner joiner = new StringJoiner("', '", "'", "'");
            List<String> publicEndpoints = Stream.concat(defaultPublicPatters.stream(), publicEnpointsProperties.stream())
                    .filter(Objects::nonNull).filter(StringUtils::hasText).collect(Collectors.toList());
            publicEndpoints.forEach(joiner::add);
            LOGGER.info("Public URL patterns: " + joiner);
            return publicEndpoints;
        }
     
        private CsrfTokenRepository getCsrfTokenRepository() {
            // The cookie can be read by javascript
            CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
     
            // Front-end and back-end are on different context paths so set cookie path to '/'
            tokenRepository.setCookiePath("/");
     
            return tokenRepository;
        }
    }
    Authentication filter (pour gérer la connexion sur l'URI '/api/login' et la création du token JWT):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
     
    public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
        private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
     
        private AuthenticationManager authenticationManager;
     
        private String jwtSecret;
     
        public JwtAuthenticationFilter(AuthenticationManager authenticationManager, String jwtSecret) {
            this.authenticationManager = authenticationManager;
            this.jwtSecret = jwtSecret;
            setFilterProcessesUrl("/api/login");
        }
     
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
                throws AuthenticationException {
            LOGGER.info("JwtAuthenticationFilter.attemptAuthentication()");
     
            try {
                // Get credentials from request
                UserCredentials credentials = new ObjectMapper().readValue(request.getInputStream(), UserCredentials.class);
     
                // Create auth object (contains credentials) which will be used by auth manager
                UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                        credentials.getUsername(), credentials.getPassword(), Collections.emptyList());
     
                // Authentication manager authenticate the user, and use
                // UserDetailsService.loadUserByUsername() method to load the user
                return this.authenticationManager.authenticate(authToken);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
     
        @Override
        protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                FilterChain filterChain, Authentication authentication) {
            LOGGER.info("JwtAuthenticationFilter.successfulAuthentication()");
     
            UserCredentials user = (UserCredentials) authentication.getPrincipal();
     
            // INFO: username is the email
            String token = TokenUtils.buildToken(user.getUsername(), this.jwtSecret);
     
            response.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token);
        }
    }
    Authorization filter (qui gère la vérification du token pour toutes les requêtes sur des URIs sécurisées):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
     
    public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
        private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthorizationFilter.class);
     
        private String jwtSecret;
     
        public JwtAuthorizationFilter(AuthenticationManager authenticationManager, String jwtSecret) {
            super(authenticationManager);
            this.jwtSecret = jwtSecret;
        }
     
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                throws IOException, ServletException {
            LOGGER.info("JwtAuthorizationFilter.doFilterInternal()");
     
            UsernamePasswordAuthenticationToken authentication = parseToken(request);
     
            if (authentication != null) {
                SecurityContextHolder.getContext().setAuthentication(authentication);
            } else {
                SecurityContextHolder.clearContext();
            }
     
            filterChain.doFilter(request, response);
        }
     
        private UsernamePasswordAuthenticationToken parseToken(HttpServletRequest request) {
            String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
            if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
                String token = authorizationHeader.replace("Bearer ", "");
                try {
                    TokenData tokenData = TokenUtils.verifyToken(token, this.jwtSecret);
     
                    // TODO retrieve roles for email with a service to do a query
                    List<GrantedAuthority> authorities = new ArrayList<>();
     
                    return new UsernamePasswordAuthenticationToken(tokenData.getEmail(), null, authorities);
                } catch (JwtException exception) {
                    LOGGER.warn("Parse token in authorization header '{}' failed with following error: {}",
                            authorizationHeader, exception.getMessage());
                }
            }
     
            return null;
        }
     
    }
    Ce que je souhaite est que le front-end appelle '/api/login' avec le username et le password de l'utilisateur ce qui doit permettre de vérifier son mot de passe et créer le token si c'est bon qui sera mis dans le header de la réponse. Ensuite, pour chaque requête sur une URI sécurisée, on vérifiera le token et on récupèrera les informations utilisateur dont ses droits afin d'initialiser le contexte de sécurité Spring (=liste de GrantedAuthority).

    Est-ce que cette logique et ce que j'ai initialisé vous semble correct?

    Egalement, comment puis-je récupérer l'authentication manager dont ont besoin mes filtres car avant on pouvait étendre WebSecurityConfigurerAdapter et surcharger le bean authenticationManagerBean() mais étendre WebSecurityConfigurerAdapter est maintenant déprécié.

    Aussi, je ne sais pas comment gérer un ou plusieurs UserDetailsService (avec gestion du UsernamePasswordAuthenticationToken) qui doivent gérer la vérification du mot de passe pour le premier filtre par exemple. Je ne sais pas si j'en ai besoin d'un aussi pour le deuxième filtre.

    Idem pour la configuration de l'authentication provider (méthode configureAuthenticationProvider() de ma configuration). Je ne sais pas si j'ai des choses à configurer.

    Enfin, je ne sais pas si j'ai besoin d'un token CSRF pour plus de sécurité car il me semblait avoir entendu que ce n'est pas nécessaire quand on a un jeton JWT.

    Pouvez-vous m'aider pour tout ca (désolé ca fait beaucoup d'interrogations) svp?

  2. #2
    Membre habitué
    Homme Profil pro
    Développeur Java
    Inscrit en
    Octobre 2020
    Messages
    9
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Seine et Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Octobre 2020
    Messages : 9
    Par défaut
    dommage que personne n'ait répondu, j'ai exactement le même problème avec spring boot 3!

  3. #3
    Membre très actif

    Profil pro
    Inscrit en
    Janvier 2009
    Messages
    483
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2009
    Messages : 483
    Billets dans le blog
    5
    Par défaut
    Bonjour,

    J'ai déjà eu le problème, et j'avais répondu dans une discussion que j'avais déterré.

    https://www.developpez.net/forums/d2.../#post11875369

    Cordialement.

Discussions similaires

  1. [XSL] Sortie commune pour plusieurs XSL
    Par pataguillon dans le forum XSL/XSLT/XPATH
    Réponses: 7
    Dernier message: 11/09/2007, 11h56
  2. Réponses: 2
    Dernier message: 26/03/2007, 16h07
  3. [C#]Variables communes pour 2 applis.
    Par cjacquel dans le forum Windows Forms
    Réponses: 3
    Dernier message: 24/06/2006, 21h07
  4. [C#]Variables communes pour 2 applis.
    Par cjacquel dans le forum MFC
    Réponses: 0
    Dernier message: 23/06/2006, 17h30
  5. Réponses: 4
    Dernier message: 14/10/2004, 17h36

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo