Spring REST security - konfiguracja w javie

0

Robię projekt w Spring 4.1.6, Spring Security 4.0.1, mam Resta który działa, teraz chcę zrobić jego autoryzację, tak sobie pomyślałem że będzie ona oparta o tokeny. Poniżej przedstawiam moje klasy konfiguracyjne:
AppConfig

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "pl.wrweb.springrest")
public class AppConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Bean(name = "messageSource")
    public ReloadableResourceBundleMessageSource getMessageSource() {
        ReloadableResourceBundleMessageSource resource = new ReloadableResourceBundleMessageSource();
        resource.setBasename("classpath:messages");
        resource.setDefaultEncoding("UTF-8");
        return resource;
    }

} 

AppInitializer

public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    public void onStartup(ServletContext container) throws ServletException {


        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(AppConfig.class);
        ctx.setServletContext(container);
        ServletRegistration.Dynamic servlet = container.addServlet("dispatcher", new DispatcherServlet(ctx));
        servlet.setLoadOnStartup(1);
        servlet.addMapping("/");

        container.addFilter("customFilter",  new DelegatingFilterProxy(new CustomFilter())).addMappingForUrlPatterns(null, true, "/*");

    }

    @Override
    protected String[] getServletMappings() {
        return new String[0];
    }


    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[0];
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[0];
    }
} 

HibernateConfiguration

 @Configuration
@EnableTransactionManagement
@ComponentScan({ "pl.wrweb.springrest.configuration" })
@PropertySource(value = { "classpath:application.properties" })
public class HibernateConfiguration {
 
    @Autowired
    private Environment environment;
 
    @Bean
    public LocalSessionFactoryBean sessionFactory() {
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        sessionFactory.setPackagesToScan(new String[] {"pl.wrweb.springrest.entity" });
        sessionFactory.setHibernateProperties(hibernateProperties());
        return sessionFactory;
     }
     
    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName"));
        dataSource.setUrl(environment.getRequiredProperty("jdbc.url"));
        dataSource.setUsername(environment.getRequiredProperty("jdbc.username"));
        dataSource.setPassword(environment.getRequiredProperty("jdbc.password"));
        return dataSource;
    }
     
    private Properties hibernateProperties() {
        Properties properties = new Properties();
        properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
        properties.put("hibernate.hbm2ddl.auto", environment.getRequiredProperty("update"));
        properties.put("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql"));
        return properties;
    }
     
    @Bean
    @Autowired
    public HibernateTransactionManager transactionManager(SessionFactory s) {
       HibernateTransactionManager txManager = new HibernateTransactionManager();
       txManager.setSessionFactory(s);
       return txManager;
    }
}

Dodałem też Filter który sprawdza z jakiego urla przychodzi request i czy ma token, jeśli ma sprawdza czy token poprawny jeśli poprawny generuje kolejny token i dokleja do header responsa.

@Configuration
@EnableWebSecurity
public class CustomFilter implements  Filter {

	@Autowired
	UserService userService;

	
	public void init(FilterConfig fc) throws ServletException {
				
	}

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain fc) throws IOException, ServletException {

    	System.out.println("doFilter");
        HttpServletRequest httpServletRequest = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        if (httpServletRequest.getRequestURI().contains("/employee/")) {
            String token = httpServletRequest.getHeader("x-token");
            System.out.println("token " + token);
            Long userId = TokenUtils.getUserNameFromToken(token);
            System.out.println("ObjectStore.token " + ObjectStore.token);

            if (token!=null) {
                if (token.equals(String.valueOf(ObjectStore.token))) {
                    int index = ObjectStore.token++;
                    ObjectStore.token = index+1;
                    System.out.println("index " +ObjectStore.token + "token " + token);
                    response.setHeader("x-token", String.valueOf(ObjectStore.token));
                    fc.doFilter(req, response);
                } else {
                    response.sendError(HttpStatus.FORBIDDEN.value());
                    fc.doFilter(req, response);
                    return;
                }
            } else {
                response.sendError(HttpStatus.FORBIDDEN.value());
                fc.doFilter(req, response);
                return;
            }
        } else {
            fc.doFilter(req, res);
        }
    }

    public void destroy() {

    }

}
 

