在 Spring 中管理或自定义用户会话 管理 JavaSE 应用程序

Posted

技术标签:

【中文标题】在 Spring 中管理或自定义用户会话 管理 JavaSE 应用程序【英文标题】:Manage or customize the user session in Spring manage the JavaSE application 【发布时间】:2018-04-11 21:56:13 【问题描述】:

伙计们,

我想知道在 SpringSecurity 托管的 JavaSE(GUI/Desktop/SWING/thinClient) 应用程序中管理或自定义用户会话??! 例如,我如何在 JavaSE 应用程序中设置 TimeOut??! 这是'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>

...域服务:

@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'!)");
     
...

...锻炼(测试):

@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!!!
     

【问题讨论】:

请提供您已经尝试过的代码示例。有人帮助你会容易得多。 好的,由于不是 Web 应用程序,即使使用 auto-config="true" create-session="always" 我也无法从 sessionRegistry.getAllPrincipals() 获得任何信息。 而且,更糟糕的是,Spring-Security 3.1.x 中似乎不推荐使用“SessionRegistryUtil”。我感谢有关专业安全性的任何指导(在独立应用程序上)。 tnx 提前 【参考方案1】:

不久前,我在某种程度上找到了一些合理的解决方案。我现在发布它,因为我认为它可能对其他人有用。 (Obs.:该解决方案是使用 SpringSecurity2.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-2.0.4.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 id="testingAuthenticationProvider" ->>por estar http auto-config="true", este NÃO é necessário: AnonymousAuthenticationProvider é injetado em ser lugar
                class="org.springframework.security.authentication.TestingAuthenticationProvider" autowire-candidate="false">
        <custom-authentication-provider />
    </beans:bean>-->
    <beans:bean class="org.springframework.security.event.authentication.LoggerListener" />
    <beans:bean class="org.springframework.security.event.authorization.LoggerListener" />
  <beans:bean id="sessionRegistry" class="org.springframework.security.concurrent.SessionRegistryImpl" />
      <context:component-scan base-package="br.com.vitoria.springSecJSR250" />
        <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.vote.UnanimousBased"><!-- access. -->
        <beans:property name="allowIfAllAbstainDecisions"><beans:value>true</beans:value></beans:property>
        <beans:property name="decisionVoters">
            <beans:list>
                <beans:ref bean="customVoter" />
                <beans:bean class="org.springframework.security.vote.AuthenticatedVoter" /><!-- access.--> 
                <beans:bean class="org.springframework.security.vote.RoleVoter"/><!-- access.--> 
            </beans:list>
        </beans:property>
<!--        <beans:constructor-arg>SpringSec 3.X+
            <beans:list>
                <beans:bean class="org.springframework.security.vote.AuthenticatedVoter" /> 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>
</beans:beans>

..保存用户会话数据的类 AuthenticationToken:

public class StandardSessionAuthenticationToken/*<C extends SecurityContext, T extends SessionStandaloneInformation>*/
                             extends UsernamePasswordAuthenticationToken implements AuthenticationDetailsSource
//    private SessionStandaloneInformation details;
    protected 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, GrantedAuthority[] authorities) 
        super(principal, credentials, authorities);
        this.setDetails(new SessionStandaloneInformation(principal, sessionId, lastRequest) );
    

    /**
     *
     * @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); //To change body of generated methods, choose Tools | Templates.
//    

    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(GrantedAuthority[] authorities) 
        this.authorities = authorities;
    
    /**
     * @return the authorities
     */
    @Override
    public GrantedAuthority[] getAuthorities() 
        return authorities;
    

..保存(会话)过期信息的类 AuthenticationToken:

import java.util.Date;
import org.springframework.security.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, timeOut, sessionId, lastRequest, null);
        //    this.timeout = null;
    

    public UsernamePasswordWithTimeoutAuthenticationToken(Object principal, Object credentials, String timeOut
                                                              , String sessionId, Date lastRequest, GrantedAuthority[] authorities) 
            super(principal, credentials, sessionId, lastRequest, authorities);
            this.timeout = timeOut;
    

    public String getTimeout() 
        return timeout;
       

    /**
     * @param authorities the authorities to set
     */
    @Override
    public void setAuthorities(GrantedAuthority[] authorities) 
        super.setAuthorities(authorities);
    
    /**
     * @return the authorities
     */
    @Override
    public GrantedAuthority[] getAuthorities() 
        return super.authorities;
    

...测试:

