Development/Java

[Spring Boot] Spring Security maximumSessions 관련

반응형

spring boot security(라고 쓰고, spring security 라고 읽을 수도 있다) 에서 동시접속 세션수를 제한하는 기능이 있다.




@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.sessionManagement()
                .maximumSessions(1)
                .maxSessionsPreventsLogin(true)
    }

}


위와 같이 @Configuration에 @EnableWebSecurity 어노테이션을 붙인 뒤, WebSecurityConfigurerAdapter를 상속받아 구현하면 된다.


override된 메서드 중 confiugre() 함수 내에 http security를 설정할 수 있는데


maximumSessions(1)을 주게 되면 해당 UserDetails에 해당하는 유저가 "1명" 만 접속이 가능하다.


이론상으론 그런데, "Maximum sessions of 1 for this principal exceeded" 와 같은 에러를 뿜고 작동이 잘 안된다.


스텝

  1. /login 페이지에서 A유저로 로그인을 진행
  2. /logout 페이지로 이동해서 로그아웃을 진행
  3. /login 페이지에서 다시 동일한 A유저로 로그인을 진행
하게 되면 세션이 이미 있어서 초과되었다는 에러와 함께 로그인이 되지 않는다.

도대체 왜 이렇게 구현이 되어있는지는 알 수가 없는데, 검색해본 결과 뭐.. spring security의 버전을 올려서 해결했다.. 현상이 이렇다 라고는 하는데

구체적인 해결방안이 없어서 구글링을 하던 도중 해결책을 찾아냈다.




@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 최대 세션 수 설정
        http.sessionManagement()
                .maximumSessions(1)
                .maxSessionsPreventsLogin(true)
                .expiredUrl("/duplicated-login")
                .sessionRegistry(sessionRegistry);
    }

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

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

}


위와 같이 SessionRegistry와 ServletListenerRegistrationBean을 Bean으로 만들면 된다.


SessionRegistry는 http의 sessionManagement에 직접 붙여주고, ServletListenerRegistrationBean는 그냥 Bean만 생성을 하면 된다.(static bean)


이렇게 설정을 하니 동일 유저로 로그인/로그아웃을 진행해도 문제없이 잘 된다.

+ 접속한 브라우저 말고 크롬 시크릿 페이지를 열어서 다른 세션으로 접속을 시도해도 한 User가 로그인이 되어 있으면 로그인이 되지 않게 된다.


또한 UserDetails를 구현한 구현체에 반드시 equals, hashcode 메서드를 override해서 구현을 해줘야 한다.


본인은 Lombok을 사용하기 때문에 




@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(of = "username")
public class User implements Principal, UserDetails {
    @Id
    @Column
    @JsonIgnore
    private String username;

    @Column
    @JsonIgnore
    private String password;

    @Column
    @JsonIgnore
    private String authorities;

    @Column
    private boolean locked;

    @Column
    @JsonIgnore
    private String ip;

    @Column
    @JsonIgnore
    private String userAgent;

    @Column
    @Temporal(TemporalType.TIMESTAMP)
    private Date creation;

    @Column
    @Temporal(TemporalType.TIMESTAMP)
    private Date lastAccess;

    @Override
    public String getName() {
        return username;
    }

    @Override
    public Collection getAuthorities() {
        List authorityList = new ArrayList<>();
        if (!StringUtils.isEmpty(authorities)) {
            String[] splitedValue = authorities.split(",");
            for (String auth : splitedValue) {
                authorityList.add(new SimpleGrantedAuthority(auth));
            }
        }
        return authorityList;
    }

    @Override
    public String getUsername() {
        return this.username;
    }

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

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

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

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


와 같이 사용한다. (username 기준으로 equals, hashcode를 생성)

반응형