Manage or customize the user session in Spring-managed JavaSE application

0

I would like to know how to manage or customize the user session in the JavaSE application (GUI / Desktop / SWING / thinClient) managed by SpringSecurity ??! For example, how could I configure TimeOut in a JavaSE application ??! Here's the 'applicationContext-security.xml':

    <beans:beans xmlns="http://www.springframework.org/schema/security"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:beans="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!-- -2.5   -3.2    -->
<!--    <context:annotation-config/>-->
    <beans:bean class="org.springframework.security.authentication.event.LoggerListener" />
    <beans:bean class="org.springframework.security.access.event.LoggerListener" />
  <beans:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" />
      <context:component-scan base-package="br.com.vitoria.springSecJSR250" />
        <global-method-security jsr250-annotations='enabled' authentication-manager-ref="authManager"/>
        <http auto-config="true" create-session="always" authentication-manager-ref="authManager"><!--     //TODO: FIXME: costumizar User Session in JavaSE... App-->       
            <session-management >
                <concurrency-control session-registry-alias="sessionRegistry" />
<!--                    <session-timeout>20</session-timeout> expired-session-strategy-ref=""
                </concurrency-control>-->
            </session-management>
        </http><!---->
    <authentication-manager id="authManager" ><!--alias='authManager'-->
        <!--    <authentication-provider ref="testingAuthenticationProvider">->>AnonymousAuthenticationProvider gerada p/ auto-config="true"
        <password-encoder hash="plaintext" />
        <jdbc-user-service data-source-ref="dataSource"
            users-by-username-query="SELECT email as username, senha as password, 'true' as enable FROM usuario WHERE email = ?"
            authorities-by-username-query="SELECT u.email as username, r.nome as authority FROM usuario u, regra r WHERE u.regra_id = r.id AND email = ?"/>
            </authentication-provider>-->
    </authentication-manager>

... or DomainService:

@PermitAll 
@Service //@Component 
public class Jsr250AnnotationDomainBizzService implements BusinessService /**/{
    @RolesAllowed("ROLE_USER") 
    @Override
    public void someUserMethod1() { 
        System.out.println("someUserMethod1(só printa SE O USUÁRIO TIVER O perfil 'ROLE_USER'!)");
    } 
...
}

... exercising (or testing):