To tez działa, ale chcę zrobić tak żeby genrowany token był dodawany do bazy i sprawdzany z tym w bazie, w tym celu próbuje wstrzyknąć:

@Autowired
	UserService userService 

ale jest nullem. Jak można rozwiązać ten problem, czy to co robię jest prawidłowe czy może powinno się to inaczej zrobić?Dodam że zależy mi na konfiguracji w javie.

0

Na szybko to brakuje Ci @ComponentScan(twoj userservice package) w konfiguracji filtra.

0

Zmieniłem na:
@ComponentScan("pl.wrweb.springrest.service")
public class CustomFilter implements Filter {

dalej userService jest null.

0

Jaka może być tego przyczyna?

Na początku wykonujesz następujący kod:

 @Autowired
    UserService userService; 

Po czym już dalej nie używasz swojej zmiennej userService. Innymi słowy, z nią się nic nie dzieje. Więc po wszystkim jest wciąż nullem.

0

Generalnie ja to robiłem mniej więcej w taki sposób:

@Configuration
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private TokenUtils tokenUtils;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterBefore(authenticationFilter(), LogoutFilter.class)
                .addFilterBefore(authenticationTokenProcessingFilter(), LogoutFilter.class)
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/register").permitAll()
                .anyRequest().authenticated()
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(new AuthenticationEntryPoint())
                .and()
                .formLogin()
                .usernameParameter("email")
                .passwordParameter("password")
                .failureHandler(authenticationFailureHandler())
                .successHandler(authenticationSuccessHandler())
                .and()
                .httpBasic();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .eraseCredentials(false)
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler(){
        return new AuthFailureHandler();
    }

    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler(){
        return new AuthSuccessHandler();
    }

    private Filter authenticationTokenProcessingFilter() {
        return new AuthenticationTokenProcessingFilter(userDetailsService, tokenUtils);
    }

    private Filter authenticationFilter() throws Exception {
        AuthenticationFilter authenticationFilter = new AuthenticationFilter();
        authenticationFilter.setAuthenticationManager(authenticationManagerBean());
        authenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
        authenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
        return authenticationFilter;
    }
}

Przykładowy filtr

public class AuthenticationTokenProcessingFilter extends GenericFilterBean {

    private TokenUtils tokenUtils;
    private UserDetailsService userDetailsService;

