当前位置: 首页 > 知识库问答 >
问题:

Spring-Boot并发sessionManagement

龙毅
2023-03-14
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
public class MessageSecurityWebApplicationInitializer extends
        AbstractSecurityWebApplicationInitializer {

    @Override
    protected boolean enableHttpSessionEventPublisher() {
        return true;
    }
}

但是当我使用Spring-Boot时,如何添加这个监听器来使sessionManagement工作,换句话说,如何配置我的WebConfig.java使同一个用户一次只能登录一次,第二次登录是被阻止的,直到第一次登录过期或注销。我的SecurityConfig.java如下所示,对不起我的池英语:)

package com.eexcel.branch.config;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.context.embedded.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.data.domain.AuditorAware;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import com.eexcel.common.domain.SpringSecurityAuditorAware;
import com.eexcel.common.service.distributor.DistributorService;

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
    }
    @Bean
    public AuditorAware<String> auditor() {
        return new SpringSecurityAuditorAware();
    }
    @Bean
    public SecurityEvaluationContextExtension expressionEvaluationContextProvider() {
        return new SecurityEvaluationContextExtension();
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Bean
    public ApplicationSecurity applicationSecurity() {
        return new ApplicationSecurity();
    }
    @Bean
    public static SessionRegistry sessionRegistry() {
        SessionRegistry sessionRegistry = new SessionRegistryImpl();
        return sessionRegistry;
    }
    @Bean
    public static ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
        return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(
                new HttpSessionEventPublisher());
    }
    @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
    protected static class ApplicationSecurity extends
            WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests().anyRequest().fullyAuthenticated().and()
                    .formLogin().loginPage("/login").failureUrl("/login?error")
                    .permitAll().and().logout()
                    .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                    .logoutSuccessUrl("/login?logout").permitAll().and()
                    .rememberMe().and().sessionManagement().maximumSessions(1)
                    .maxSessionsPreventsLogin(true)
                    .expiredUrl("/login?expired")
                    .sessionRegistry(sessionRegistry());
        }
    }
    @Order(Ordered.HIGHEST_PRECEDENCE)
    @Configuration
    protected static class AuthenticationManagerConfiguration extends
            GlobalAuthenticationConfigurerAdapter {
        @Autowired
        private DataSource dataSource;
        @Autowired
        private PasswordEncoder passwordEncoder;
        @Autowired
        private DistributorService userDetailsService;

        @Override
        public void init(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService).passwordEncoder(
                    passwordEncoder);
        }
    }
}

共有1个答案

常小白
2023-03-14

我的配置是正确的,它不能工作的原因是我定义了一个对象'CustomUserDetails'实现了SpringSecurity的接口'UserDetails',而SessionRegistryImpl中的方法

public List<SessionInformation> getAllSessions(Object principal,
            boolean includeExpiredSessions) {
        final Set<String> sessionsUsedByPrincipal = principals.get(principal);

        if (sessionsUsedByPrincipal == null) {
            return Collections.emptyList();
        }

        List<SessionInformation> list = new ArrayList<SessionInformation>(
                sessionsUsedByPrincipal.size());

        for (String sessionId : sessionsUsedByPrincipal) {
            SessionInformation sessionInformation = getSessionInformation(sessionId);

            if (sessionInformation == null) {
                continue;
            }

            if (includeExpiredSessions || !sessionInformation.isExpired()) {
                list.add(sessionInformation);
            }
        }

        return list;
    }

校长们是

private final ConcurrentMap<Object, Set<String>> principals = new ConcurrentHashMap<Object, Set<String>>();

因此,它获取principal的实例是CustomUserDetails,如果不重写EqualsAndHashCode,则Principals.get(principal)始终为null。最后,我重写我的CustomUserDetails,它工作了

@EqualsAndHashCode(callSuper = true, exclude = { "authorities", "sysOperator" })
public class CustomUserDetails extends SysOperator implements UserDetails {
    private static final long serialVersionUID = 1L;
    private List<String> authorities;
    private SysOperator sysOperator;