@RunWith(value = SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/applicationContext.xml")
@org.junit.FixMethodOrder(MethodSorters.NAME_ASCENDING)//->>Ordem: a1, a2, a3, ... 
public class Jsr250AnnotationDomainBizzServiceTest {
    @Inject // @Resource // @Autowired
    private /*static*/ BusinessService target; 
    @Inject // 
    private ApplicationEventPublisher  _eventPublisher; 
    @Inject // 
    private AuthenticationProvider  _authProvider; 

    @After 
    public void clearSecurityContext() { 
        SecurityContextHolder.clearContext(); 
    } 

@Inject
    private SessionRegistry sessionRegistry;
    @Test (expected=/*AuthenticationFailureProviderNotFoundEvent*/ProviderNotFoundException.class)
    public void a3targetShouldPreventInvocationWithCorrectRoleButNoLongerAuthenticated() { 
        UsernamePasswordAuthenticationToken token
            = new UsernamePasswordAuthenticationToken("Test", "Password", AuthorityUtils.createAuthorityList("ROLE_USER")); 
    //    Authentication auth = _authProvider.authenticate(token);
        SecurityContextHolder.getContext().setAuthentication(token); //auth 
//        _eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(auth, this.getClass() ) ); // token
    /*    SecurityContextHolder.getContext().getAuthentication()*/token.setAuthenticated(false)/*.eraseCredentials()*/;
        SecurityContext sc = SecurityContextHolder.getContext();  

        Integer idSessao = SessionRegistryUtil.obtainSessionIdFromAuthentication(sc.getAuthentication());  

        SessionInformation[] sessoes = this.getAllSessions(SessionRegistryUtils.obtainPrincipalFromAuthentication(sc.getAuthentication()), true);    sessionRegistry.getAllSessions(/*auth.getName()*/"Test", true ).get(0).expireNow();
        target.someUserMethod1(); //<<<--incide aqui o teste!!!
    } 
}

In short, how can I set the TimeOut and, from the event of the Session expire notify LogOut in a JavaSE application (forcing the LogIn modal screen) ??!

    
asked by anonymous 10.11.2017 / 03:40

1 answer

0

Some time ago I came up with a reasonable solution, at some extent. I publish now because I think it might be useful for someone else. (Note: the solution was implemented using SpringSecurity3.2.x.)

<beans:beans xmlns="http://www.springframework.org/schema/security"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns:beans="http://www.springframework.org/schema/beans"
   xmlns:context="http://www.springframework.org/schema/context"
         xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!-- -2.5   -3.2  -3.0.4  -->
<!--    <context:annotation-config/>-->
        <context:component-scan base-package="br.com.vitoria.springSecJSR250" />
    <beans:bean id="testingAuthenticationProvider" 
                class="org.springframework.security.authentication.TestingAuthenticationProvider" autowire-candidate="false">
    <!--    <custom-authentication-provider />->>por estar http auto-config="true", este NÃO é necessário: AnonymousAuthenticationProvider é injetado em ser lugar-->
    </beans:bean>
    <beans:bean class="org.springframework.security.authentication.event.LoggerListener" />
    <beans:bean class="org.springframework.security.access.event.LoggerListener" />
  <beans:bean id="sessionRegistryimpl" class="org.springframework.security.core.session.SessionRegistryImpl" />
  <beans:bean id="sessionRegistry" class="br.com.vitoria.springSecJSR250.config.SessionRegistryApplicationListener" />
        <global-method-security jsr250-annotations='enabled'  access-decision-manager-ref="accessDecisionManager" authentication-manager-ref="authManager"/>

    <!-- Voter customizado -->
    <beans:bean id="customVoter" class="br.com.vitoria.springSecJSR250.config.CustomVoter" />
    <!-- Define AccessDesisionManager como UnanimousBased e coloca o Voter na lista -->
    <beans:bean id="accessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased"><!-- access. -->
        <beans:property name="allowIfAllAbstainDecisions"><beans:value>true</beans:value></beans:property>
        <beans:constructor-arg><!-- SpringSec 3.X+ <beans:property name="decisionVoters">--> 
            <beans:list>
                <beans:bean class="org.springframework.security.access.vote.AuthenticatedVoter" /><!-- access.--> 
                <beans:bean class="org.springframework.security.access.vote.RoleVoter"/><!-- access.--> 
                <beans:ref bean="customVoter" />
            </beans:list>
        </beans:constructor-arg>
    </beans:bean>
    <authentication-manager alias='authManager' ><!--id="authManager" -->
            <authentication-provider><!-- user-service-ref="" ref="testingAuthenticationProvider"->>AnonymousAuthenticationProvider gerada p/ auto-config="true" -->
                <user-service>
                    <user name="Test" password="Password" authorities="ROLE_USER"/>
                    <user name="admin" password="admin" authorities="ROLE_ADMIN"/>
                </user-service>
            </authentication-provider>
        </authentication-manager>
</beans:beans>

.. the class AuthenticationToken that keeps the User Session data:

public class StandardSessionAuthenticationToken/*<C extends SecurityContext, T extends SessionStandaloneInformation>*/
                                extends UsernamePasswordAuthenticationToken implements AuthenticationDetailsSource {
//    private SessionStandaloneInformation details;
    protected Collection</*? extends */GrantedAuthority> authorities;

    public StandardSessionAuthenticationToken(Object principal, Object credentials, String sessionId, Date lastRequest) {
        this(principal, credentials, sessionId, lastRequest, null);
    }

    public StandardSessionAuthenticationToken(Object principal, Object credentials, String sessionId, Date lastRequest
                                          , Collection</*? extends */GrantedAuthority> authorities) {
    super(principal, credentials, authorities);
        this.setDetails(new SessionStandaloneInformation(principal, sessionId, lastRequest) );
        /*this.*/setAuthorities(authorities);
        this.setAuthorities(authorities);
    }

    /**
     *
     * @param context
     * @return
     */
    @Override
    public Object buildDetails(Object context) {
        final Object principal = ( (/*C*/SecurityContext)context).getAuthentication().getPrincipal(); //To change body of generated methods, choose Tools | Templates.
        this.setDetails(new SessionStandaloneInformation(principal, ( (SessionStandaloneInformation)getDetails() ).getSessionId(), new Date() ) );

        return this.getDetails();
    }

//    @Override
//    public boolean implies(Subject subject) {
//        return super.implies(subject); //
//    }
    /**
     * just for back-compatibility purposes (SpringSecurity 2 / SpringSec 3.X+).
     */
    public static class SessionStandaloneInformation extends SessionInformation implements SessionIdentifierAware {

        public SessionStandaloneInformation(Object principal, String sessionId, Date lastRequest) {
            super(principal, sessionId, lastRequest);
        }
    }

    /**
     * @param authorities the authorities to set
     */
    public void setAuthorities(Collection</*? extends */GrantedAuthority> authorities) {
        this.authorities = authorities;
    }
    /**
     * @return the authorities
     */
    @Override
    public Collection</*? extends */GrantedAuthority> getAuthorities() {
        return  this.authorities;
    }

    /** 
     *(se estamos numa instância assignable StandardSessionAuthenticationToken, obviamente o tipo de:
     * @return SessionStandaloneInformation
     */
    @Override
    public SessionStandaloneInformation getDetails() {
        return (SessionStandaloneInformation)super.getDetails();
    }
}

.. the class AuthenticationToken that keeps the (session) expiring info:

import java.util.Collection;
import java.util.Date;
import org.springframework.security.core.GrantedAuthority;

/**
 *
 * @author Derlon.Aliendres
 */
public class UsernamePasswordWithTimeoutAuthenticationToken extends StandardSessionAuthenticationToken
                                                                   /* UsernamePasswordAuthenticationToken */{
    private String timeout = null;

    public UsernamePasswordWithTimeoutAuthenticationToken(Object principal, Object credentials, String timeOut
                                                          , String sessionId, Date lastRequest) {
        this(principal, credentials, sessionId, lastRequest, timeOut, null);
    //    this.timeout = null;
    }

    /**
     *
     * @param principal the value of principal
     * @param credentials the value of credentials
     * @param sessionId the value of sessionId
     * @param lastRequest the value of lastRequest
     * @param timeOut the value of timeOut
     * @param authorities the value of authorities
     */
    public UsernamePasswordWithTimeoutAuthenticationToken(Object principal, Object credentials, String sessionId, Date lastRequest
                                                          , String timeOut, Collection</*? extends */GrantedAuthority> authorities) {
        super(principal, credentials, sessionId, lastRequest, authorities);
        this.timeout = timeOut;
    }

    public String getTimeout() {
            return timeout;
    }   

    public Boolean jahExpirouSessionDa() {
        Boolean result;
        final SessionStandaloneInformation sessnInfo = /*(SessionStandaloneInformation)*/(this.getDetails() );
        final String sessionId = sessnInfo.getSessionId();
        final Date datimeLastRequest = sessnInfo.getLastRequest();
        final long timeOutTime = extractNonceValue(this.getTimeout() );
        if ( ( (new Date().getTime() ) - datimeLastRequest.getTime() ) > timeOutTime) {
            //attr.equals("ROLE_USER")
            //ROLE_USER é a ROLE de usuário, logado, então retorna true sempre
            result = false;
        } else {
            //chama uma lógica específica que verifica se o usuário possui permissão no contexto atual
            result = true; // gerenciadorPermissao.verificarPermissao(variavelSessao, attr)
        }
        return result;
    }

    private long extractNonceValue(String timeout) {
        return /*getLong*/ Long.valueOf(timeout);
    }

}

... the test:

/**
 *
 * @author derlon.aliendres
 */
@RunWith(value = SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/applicationContext.xml")
@org.junit.FixMethodOrder(MethodSorters.NAME_ASCENDING)//->>Ordem: a1, a2, a3, ... 
public class Jsr250AnnotationDomainBizzServiceTest {

//    private static InMemoryXmlApplicationContext appContext; 
    @Inject // @Resource // @Autowired
    private /*static*/ BusinessService target; 
    @Inject // 
    private ApplicationEventPublisher  _eventPublisher; 
    @Inject // 
    private DaoAuthenticationProvider  daoAuthenticationProvider; // AuthenticationProvider _authProvider

    @BeforeClass
    public static void setUpClass() {
    //    SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_GLOBAL);
    }

    @Before 
    public void loadAppContext() { 
    } 

    @After 
    public void clearSecurityContext() { 
        SecurityContextHolder.clearContext(); 
    } 

    @AfterClass
    public static void tearDownClass() {
//        if (appContext != null) { 
//            appContext.close();//<<-Como o contexto é carregado só 1x(no setUpClass) só pode ser encerrado aki! 
//        } 
    }


    @Test(expected=AuthenticationCredentialsNotFoundException.class) 
    public void a1targetShouldPreventProtectedMethodInvocationWithNoContext() { 
        target.someUserMethod1(); //<<<--incide aqui o teste!!!
        System.out.println("Falha na Autenticação: fluxo exec BLOQUEADO!!!)");
    } 

    @Test 
    public void a2permitAllShouldBeDefaultAttribute() { 
        UsernamePasswordAuthenticationToken token
            = new UsernamePasswordAuthenticationToken("Test", "Password", AuthorityUtils.createAuthorityList("ROLE_USER") ); 
        SecurityContextHolder.getContext().setAuthentication(token);// SecurityContextHolder.getContextHolderStrategy().getContext().getAuthentication().
//        LoggerListener listener = new LoggerListener();listener.onApplicationEvent(event);
        _eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(token, this.getClass() ) );

        target.someOther(0); //<<<--incide aqui o teste!!!
    } 

    @Test 
    public void a3targetShouldAllowProtectedMethodInvocationWithCorrectRole() { 
        UsernamePasswordAuthenticationToken token
            = new UsernamePasswordAuthenticationToken("Test", "Password", AuthorityUtils.createAuthorityList("ROLE_USER")); 
        SecurityContextHolder.getContext().setAuthentication(token); 
        _eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(token, this.getClass() ) );

        target.someUserMethod1(); //<<<--incide aqui o teste!!!
    } 

    @Test() // expected=AccountExpiredException/*AccessDeniedException*/.class
    public void a4targetShouldAllowInvocationWithCorrectRoleAndNotTimedoutAuthentication() { 
        final String timeOut = "0080";
        final Date datimeLastRequest = new Date()/*, AuthorityUtils.stringArrayToAuthorityArray(new String[]{"ROLE_USER"}) */;
        UsernamePasswordWithTimeoutAuthenticationToken token
                = new UsernamePasswordWithTimeoutAuthenticationToken("Test", "Password", timeOut, "001", datimeLastRequest); 
//                = new UsernamePasswordWithTimeoutAuthenticationToken("Test", "Password", "001", datimeLastRequest
//                                                                     , timeOut, AuthorityUtils.createAuthorityList("ROLE_USER") ); 
        Authentication auth = /*(StandardSessionAuthenticationToken)*/daoAuthenticationProvider.authenticate(token); // 
        token.setAuthorities(new ArrayList<GrantedAuthority>(auth.getAuthorities() ) );
        SecurityContextHolder.getContext().setAuthentication( /*(UsernamePasswordWithTimeoutAuthenticationToken*/token); //auth.getDetails() 
        _eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(token, this.getClass() ) ); // token
        target.someUserMethod1(); //<<<--incide aqui o teste!!!
    } 