    public AuthenticationTokenProcessingFilter(UserDetailsService userDetailsService, TokenUtils tokenUtils) {
        this.userDetailsService = userDetailsService;
        this.tokenUtils = tokenUtils;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
            ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;

        String authToken = tokenUtils.extractAuthTokenFromRequest(httpRequest);
        String userName = tokenUtils.getUserNameFromToken(EncryptionUtils.decode(authToken));

        if (userName != null && !isUrlForAuthentication(httpRequest)) {
            UserDetails userDetails = userDetailsService.loadUserByUsername(userName);
            if (tokenUtils.validateToken(authToken, userDetails)) {
                UsernamePasswordAuthenticationToken authentication = getUsernamePasswordAuthenticationToken(userDetails);
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
        chain.doFilter(request, response);
    }

    private UsernamePasswordAuthenticationToken getUsernamePasswordAuthenticationToken(UserDetails userDetails) {
        return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    }
}
0

A mógłbyś zamieścić jeszcze klasę:
AuthenticationFilter i metodę isUrlForAuthentication z AuthenticationTokenProcessingFilter?

0
public class AuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private static final Logger logger = LoggerFactory.getLogger(AuthenticationFilter.class);

    private static final String DEFAULT_FILTER_PROCESSES_URL = "/authenticate";
    private static final String POST = "POST";

    public AuthenticationFilter () {
        super(DEFAULT_FILTER_PROCESSES_URL);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals(POST)) {
            throw new AuthenticationServiceException("Authentication method not supported with HTTP " + request.getMethod());
        }

        AuthRequest authRequest = this.getAuthRequest(request); // klasa która wyciagała z body requesta potrzebny stuff

        UsernamePasswordAuthenticationToken loginRequest = new UsernamePasswordAuthenticationToken( authRequest
                .getEmail(), authRequest.getPassword());

        loginRequest.setDetails(authenticationDetailsSource.buildDetails(request));
        return this.getAuthenticationManager().authenticate(loginRequest);
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        final HttpServletRequest request = (HttpServletRequest) req;
        final HttpServletResponse response = (HttpServletResponse) res;
        if(request.getMethod().equals(POST)) {
            super.doFilter(request, response, chain);
        } else {
            chain.doFilter(request, response);
        }
    }

0

Czyli działa to w taki sposób że za każdym responsem wysyłany jest nowy token?Jak można podpiąć bazę do sprawdzania loginu, hasła i tokena?Czy nie jest potrzebne sprawdzanie token w bazie - token jest zapisywany i pobierany tutaj:
SecurityContextHolder.getContext().setAuthentication(authentication)
?

0

Generalnie to działało bez zapisywania/odczytywania tokenu z bazy. Token był ważny przez jakiś czas, i sam w sobie zawierał informacje jak długo.
Generalnie chciałem Ci tylko pokazać, jak można zrobić to bez @Autowired, bo nie jestem całkiem pewny czy jest możliwe wstrzykniecie tam czegokolwiek, ale moge się mylić.
Możesz stworzyc sobie filtr, który w konstruktorze będzie przyjmował serwis, który będzie pobierał dane z bazy.

0

Ok, a w którym miejscu dodać sprawdzanie emaila i hasła w bazie?Co robi ta metoda getAuthRequest bo jej nie ma.

1

możesz zaimplementować UserDetailsService i przekazać tego beana do AuthenticationManagerBuilder'a + np. do Filtra który ma za zadanie sprawdzić token dla usera

0

Masz na myśli sprawdzanie loginu i hasła w bazie?

0

Nie mam pojęcia, przecież to Ty chcesz sprawdzać coś w bazie

0

właśnie to chcę sprawdzic, przerobiłem trochę klasę AuthenticationFilter:

 public class AuthenticationFilter extends AbstractAuthenticationProcessingFilter {
 
 
    private static final String DEFAULT_FILTER_PROCESSES_URL = "/authenticate";
    private static final String POST = "POST";
 
    public AuthenticationFilter () {
        super(DEFAULT_FILTER_PROCESSES_URL);
    }
 
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals(POST)) {
            throw new AuthenticationServiceException("Authentication method not supported with HTTP " + request.getMethod());
        }
 
        UsernamePasswordAuthenticationToken loginRequest = new UsernamePasswordAuthenticationToken( request.getAttribute("username"), request.getAttribute("password"));
 
        loginRequest.setDetails(authenticationDetailsSource.buildDetails(request));
        return this.getAuthenticationManager().authenticate(loginRequest);
    }
 
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        final HttpServletRequest request = (HttpServletRequest) req;
        final HttpServletResponse response = (HttpServletResponse) res;
        if(request.getMethod().equals(POST)) {
            super.doFilter(request, response, chain);
        } else {
            chain.doFilter(request, response);
        }
    }
}

żeby sprawdzić czy mam takiego usera w bazie uzywam:

@Service
public class UserDetailServiceImpl extends GenericServiceImpl<User> implements UserDetailsService{

	private UserDao userDao;
	
    @Autowired
    public UserDetailServiceImpl(UserDao userDao) {
        super(userDao);
        this.userDao = userDao;
    }
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		User user = userDao.getUserByName(username);
		//UserDetails userDetails = new User();
		return user;
	}

} 

wysyłam posta tak:
http://localhost:8080/authenticate
parametry username:test password:test
i mam 404

0

Z tego co zauważyłem nie żaden doFilter się nie wywołuje.

1 użytkowników online, w tym zalogowanych: 0, gości: 1