2016-04-08 1 views
1

Je construis l'API de repos en utilisant Spring Boot v1.3.3. L'API est sécurisée par Spring Security. J'ai implémenté le service de détails d'utilisateur personnalisé pour avoir un principal personnalisé dans le contexte d'authentification. J'ai eu besoin de partager des sessions d'API avec d'autres applications Spring, j'ai donc choisi d'implémenter Spring Session avec le serveur Redis dans mon application en utilisant ce tutoriel docs.spring.io/spring-session/docs/current/reference/html5/ guides/security.html. Malheureusement, cela a provoqué l'arrêt du fonctionnement d'Authentication Principal. Lorsque j'essaie d'obtenir le principal actuel soit par annotation @AuthenticationPrincipal CustomUserDetails user ou par SecurityContextHolder.getContext().getAuthentication().getPrincipal() il renvoie mes détails d'utilisateur personnalisés, mais avec Id = 0 et tous les champs mis à null (screen from debugging). Je ne peux même pas obtenir le nom d'utilisateur de SecurityContextHolder.getContext().getAuthentication().getName().Authentification Principal est vide lors de l'utilisation Spring Session Redis

Après avoir commenté le code Redis et la dépendance maven il fonctionne (see debug screen). Comment le faire fonctionner avec Spring Session et le serveur Redis?

Voici un code de l'application:

Quelques exemples méthode pour vérifier Principal

@RequestMapping(value = "/status", method = RequestMethod.GET) 
public StatusData status(@AuthenticationPrincipal CustomUserDetails user) { 
    User user2 = (CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 
    if (user != null) { 
     String name = user.getUsername(); 
     return new StatusData(name); 
    } else return new StatusData(null); 
} 

application et configuration Redis:

@Configuration 
@EnableRedisHttpSession 
public class AppConfig { 

    @Bean 
    public JedisConnectionFactory connectionFactory() { 
     return new JedisConnectionFactory(); 
    } 

    @Bean 
    public CookieSerializer cookieSerializer() { 
     DefaultCookieSerializer serializer = new DefaultCookieSerializer(); 
     serializer.setCookieName("JSESSIONID"); 
     serializer.setCookiePath("/"); 
     serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$"); 
     return serializer; 
    } 

    @Bean 
    public ShaPasswordEncoder shaEncoder() { 
     return new ShaPasswordEncoder(256); 
    } 

    @Bean 
    public BCryptPasswordEncoder bCryptPasswordEncoder() { 
     return new BCryptPasswordEncoder(); 
    } 

    @Bean(name = "messageSource") 
    public ResourceBundleMessageSource messageSource() { 
     ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource(); 
     resourceBundleMessageSource.setBasename("messages/messages"); 
     return resourceBundleMessageSource; 
    } 

    @Bean 
    public Validator basicValidator() { 
     LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); 
     validator.setValidationMessageSource(messageSource()); 
     return validator; 
    } 

    public AppConfig() { 
     DateTimeZone.setDefault(DateTimeZone.UTC); 
    } 
} 

Initializer (utilisé pour Redis Session)

public class Initializer extends AbstractHttpSessionApplicationInitializer { 

} 

SecurityInitializer (utilisé pour la session Redis)

public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer { 

    public SecurityInitializer() { 
     super(WebSecurityConfig.class, AppConfig.class); 
    } 
} 

WebSecurityConfig (printemps config de sécurité)

@Configuration 
@EnableWebSecurity 
//@EnableWebMvcSecurity 
@ComponentScan(basePackageClasses = {UserRepository.class, CustomUserDetailsService.class}) 
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 

    @Autowired 
    private DataSource dataSource; 

    @Autowired 
    private UserDetailsService customUserDetailsService; 

    @Autowired 
    private HttpAuthenticationEntryPoint httpAuthenticationEntryPoint; 

    @Autowired 
    private AuthSuccessHandler authSuccessHandler; 

    @Autowired 
    private AuthFailureHandler authFailureHandler; 

    @Autowired 
    private HttpLogoutSuccessHandler logoutSuccessHandler; 

    @Autowired 
    private BCryptPasswordEncoder bCryptPasswordEncoder; 

    /** 
    * Persistent token repository stored in database. Used for remember me feature. 
    */ 
    @Bean 
    public PersistentTokenRepository tokenRepository() { 
     JdbcTokenRepositoryImpl db = new JdbcTokenRepositoryImpl(); 
     db.setDataSource(dataSource); 
     return db; 
    } 

    /** 
    * Enable always remember feature. 
    */ 
    @Bean 
    public AbstractRememberMeServices rememberMeServices() { 
     CustomTokenPersistentRememberMeServices rememberMeServices = new CustomTokenPersistentRememberMeServices("xxx", customUserDetailsService, tokenRepository()); 
     rememberMeServices.setAlwaysRemember(true); 
     rememberMeServices.setTokenValiditySeconds(1209600); 
     return rememberMeServices; 
    } 

    /** 
    * Configure spring security to use in REST API. 
    * Set handlers to immediately return HTTP status codes. 
    * Enable remember me tokens. 
    */ 
    @Override 
    protected void configure(HttpSecurity http) throws Exception { 
     http 
       .csrf().disable() 
       .exceptionHandling() 
       .authenticationEntryPoint(httpAuthenticationEntryPoint) 
       .and() 
       .authorizeRequests() 
       .antMatchers("/cookie", "/register", "/redirect/**", "/track/**") 
       .permitAll() 
       .anyRequest().authenticated() 
       .and() 
       .formLogin() 
       .loginPage("/login") 
       .permitAll() 
       .successHandler(authSuccessHandler) 
       .failureHandler(authFailureHandler) 
       .and() 
       .logout() 
       .permitAll().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler) 
       .and() 
       .rememberMe().rememberMeServices(rememberMeServices()) 
       .and() 
       .headers() 
       .addHeaderWriter(new HeaderWriter() { 
        /** 
        * Header to allow access from javascript AJAX in chrome extension. 
        */ 
        @Override 
        public void writeHeaders(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { 
         String corsUrl = "https://mail.google.com"; 
         if (httpServletRequest.getHeader("Origin") != null && httpServletRequest.getHeader("Origin").equals(corsUrl)) { 
          httpServletResponse.setHeader("Access-Control-Allow-Origin", "https://mail.google.com"); 
          httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS"); 
          httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true"); 
          httpServletResponse.setHeader("Access-Control-Expose-Headers", "Location"); 
         } 
        } 
       }); 
    } 

    /** 
    * Set custom user details service to allow for store custom user details and set password encoder to BCrypt. 
    */ 
    @Autowired 
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 
     auth 
       .userDetailsService(customUserDetailsService).passwordEncoder(bCryptPasswordEncoder); 
    } 
} 