//    @Inject
//    private SessionRegistry sessionRegistry;
    @Test(expected=AccountExpiredException/*AccessDeniedException*/.class) // ProviderNotFoundException
    public void a4targetShouldPreventInvocationWithCorrectRoleButNoLongerAuthenticated() { 
        final String timeOut = "0010";
        final Date datimeLastRequest = new Date()/*, AuthorityUtils.stringArrayToAuthorityArray(new String[]{"ROLE_USER"}) */;
        UsernamePasswordWithTimeoutAuthenticationToken token
                = new UsernamePasswordWithTimeoutAuthenticationToken("Test", "Password", timeOut, "001", datimeLastRequest); 
//                = new UsernamePasswordWithTimeoutAuthenticationToken("Test", "Password", "001", datimeLastRequest
//                                                                     , timeOut, AuthorityUtils.createAuthorityList("ROLE_USER") ); 
        Authentication auth = /*(StandardSessionAuthenticationToken)*/daoAuthenticationProvider.authenticate(token); // 
        token.setAuthorities(new ArrayList<GrantedAuthority>(auth.getAuthorities() ) );
        SecurityContextHolder.getContext().setAuthentication( /*(UsernamePasswordWithTimeoutAuthenticationToken*/token); //auth.getDetails() 
        _eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(token, this.getClass() ) ); // 
