20
respostas

Spring security

Estou implementado o spring security, na aplicação. Está logando corretamente, logout também. Nas páginas abaixo só entra se tiver logado com o role ROLE_ADMINISTRADOR.

    @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable();
            http.authorizeRequests()
                    // Configuração para todos usuarios do sistema
                    .antMatchers("/error/**", "/resources/**", "/jsCss/**", "/webjars/**", "/recuperarSenha").permitAll()
                    // Configuração para todos usuarios com permissão de
                    // ROLE_ADMINISTRADOR
                    .antMatchers("/codigo/**", "/subCodigo/**", "/tipoCredito/**", "/tipoCancelamento/**", "/usuario/**",
                            "/servico/**", "/notaFiscal/**", "/erroAlerta/**", "/credito/**", "/configuracao/**",
                            "/cnaeSubCodigo/**", "/cnae/**", "/erroAlerta/**", "/atualizacaoMonetariaItem/**",
                            "/atualizacaoMonetaria/**", "/dashboardAdmin/**", "/porcentagemReter/**")
                    .access("hasRole('ROLE_ADMINISTRADOR')")
                    // Configuração para todos usuarios do sistema
                    .and().formLogin().loginPage("/login").successHandler(loginSucessHandler).permitAll().and().rememberMe()
                    // Logout
                    .and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).and().sessionManagement()
                    .maximumSessions(1).maxSessionsPreventsLogin(true).expiredUrl("/login")
                    .sessionRegistry(sessionRegistry());

        }

Estou algumas dúvidas.

  1. está parte .and().sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true).expiredUrl("/login").sessionRegistry(sessionRegistry());, define que um usuário por vez esteja logado, o que está funcionando. Mas o problema é que depois de fazer o logout, não consigo entrar com o mesmo login, ai tenho que parar o servidor, para conseguir logar novamente.

2.Configuração de tempo de sessão, não consegui fazer. Tipo se o usuário não trabalhar com o sistema, ele desloga automaticamente.

20 respostas

Fala Guilherme, tudo bem ?

Tente fazer o seguinte:

  1. Remova a invocação de maxSessionsPreventsLogin(true), deixando assim o padrão false. De uma olhada no que diz a documentação: The advantage of this approach is if a user accidentally does not log out, there is no need for an administrator to intervene or wait till their session expires. Imagino que deva resolver o problema.

  2. Quanto ao tempo de Sessão estamos falando mais próximo do ServletContainer e da especificação. Logo temos que configurar um passo antes do framework.

Exemplos

Configurando para 15 minutos.

web.xml

<session-config>
    <session-timeout>15<session-timeout> 
</session-config>

ou programaticamente:

Listener

public class MyAppHttpSessionListener implements HttpSessionListener {

    @Override
    public void sessionCreated(HttpSessionEvent event) {
        event.getSession().setMaxInactiveInterval(15 * 60); 
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent event) {
        // session destroyed
    }
}

e no web.xml

<webapp>
    <listeners>
        <listener-class>br.com.bla.bla.MyAppHttpSessionListener</listener-class>
    </listeners>
</webapp>

Acho que isso já deve resolver.

Espero ter ajudado. Abraço!

Obrigado pelo retorno

Mas retornando a configuração de maxSessionsPreventsLogin(true) para maxSessionsPreventsLogin(false), o mesmo usuário loga mais de uma vez sem deslogar, e isto não deve ser possível. Por isto o parametro deve ser true e maximumSessions(1), isto que entendi.

Sobre o tempo criei está classe, mas não funciona

import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

public class NotaFiscalEletronicaAppHttpSessionListener implements HttpSessionListener {

    @Override
    public void sessionCreated(HttpSessionEvent event) {
        event.getSession().setMaxInactiveInterval(60);
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent event) {
        // session destroyed
    }
}

Fala Guilherme ..

To dando uma pesquisada por aqui pra ver porque isso não está funcionando.. Assim que tiver uma ideia ou solução posto aqui pra gente.

Mas a classe eu construí NotaFiscalEletronicaAppHttpSessionListener correto ?

Obrigado, estou tentando.

Alguma novidade ?

Fala Guilherme, tudo bem ?

Desculpe a demora no retorno. Mas na verdade, não foi possível fazer funcionar esse mecanismo.