    public CustomUserDetails(SysOperator sysOperator,
            final List<String> authorities) {
        super(sysOperator);
        this.authorities = authorities;
        this.sysOperator = sysOperator;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        String[] tmp = authorities.toArray(new String[authorities.size()]);
        return AuthorityUtils.createAuthorityList(tmp);
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return sysOperator.getEnable();
    }

    @Override
    public String getUsername() {
        return sysOperator.getLoginName();
    }
}

@Entity
@Table(name = "sys_operator")
@EqualsAndHashCode(callSuper = false, exclude = { "realName", "password",
        "mobile", "email", "enable", "remark", "roleIds", "branchNo",
        "birthday" })
public class SysOperator extends IdEntity {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    @Column(nullable = false, unique = true, length = 32)
    private String loginName;
    private String realName;
    @Column(nullable = false)
    private String password;
    private String mobile;
    @Column(nullable = false, unique = true, length = 32)
    private String email;
    @Column(nullable = false)
    private Boolean enable;
    private String remark;
    @Column(nullable = true)
    private String roleIds;

    private String branchNo;

    @Past
    @Temporal(TemporalType.DATE)
    private Date birthday;

    public SysOperator(SysOperator sysOperator) {
        BeanUtils.copyProperties(sysOperator, this);
    }

    public SysOperator() {
        super();
    }

    @Transient
    public boolean isAdmin() {
        return (AuthConsts.ADMIN.equalsIgnoreCase(this.loginName) || Arrays
                .asList(AuthConsts.ADMIN_GROUP).contains(this.loginName));
    }

    public String getLoginName() {
        return loginName;
    }

    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }

    public String getRealName() {
        return realName;
    }

    public void setRealName(String realName) {
        this.realName = realName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    public String getRoleIds() {
        return roleIds;
    }

    public void setRoleIds(String roleIds) {
        this.roleIds = roleIds;
    }

    public Boolean getEnable() {
        return enable;
    }

    public void setEnable(Boolean enable) {
        this.enable = enable;
    }

    public String getBranchNo() {
        return branchNo;
    }

    public void setBranchNo(String branchNo) {
        this.branchNo = branchNo;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }
}
 类似资料:
  • 我正在使用Spring Boot构建一个RESTful web服务。我的IDE是Eclipse Oxygen。 这里是我的控制器代码: 我的控制台输出是: 控制台输出显示每5秒调用一次控制器。但我每隔两秒就发送一次请求。 是否可以接受来自同一会话的并发多个请求? 谢谢!

  • 这里是再现异常的示例项目。 null UPD:使用任务执行器的工作放在另一个分支中。Spring与事务性任务相结合可以解决这个问题。

  • 我想合并两个响应并返回一个流量。 这就是我正在做的,正在工作,但我希望在调用Mono.just(获取(状态))之前等待合并。我将解释,sendGetRequest返回一个Mono,该Mono进行API调用,并从结果将东西保存到db。随后,合并操作将调用带有读取方法的数据库,但该数据尚未更新。如果我再打一次电话,我就会得到更新的数据。

  • 使用Spring Cloud Stream版本Chelsea. SR2,RabbitMQ作为消息代理。要拥有多个消费者,我们使用属性并发(入站消费者的并发)。 如果我们将并发设置为50。它从1开始,慢慢地增加消费者计数。有没有任何可能的解决方案可以使用更高的数字而不是一个来启动初始消费者计数,以提高消费者性能。

  • 我正在用Spring-Kafka写我的第一个Kafka消费者。看了一下framework提供的不同选项,对相同的选项几乎没有疑问。能否有人请澄清以下,如果你已经在它工作。 问题1:根据Spring-Kafka文档,有两种实现Kafka-Consumer的方法;“您可以通过配置MessageListenerContainer并提供消息侦听器或使用@Kafkalistener注释来接收消息”。有人能告

  • 我正在与spring boot spring@KafkaListener合作。我期望的行为是:我的Kafka侦听器以10个线程读取消息。因此,如果其中一个线程挂起,其他消息将继续读取和处理消息。 我定义了bean of 和spring启动配置: 我看到所有配置都能正常工作,我在jmx中看到了我的10个线程: 但是我做了这样的测试: 如果版本是 也许我的期望不是真的,这是Kafka听众的正确行为。请