//    /*    SecurityContextHolder.getContext().getAuthentication()*/token.setAuthenticated(false)/*.eraseCredentials()*/;
        try {
            /*new */Thread.sleep(30/*00*/);//<<<--Idle time of user Session Simulation
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//        SecurityContext sc = SecurityContextHolder.getContext();
//
//        String idSessao = SessionRegistryUtils.obtainSessionIdFromAuthentication(sc.getAuthentication());
//        sessionRegistry.registerNewSession(idSessao, auth.getPrincipal())/*getName()*//*.get(0).expireNow()*/;
//    //    System.out.println("SessionInformation na Autenticação: " + idSessao);
//        SessionInformation[] sessoes = sessionRegistry.getAllSessions(auth.getPrincipal(), true); // sc.getAuthentication()
//        for (SessionInformation session: sessoes) {
//            int i = 1;
//            System.out.println("SessionInformation na Autenticação: " + i++ + "º: " + session.getSessionId() );
//        }
    //    auth = null;
        target.someUserMethod1(); //<<<--incide aqui o teste!!!
    } 

    @Test(expected=AccessDeniedException.class)
    public void a5targetShouldPreventProtectedMethodInvocationWithIncorrectRole() { 
        TestingAuthenticationToken token
                = new TestingAuthenticationToken("Test", "Password", /*AuthorityUtils.createAuthorityList(*/"ROLE_SOMEINVALIDROLE"/*)*/); 
        SecurityContextHolder.getContext().setAuthentication(token); 
        _eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(token, this.getClass() ) );

        target.someAdminMethod(); //<<<--incide aqui o teste!!!
        System.out.println("Falha na Autenticação: fluxo exec BLOQUEADO!!!)");
    } 
}