dépendances Maven

<dependencies> 
    <dependency> 
     <groupId>${project.groupId}</groupId> 
     <artifactId>models</artifactId> 
     <version>${project.version}</version> 
    </dependency> 
    <dependency> 
     <groupId>org.springframework.boot</groupId> 
     <artifactId>spring-boot-starter-web</artifactId> 
    </dependency> 
    <dependency> 
     <groupId>org.springframework.boot</groupId> 
     <artifactId>spring-boot-starter-data-jpa</artifactId> 
    </dependency> 
    <dependency> 
     <groupId>org.springframework.boot</groupId> 
     <artifactId>spring-boot-starter-security</artifactId> 
    </dependency> 
    <dependency> 
     <groupId>org.hibernate</groupId> 
     <artifactId>hibernate-validator</artifactId> 
     <version>5.2.3.Final</version> 
    </dependency> 
    <dependency> 
     <groupId>joda-time</groupId> 
     <artifactId>joda-time</artifactId> 
    </dependency> 
    <dependency> 
     <groupId>org.jadira.usertype</groupId> 
     <artifactId>usertype.core</artifactId> 
     <version>3.1.0.CR1</version> 
    </dependency> 
    <dependency> 
     <groupId>com.fasterxml.jackson.jaxrs</groupId> 
     <artifactId>jackson-jaxrs-json-provider</artifactId> 
     <version>2.2.1</version> 
    </dependency> 
    <dependency> 
     <groupId>com.fasterxml.jackson.datatype</groupId> 
     <artifactId>jackson-datatype-joda</artifactId> 
    </dependency> 
    <dependency> 
     <groupId>com.maxmind.geoip2</groupId> 
     <artifactId>geoip2</artifactId> 
     <version>2.6.0</version> 
    </dependency> 
    <dependency> 
     <groupId>com.ganyo</groupId> 
     <artifactId>gcm-server</artifactId> 
     <version>1.0.2</version> 
    </dependency> 
    <dependency> 
     <groupId>org.springframework.session</groupId> 
     <artifactId>spring-session</artifactId> 
     <version>1.1.1.RELEASE</version> 
    </dependency> 
    <dependency> 
     <groupId>org.springframework.boot</groupId> 
     <artifactId>spring-boot-starter-redis</artifactId> 
    </dependency> 
    <dependency> 
     <groupId>mysql</groupId> 
     <artifactId>mysql-connector-java</artifactId> 
     <scope>runtime</scope> 
    </dependency> 
    <dependency> 
     <groupId>org.springframework.boot</groupId> 
     <artifactId>spring-boot-starter-test</artifactId> 
     <scope>test</scope> 
    </dependency> 
    <dependency> 
     <groupId>org.springframework.security</groupId> 
     <artifactId>spring-security-test</artifactId> 
     <version>4.0.4.RELEASE</version> 
     <scope>test</scope> 
    </dependency> 
    <dependency> 
     <groupId>com.jayway.jsonpath</groupId> 
     <artifactId>json-path</artifactId> 
     <scope>test</scope> 
    </dependency> 
</dependencies> 

Répondre

2

J'ai résolu ce problème. Il s'est avéré que Spring-Session sérialisait l'objet Principal. Ma mise en œuvre personnalisée de UserDetails était sous-classe de la classe Hibernate modèle User. Je l'ai résolu en mettant en œuvre l'interface Serializable dans mon modèle personnalisé UserDetails, User et toutes les classes utilisées dans ce modèle.

+0

J'ai aussi un UserDetails sur mesure, et je l'ai changé pour mettre en œuvre Serializable (MyUserDetails public class étend org.springframework.security.core.userdetails.User implémente Serializable).J'utilise Spring Session avec le backend JDBC, mais la colonne nom_principal est NULL. Étrange. – yglodt

+0

Je suis d'accord avec @yglodt, ce n'est peut-être pas la bonne réponse. Si vous vérifiez à la classe 'org.springframework.security.core.userdetails.UserDetails', il étend déjà l'interface' java.io.Serializable' – BigDong

1

Pour le faire fonctionner dans mon cas j'avais aussi bien pour vous assurer que les filtres de servlets ont été mis en place dans le bon ordre.

Pour moi, ce fut:

... 
<filter-name>CharacterEncodingFilter</filter-name> 
... 
<filter-name>springSessionRepositoryFilter</filter-name> 
... 
<filter-name>springSecurityFilterChain</filter-name> 
... 
<filter-name>csrfFilter</filter-name> 
... 

Après cela, le principal était pas vide plus.