Segui os passos citados na documentação e você pode ver os mesmo acessando este link => https://docs.spring.io/spring-security/site/docs/5.0.0.BUILD-SNAPSHOT/reference/htmlsingle/#ns-concurrent-sessions

Meu código com as configurações de segurança ficou assim:

@EnableWebMvcSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private SessionRegistry sessionRegistry;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/products/form").hasRole("ADMIN")
            .antMatchers("/shopping/**").permitAll()
            .antMatchers(HttpMethod.POST,"/products").hasRole("ADMIN")
            .antMatchers("/products/**").permitAll()
            .anyRequest().authenticated()
        .and()
            .formLogin()
            .loginPage("/login").permitAll()
            .defaultSuccessUrl("/products")
        .and()
            .logout()
            .logoutRequestMatcher(new AntPathRequestMatcher("/logout")).permitAll()
            .logoutSuccessUrl("/login")
        .and()
            .exceptionHandling()
            .accessDeniedPage("/WEB-INF/views/errors/403.jsp")
        .and()
            .sessionManagement()
            .maximumSessions(1)
            .maxSessionsPreventsLogin(true)
            .expiredUrl("/login")
            .sessionRegistry(sessionRegistry);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
            .passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resources/**");
    }

    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }

    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }
}

Registrando também o listener dos eventos de session na classe que serve como initializer:

public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { SecurityConfiguration.class, CasadocodigoWebAppConfiguration.class,
                JpaConfiguration.class };
    }

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

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

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);
        servletContext.addListener(new HttpSessionEventPublisher());
    }
}

Infelizmente não funcionou.. Sequer bloqueou o segundo login. Verifiquei algumas issues no repositório do projeto no github falando em torno desses problemas ao gerenciar as sessoes. Seguindo algumas recomendações registradas nas discussões, sem sucesso:

https://github.com/spring-projects/spring-security/issues/3078

https://github.com/spring-projects/spring-boot/issues/1537

Caso consiga uma solução registro aqui, mas imagino que será necessário depurar as classes do próprio framework em busca de mais detalhes de implementação deles.

Sobre o tempo de sessão imagino que tinhamos discutido antes, imagino que ficou faltando o registro da sua classe que representa o listener NotaFiscalEletronicaAppHttpSessionListener.

Você pode registrar o listener pelo web.xml como foi comentado na primeira reposta, ou registrar programaticamente como feito no exemplo acima:

public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    // ...
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);
        servletContext.addListener(new HttpSessionEventPublisher());
        servletContext.addListener(new NotaFiscalEletronicaAppHttpSessionListener());
    }
}

Espero ter ajudado. Abraço!

Aqui só entra uma vez com o mesmo login.

Se não retorna esta mensagem:

AI para voltar entrar com o mesmo login, tenho que reiniciar o servidor.

Sobre o tempo de sessão vou fazer o que vc disse.

Obrigado.

Deu erro ao iniciar com a configuração do tempo

https://gist.github.com/anonymous/609b4600d8b91aaa21569956dbd8d669

Fala Guilherme ...

Não dá pra ver qual foi o erro nos logs apresentados .. pode postar a stack completa pra gente ver ?

Desculpe achei que tinha ido

https://gist.github.com/anonymous/f6f07b3b2ef32fa7c7c3694de93ea178

Fala Guilherme, tudo bem ?

Está dando NullPointerException no código do SpringWebInitializer.

NullPointerException
at br.com.netsoft.SpringWebInitializer.onStartup(SpringWebInitializer.java:24)

linha 24

Como está o código nessa linha? Dê uma olhada nisso..

Linha 24 desta classe está assim: dispatcher.setAsyncSupported(true);

Esta classe está assim

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

public class SpringWebInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
        applicationContext.scan(SpringWebInitializer.class.getPackage().getName());
        servletContext.addListener(new ContextLoaderListener(applicationContext));
        servletContext.addListener(new RequestContextListener());
        ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher",
                dispatcherServlet(applicationContext));
        dispatcher.setAsyncSupported(true);
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");
    }

    private DispatcherServlet dispatcherServlet(WebApplicationContext applicationContext) {
        return new DispatcherServlet(applicationContext);
    }
}

Fala Guilherme,

Repare que esse deve ser outro problema. Se houve uma NullPointerException nessa linha ocorreu problema na verdade com a adição da servlet ao contexto.

Dê uma olhada nesse trecho da documentação => https://docs.spring.io/spring/docs/4.3.14.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/#mvc-servlet .. principalmente onde ele exemplifica a montagem do contexto usando a implementação de initilizer que o spring já traz (estendendo a classe AbstractAnnotationConfigDispatcherServletInitializer). Assim você não precisa ter trabalho de montar o contexto na mão.

Subiu sem erros agora

Alterei a classe conforme entendimento da documentação que me passou.

@Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
        applicationContext.scan(SpringWebInitializer.class.getPackage().getName());
        servletContext.addListener(new ContextLoaderListener(applicationContext));
        servletContext.addListener(new RequestContextListener());
        ServletRegistration.Dynamic dispatcher = servletContext.addServlet("nota-fiscal-servico-web",
                dispatcherServlet(applicationContext));
        dispatcher.setAsyncSupported(true);
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/nota-fiscal-servico-web/*");
    }

    private DispatcherServlet dispatcherServlet(WebApplicationContext applicationContext) {
        return new DispatcherServlet(applicationContext);
    }

Agora o tempo de sessão tem que funcionar correto ?

Deveria .. certifique-se de registrar o HttpSessionListener criado => NotaFiscalEletronicaAppHttpSessionListener

O servidor sobe sem erros no console

Mas tem algo de de errado. Alterei esta classe também

package br.com.netsoft;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

import br.com.netsoft.config.NotaFiscalEletronicaSessionListener;

public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

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

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

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/nota-fiscal-servico-web/*" };
    }

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);
        servletContext.addListener(new HttpSessionEventPublisher());
        servletContext.addListener(new NotaFiscalEletronicaSessionListener());
    }
}

Ao ir na aplicação no navegado fica assim:

Sem esta nova configuração consegui acessar normalmente.

Fala Guilherme,

Ao partir para essa configuração você precisa de alguns outros passos (que na verdade são parecidos com os que eram feitos na mão). Por exemplo, precisamos passar para o Spring classes de configuração onde mapeamos seus beans e usamos suas Annotations para: escanear componentes (@ComponentScan(basePackages={"br.com.meudominio.meuprojeto"})), habilitar recursos (@EnableWebMvc, @EnableAsync), etc.

Dê uma olhada nessa task do curso de Spring da Alura (https://cursos.alura.com.br/course/spring-mvc-1-criando-aplicacoes-web/task/11390) onde é feito exatamente esse processo.

A documentação na sequência também mostra como obter a configuração programática a partir da extensão da ultima classe citada.

Entendi.

Então eu vi e fiz este curso a um tempo atrás. Mas pelo que entendi os projeto estão diferentes, pois utilizo o thymeleaf. Estas configurações estão em outros arquivos próprios do thymeleaf.

Vou colocar eles aqui.

package br.com.netsoft.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.thymeleaf.ITemplateEngine;
import org.thymeleaf.extras.springsecurity4.dialect.SpringSecurityDialect;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ITemplateResolver;

import nz.net.ultraq.thymeleaf.LayoutDialect;

@Configuration
public class ThymeleafConfig {

    @Autowired
    private ApplicationContext applicationContext;

    @Bean
    public ViewResolver thymeleafViewResolver(ITemplateEngine templateEngine) {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setTemplateEngine(templateEngine);
        viewResolver.setCharacterEncoding("UTF-8");
        return viewResolver;
    }

    @Bean
    public ITemplateEngine templateEngine(ITemplateResolver templateResolver) {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
        templateEngine.setEnableSpringELCompiler(true);
        templateEngine.addDialect(new LayoutDialect());
        templateEngine.addDialect(new SpringSecurityDialect());
        return templateEngine;
    }

    @Bean
    public ITemplateResolver templateResolver() {
        SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
        templateResolver.setApplicationContext(this.applicationContext);
        templateResolver.setTemplateMode(TemplateMode.HTML);
        templateResolver.setPrefix("/WEB-INF/paginas/");
        templateResolver.setSuffix(".html");
        templateResolver.setCharacterEncoding("UTF-8");
        templateResolver.setCacheable(false);
        return templateResolver;
    }
}