.. and finally (in my opnion) the key implementation (the 'CustomVoter'):

/**
 *
 * @author Derlon.Aliendres
 */
public class CustomVoter implements AccessDecisionVoter/*<Object>*/ {
//    @Inject
//    private SessionRegistry sessionRegistry;
    @Inject // 
    private ApplicationEventPublisher  _eventPublisher; 

    final protected Logger logger = Logger/*Factory*/.getLogger(getClass().getName());

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class/*<?>*/ clazzAuthentication) {
        return true/*org.aopalliance.intercept.MethodInvocation.class.isAssignableFrom(clazzAuthentication)*/;
    }

    @Override
    public int vote(Authentication authentication, Object object,/* ConfigAttributeDefinition*/Collection<ConfigAttribute> attributes) {
        logger.info("### Controle de Acesso  ###");
//
        //verifica se as credenciais são do tipo esperado
        if (SessionStandaloneInformation.class.isInstance(authentication.getDetails())) {/*authentication instanceof UsernamePasswordWithTimeoutAuthenticationToken*/
            Boolean result/* = null*/;
            UsernamePasswordWithTimeoutAuthenticationToken user
                    = (UsernamePasswordWithTimeoutAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); // authentication/*.getPrincipal()*/
        //    for (ConfigAttribute configAttribute: attributes) {
        //        String attr = configAttribute.getAttribute();
                result = user.jahExpirouSessionDa();
        //    }

            if (result == null || result == Boolean.FALSE) {
                logger.info(" -> Acesso Negado!");
            //    SecurityContextHolder.clearContext();// <<<--threatment already in SessionRegistry App listener!!!
                final AccountExpiredException userSessionExpiredException =
                        new AccountExpiredException("Sessão do Usuário expirou! Efetue o LogOn novamente.");
                _eventPublisher.publishEvent(new AuthenticationFailureExpiredEvent
                                                    (authentication, userSessionExpiredException) );
                throw userSessionExpiredException;
            //    return ACCESS_ABSTAIN; // ACCESS_DENIED
            } else {
                logger.info(" -> Acesso Permitido!");
            //    sessionRegistry.refreshLastRequest(sessionId);
                return ACCESS_GRANTED;
            }

        } else {
            System.out.println(" -> Não é do tipo UsernamePasswordWithTimeoutAuthenticationToken!");
            return ACCESS_ABSTAIN;
        }

    }
}
    
23.01.2018 / 15:48