/**
  * @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 org.springframework.security.providers.dao.DaoAuthenticationProvider  daoAuthenticationProvider; 

    @BeforeClass
    public static void setUpClass() 
//        appContext = new InMemoryXmlApplicationContext( 
//            //    "<b:bean id='target' class='org.springframework.security.access.annotation.Jsr250BusinessServiceImpl'/>" + 
//               " <context:component-scan base-package=\"br.com.vitoria.springSecJSR250\" />" + 
//                "<global-method-security jsr250-annotations='enabled'/>" + ConfigTestUtilsEnum.AUTH_PROV_TEST_TOKEN 
//                ); 
//        target = (BusinessService)appContext.getBean(BusinessService.class/*"target"*/); 
    //    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"/*)*/ ); 
        UsernamePasswordAuthenticationToken token
            = new UsernamePasswordAuthenticationToken("Test", "Password", AuthorityUtils.stringArrayToAuthorityArray(new String[]"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")); 
        UsernamePasswordAuthenticationToken token
            = new UsernamePasswordAuthenticationToken("Test", "Password", AuthorityUtils.stringArrayToAuthorityArray(new String[]"ROLE_USER")); 
        SecurityContextHolder.getContext().setAuthentication(token); 
        _eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(token, this.getClass() ) );

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

    @Inject
    private SessionRegistry sessionRegistry;
    @Test(expected=NonceExpiredException/*ProviderNotFoundException*/.class) //AccessDeniedException AuthenticationFailureProviderNotFoundEvent
    public void a3targetShouldPreventInvocationWithCorrectRoleButNoLongerAuthenticated()  
        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", timeOut, "001", datimeLastRequest, AuthorityUtils.stringArrayToAuthorityArray(new String[]"ROLE_USER")/**/); 
    //    Authentication auth = /*(StandardSessionAuthenticationToken)*/daoAuthenticationProvider.authenticate(token); // 
    //    token.setAuthorities(auth.getAuthorities() );
        SecurityContextHolder.getContext().setAuthentication( /*(UsernamePasswordWithTimeoutAuthenticationToken*/token); //auth.getDetails() 
        _eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(token, this.getClass() ) ); // token
//    /*    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, token.getPrincipal())/*auth.getName()*//*.get(0).expireNow()*/;
    //    System.out.println("SessionInformation na Autenticação: " + idSessao);
        SessionInformation[] sessoes = sessionRegistry.getAllSessions(token.getPrincipal(), true); // sc.getAuthentication()
        for (SessionInformation session: sessoes) 
            int i = 1;
            System.out.println("SessionInformation na Autenticação: " + i++ + "º: " + session.getSessionId() );
        
        target.someUserMethod1(); //<<<--incide aqui o teste!!!
        token.setAuthenticated(false);
        SecurityContextHolder.getContext().setAuthentication(token); //auth.getDetails() 
        target.someUserMethod1(); //<<<--incide aqui o teste!!!
     

    @Test(expected=AccessDeniedException.class)
    public void a4targetShouldPreventProtectedMethodInvocationWithIncorrectRole()  
        UsernamePasswordAuthenticationToken token
            = new UsernamePasswordAuthenticationToken("Hakcer", "Password", AuthorityUtils.stringArrayToAuthorityArray(new String[]"ROLE_SOMEINVALIDROLE")); 
//        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!!!)");
     
 

.. 最后(在我看来)关键实现('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 (UsernamePasswordWithTimeoutAuthenticationToken.class.isInstance(authentication) ) /*authentication instanceof UsernamePasswordWithTimeoutAuthenticationToken*/
            Boolean result = null;
            UsernamePasswordWithTimeoutAuthenticationToken user = (UsernamePasswordWithTimeoutAuthenticationToken)authentication/*.getPrincipal()*/;
            final SessionStandaloneInformation sessnInfo = (SessionStandaloneInformation)(user.getDetails() );
            final String sessionId = sessnInfo.getSessionId();
            final Date datimeLastRequest = sessnInfo.getLastRequest();
            final long timeOutTime = extractNonceValue(user.getTimeout());

        //    for (ConfigAttribute configAttribute : attributes) 
        //        String attr = configAttribute.getAttribute();
                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)
                
        //    

            if (result == null || result == Boolean.FALSE) 
                logger.info(" -> Acesso Negado!");
                SecurityContextHolder.clearContext();/*.getContext().getAuthentication().setAuthenticated(false)*/; //authentication .clearContext()
                final NonceExpiredException userSessionExpiredException =/* null*/new NonceExpiredException("Sessão deo 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;
        

    

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

【讨论】:

以上是关于在 Spring 中管理或自定义用户会话 管理 JavaSE 应用程序的主要内容,如果未能解决你的问题,请参考以下文章

通过流量管理器和 Azure Functions(作为代理)为全球用户提供最靠近的认知服务(或自定义API)

使用 Spring Security 进行会话管理

Spring Security用户手动登录会话由管理员创建,无需密码

Spring Security解析八:SessionManagementConfigurer

使用Spring Session做分布式会话管理

使用Spring Session做分布式会话管理