Spring Boot+Vue 앞 뒤 분리 항목 로그 인 한 사용 자 를 어떻게 제거 합 니까?

상편에서 우 리 는 Spring Security 에서 이전 로그 인 사용 자 를 어떻게 차 버 리 거나 사용자 의 2 차 로그 인 을 금지 하 는 지 에 대해 간단 한 사례 를 통 해 우리 가 원 하 는 효 과 를 실현 했다.
그러나 완벽 하지 않 은 점 이 있 습 니 다.바로 우리 의 사용 자 는 메모리 에 설 치 된 사용자 입 니 다.우 리 는 사용 자 를 데이터베이스 에 넣 지 않 았 습 니 다.정상 적 인 상황 에서 송 형 이 Spring Security 시리즈 에서 말 한 다른 설정 은 모두Spring Security+Spring Data Jpa 가 강하 게 손 을 잡 으 면 안전 관리 가 더 간단 합 니 다!글 을 참고 하여 데 이 터 를 데이터베이스 에 있 는 데이터 로 전환 하면 된다.
본 고 는 본 시리즈 의 13 편 으로 앞의 글 을 읽 으 면 본 고 를 더욱 잘 이해 하 는 데 도움 이 된다.
  • 큰 구 덩이 를 파 라,Spring Security 가 열 어 라!
  • 송 형 이 손 으로 데 리 고 들 어 갈 게.Spring Security,비밀번호 어떻게 풀 었 는 지 묻 지 마.
  • 스프링 시 큐 리 티 의 폼 로그 인 을 손 으로 가르쳐 드 립 니 다.
  • Spring Security 는 앞 뒤 가 분리 되 어 있 으 니 페이지 를 뛰 어 넘 지 맙 시다!모두 JSON 상호작용
  • Spring Security 의 권한 수여 작업 은 원래 이렇게 간단 합 니 다.
  • Spring Security 는 어떻게 사용자 데 이 터 를 데이터베이스 에 저장 합 니까?
  • Spring Security+Spring Data Jpa 가 강하 게 손 을 잡 으 면 안전 관리 가 더 간단 합 니 다!
  • Spring Boot+Spring Security 자동 로그 인 기능 구현
  • Spring Boot 자동 로그 인,안전 위험 은 어떻게 통제 해 야 합 니까?
  • 마이크로 서비스 항목 중 Spring Security 가 Shiro 보다 나 은 것 은 어디 입 니까?
  • SpringSecurity 사용자 정의 인증 논리의 두 가지 방식(고급 게임 방법)
  • Spring Security 에서 로그 인 사용자 의 IP 주소 등 정 보 를 어떻게 신속하게 볼 수 있 습 니까?
  • 그러나 Spring Security 의 session 병행 처 리 를 할 때 메모리 에 있 는 사용 자 를 데이터베이스 에 있 는 사용자 로 직접 전환 하 는 데 문제 가 있 을 수 있 습 니 다.오늘 우 리 는 이 문 제 를 이야기 하 는 김 에 이 기능 을 마이크로 인사 에 응용 하 겠 습 니 다https://github.com/lenve/vhr.
    본 논문 의 사례 는Spring Security+Spring Data Jpa 의 강력 한 합작 을 바탕 으로 안전 관 리 는 더욱 간단 할 수 밖 에 없습니다!라 는 글 로 구 축 될 것 이기 때문에 중복 되 는 코드 는 쓰 지 않 겠 습 니 다.어린이 들 이 익숙 하지 않 으 면 이 글 을 참고 할 수 있 습 니 다.
    1.환경 준비
    먼저,우 리 는Spring Security+Spring Data Jpa 가 강하 게 손 을 잡 으 면 안전 관리 가 더 간단 합 니 다!글 의 사례 를 열 었 는데 이 사례 는 Spring Data Jpa 와 결합 하여 사용자 데 이 터 를 데이터베이스 에 저장 했다.
    그리고 우 리 는 위의 글 에서 언급 된 로그 인 페이지 를 프로젝트 에 복사 합 니 다(문장 끝 에 전체 사례 를 다운로드 할 수 있 습 니 다).
    [외부 체인 이미지 전송 에 실 패 했 습 니 다.원본 사이트 에 도 난 방지 체인 체제 가 있 을 수 있 습 니 다.그림 을 저장 하여 직접 업로드 하 는 것 을 권장 합 니 다(img-7XB0viq6-1588898082940)(http://img.itboyhub.com/2020/...]
    그리고 Security Config 에서 로그 인 페이지 를 약간 설정 합 니 다:
    
    @Override
    public void configure(WebSecurity web) throws Exception {
     web.ignoring().antMatchers("/js/**", "/css/**", "/images/**");
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
     http.authorizeRequests()
     ...
     .and()
     .formLogin()
     .loginPage("/login.html")
     .loginProcessingUrl("/doLogin")
     ...
     .and()
     .sessionManagement()
     .maximumSessions(1);
    }
    여 기 는 모두 일반적인 설정 이 니 더 이상 말 하지 않 겠 습 니 다.맨 뒤에 세 션 수 를 1 로 설정 합 니 다.
    자,설정 이 완료 되면 프로젝트 를 시작 하고 병렬 다 중 로그 인 테스트 를 시작 합 니 다.
    여러 개의 브 라 우 저 를 열 고 각각 다단 계 로그 인 테스트 를 진행 합 니 다.우 리 는 모든 브 라 우 저가 로그 인 에 성공 할 수 있 고 로그 인 에 성공 할 때마다 이미 로그 인 한 사용 자 를 차 버 리 지 않 는 다 는 것 을 놀 라 게 발 견 했 습 니 다!
    이거 어떻게 된 거 야?
    2.문제 분석
    이 문 제 를 알 기 위해 서 는 Spring Security 가 사용자 대상 과 session 을 어떻게 저장 하 는 지 알 아야 합 니 다.
    Spring Security 에 서 는 Session Registry Impl 류 를 통 해 세 션 정보 에 대한 통일 적 인 관 리 를 실현 합 니 다.이러한 소스 코드(부분)를 살 펴 보 겠 습 니 다.
    
    public class SessionRegistryImpl implements SessionRegistry,
     ApplicationListener<SessionDestroyedEvent> {
     /** <principal:Object,SessionIdSet> */
     private final ConcurrentMap<Object, Set<String>> principals;
     /** <sessionId:Object,SessionInformation> */
     private final Map<String, SessionInformation> sessionIds;
     public void registerNewSession(String sessionId, Object principal) {
     if (getSessionInformation(sessionId) != null) {
     removeSessionInformation(sessionId);
     }
     sessionIds.put(sessionId,
     new SessionInformation(principal, sessionId, new Date()));
    
     principals.compute(principal, (key, sessionsUsedByPrincipal) -> {
     if (sessionsUsedByPrincipal == null) {
     sessionsUsedByPrincipal = new CopyOnWriteArraySet<>();
     }
     sessionsUsedByPrincipal.add(sessionId);
     return sessionsUsedByPrincipal;
     });
     }
     public void removeSessionInformation(String sessionId) {
     SessionInformation info = getSessionInformation(sessionId);
     if (info == null) {
     return;
     }
     sessionIds.remove(sessionId);
     principals.computeIfPresent(info.getPrincipal(), (key, sessionsUsedByPrincipal) -> {
     sessionsUsedByPrincipal.remove(sessionId);
     if (sessionsUsedByPrincipal.isEmpty()) {
     sessionsUsedByPrincipal = null;
     }
     return sessionsUsedByPrincipal;
     });
     }
    
    }
    이런 종류의 소스 코드 는 비교적 길다.나 는 여기 서 비교적 관건 적 인 부분 을 추출 했다.
  • 먼저 보 세 요.올 라 와 서 principals 대상 을 밝 혔 습 니 다.이것 은 동시 방문 을 지원 하 는 map 집합 입 니 다.집합 한 key 는 사용자 의 주체(principal)입 니 다.정상 적 으로 볼 때 사용자 의 principal 은 사용자 대상 입 니 다.송 형 은 이전 글 에서 도 principal 이 Authentication 에 어떻게 저장 되 었 는 지(참조:Spring Security 로그 인 절차에 대해 이야기 한 적 이 있 으 며,집합 한 value 는 set 집합 이 며,이 set 집합 에는 이 사용자 가 대응 하 는 sessionid 가 저장 되 어 있다.
  • 새로운 session 을 추가 할 필요 가 있 으 면 registerNewSession 방법 에 추가 합 니 다.구체 적 으로 principals.copute 방법 으로 추가 합 니 다.key 는 principal 입 니 다.
  • 사용자 가 로그 인 을 취소 하면 sessionid 를 제거 해 야 합 니 다.관련 작업 은 removeSession Information 방법 에서 이 루어 집 니 다.구체 적 으로 principals.coputeIfPresent 방법 을 호출 합 니 다.집합 에 관 한 기본 적 인 작업 은 더 이상 언급 하지 않 겠 습 니 다.
  • 여기 서 여러분 은 질문 하 나 를 발 견 했 습 니 다.ConcurrentMap 이 집합 한 key 는 principal 대상 입 니 다.대상 으로 key 를 만 들 려 면 반드시 equals 방법 과 hashCode 방법 을 다시 써 야 합 니 다.그렇지 않 으 면 처음으로 데 이 터 를 저장 하고 다음 에 찾 을 수 없습니다.이것 은 자바 SE 분야 의 지식 입 니 다.저 는 더 이상 말 할 필요 가 없습니다.
    메모리 기반 사용 자 를 사용 했다 면 Spring Security 의 정 의 를 살 펴 보 겠 습 니 다.
    
    public class User implements UserDetails, CredentialsContainer {
     private String password;
     private final String username;
     private final Set<GrantedAuthority> authorities;
     private final boolean accountNonExpired;
     private final boolean accountNonLocked;
     private final boolean credentialsNonExpired;
     private final boolean enabled;
     @Override
     public boolean equals(Object rhs) {
     if (rhs instanceof User) {
     return username.equals(((User) rhs).username);
     }
     return false;
     }
     @Override
     public int hashCode() {
     return username.hashCode();
     }
    }
    그 자신 이 실제로 equals 와 hashCode 방법 을 다시 쓴 것 을 볼 수 있다.
    따라서 메모리 기반 사용 자 를 사용 할 때 문제 가 없 으 며 사용자 정의 사용 자 를 사용 하 는 데 문제 가 있 습 니 다.
    문제 의 소 재 를 찾 으 면 문 제 를 해결 하 는 것 이 쉬 워 집 니 다.User 류 의 equals 방법 과 hashCode 방법 을 다시 쓰 면 됩 니 다.
    
    @Entity(name = "t_user")
    public class User implements UserDetails {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Long id;
     private String username;
     private String password;
     private boolean accountNonExpired;
     private boolean accountNonLocked;
     private boolean credentialsNonExpired;
     private boolean enabled;
     @ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.PERSIST)
     private List<Role> roles;
    
     @Override
     public boolean equals(Object o) {
     if (this == o) return true;
     if (o == null || getClass() != o.getClass()) return false;
     User user = (User) o;
     return Objects.equals(username, user.username);
     }
    
     @Override
     public int hashCode() {
     return Objects.hash(username);
     }
     ...
     ...
    }
    설정 이 완료 되면 항목 을 다시 시작 하고 다단 계 로그 인 테스트 를 진행 하면 로그 인 한 사용 자 를 성공 적 으로 차 버 릴 수 있 습 니 다.
    Jpa 가 아 닌 MyBatis 를 사용 했다 면 로그 인 사용자 의 equals 방법 과 hashCode 방법 만 다시 쓰 면 됩 니 다.
    3.마이크로 인사 응용
    3.1 존재 하 는 문제점
    마이크로 인 사 는 현재 JSON 형식 으로 로그 인 되 어 있 기 때문에 프로젝트 가 session 병발 수 를 제어 하면 추가 적 인 문제 가 발생 할 수 있 습 니 다.
    가장 큰 문 제 는 사용자 정의 필터 로 UsernamePassword AuthenticationFilter 를 대체 하여 앞에서 말 한 session 에 대한 설정 을 모두 무효 화 하 는 것 입 니 다.모든 관련 설정 은 새 필터 LoginFilter 에서 설정 해 야 합 니 다.Session Authentication Strategy 를 포함 하여 수 동 으로 설정 해 야 합 니 다.
    이 는 업 무량 을 가 져 왔 지만 끝나 면 Spring Security 에 대한 이해 가 한층 더 높 아 질 것 이 라 고 믿 습 니 다.
    3.2 구체 적 인 응용
    구체 적 으로 어떻게 실현 되 는 지 살 펴 보 겠 습 니 다.저 는 주로 관건 적 인 코드 를 열거 합 니 다.전체 코드 는 GitHub 에서 다운로드 할 수 있 습 니 다.https://github.com/lenve/vhr
    먼저 첫 번 째 단 계 는 Hr 류 의 equals 와 hashCode 방법 을 다시 씁 니 다.다음 과 같 습 니 다.
    
    public class Hr implements UserDetails {
     ...
     ...
     @Override
     public boolean equals(Object o) {
     if (this == o) return true;
     if (o == null || getClass() != o.getClass()) return false;
     Hr hr = (Hr) o;
     return Objects.equals(username, hr.username);
     }
    
     @Override
     public int hashCode() {
     return Objects.hash(username);
     }
     ...
     ...
    }
    다음은 Security Config 에서 설정 합 니 다.
    여기 서 저 희 는 Session AuthenticationStrategy 를 제공 해 야 합 니 다.앞에서 session 을 처리 하 는 것 은 Concurrent Session Control AuthenticationStrategy 입 니 다.즉,저 희 는 Concurrent Session Control AuthenticationStrategy 의 인 스 턴 스 를 제공 한 다음 에 Login Filter 에 배치 해 야 합 니 다.그러나 Concurrent Session Control Authentication Strategy 인 스 턴 스 를 만 드 는 과정 에서 Session Registry Impl 대상 이 필요 합 니 다.
    앞에서 말 했 듯 이 Session Registry Impl 대상 은 세 션 정 보 를 유지 하 는 데 사 용 됩 니 다.현재 이 물건 도 저희 가 직접 제공 해 야 합 니 다.Session Registry Impl 인 스 턴 스 는 잘 만 들 어 졌 습 니 다.다음 과 같 습 니 다.
    
    @Bean
    SessionRegistryImpl sessionRegistry() {
     return new SessionRegistryImpl();
    }
    그리고 Login Filter 에 Session Authentication Strategy 를 설정 합 니 다.다음 과 같 습 니 다.
    
    @Bean
    LoginFilter loginFilter() throws Exception {
     LoginFilter loginFilter = new LoginFilter();
     loginFilter.setAuthenticationSuccessHandler((request, response, authentication) -> {
     //  
     }
     );
     loginFilter.setAuthenticationFailureHandler((request, response, exception) -> {
     //  
     }
     );
     loginFilter.setAuthenticationManager(authenticationManagerBean());
     loginFilter.setFilterProcessesUrl("/doLogin");
     ConcurrentSessionControlAuthenticationStrategy sessionStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
     sessionStrategy.setMaximumSessions(1);
     loginFilter.setSessionAuthenticationStrategy(sessionStrategy);
     return loginFilter;
    }
    저 희 는 여기 서 Concurrent Session Control AuthenticationStrategy 인 스 턴 스 를 수 동 으로 구축 하고 구축 할 때 Session Registry Impl 인 자 를 전달 한 다음 에 session 의 병발 수 를 1 로 설정 한 다음 에 session Strategy 를 Login Filter 에 설정 합 니 다.
    사실상편에서 우리 의 배치 방안 도 결국 위 와 같이 지금 우리 스스로 이것 을 썼 을 뿐이다.
    이것 괜찮아요?없다session 처리 에 또 하나의 관건 적 인 필 터 는 Concurrent Session Filter 라 고 합 니 다.원래 이 필 터 는 우리 가 관리 할 필요 가 없 었 지만 이 필터 에 도 Session Registry Impl 이 사용 되 었 습 니 다.Session Registry Impl 은 현재 우리 가 정의 하고 있 기 때문에 이 필 터 는 우리 도 다시 설정 해 야 합 니 다.다음 과 같 습 니 다.
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
     http.authorizeRequests()
     ...
     http.addFilterAt(new ConcurrentSessionFilter(sessionRegistry(), event -> {
     HttpServletResponse resp = event.getResponse();
     resp.setContentType("application/json;charset=utf-8");
     resp.setStatus(401);
     PrintWriter out = resp.getWriter();
     out.write(new ObjectMapper().writeValueAsString(RespBean.error("          ,       !")));
     out.flush();
     out.close();
     }), ConcurrentSessionFilter.class);
     http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
    }
    시스템 의 기본 값 대신 Concurrent Session Filter 의 인 스 턴 스 를 다시 만 들 면 됩 니 다.새 Concurrent Session Filter 인 스 턴 스 를 만 들 때 두 개의 인자 가 필요 합 니 다.
  • session Registry 는 바로 우리 가 앞에서 제공 한 Session Registry Impl 인 스 턴 스 입 니 다.
  • 두 번 째 매개 변 수 는 session 이 만 료 된 후의 리 셋 함수 입 니 다.즉,사용자 가 다른 로그 인 에 의 해 오프라인 으로 차 인 후에 어떤 오프라인 힌트 를 줄 지 여기 서 완성 하 는 것 입 니 다.
  • 마지막 으로 로그 인 데 이 터 를 처리 한 후에 Session Registry Impl 에 수 동 으로 기록 을 추가 해 야 합 니 다.
    
    public class LoginFilter extends UsernamePasswordAuthenticationFilter {
     @Autowired
     SessionRegistry sessionRegistry;
     @Override
     public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
     //  
     UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
     username, password);
     setDetails(request, authRequest);
     Hr principal = new Hr();
     principal.setUsername(username);
     sessionRegistry.registerNewSession(request.getSession(true).getId(), principal);
     return this.getAuthenticationManager().authenticate(authRequest);
     } 
     ...
     ...
     }
    }
    여기 서 session Registry.registerNewSession 방법 을 수 동 으로 호출 하여 Session Registry Impl 에 session 기록 을 추가 합 니 다.
    OK,그 후에 우리 의 프로젝트 설정 이 완성 되 었 습 니 다.
    다음 에 vhr 프로젝트 를 다시 시작 하고 다단 계 로그 인 테스트 를 실시 합 니 다.만약 에 자신 이 오프라인 상태 에 빠 지면 다음 과 같은 힌트 를 볼 수 있 습 니 다.

    완전한 코드,나 는 이미 vhr 에 업데이트 되 었 으 니,여러분 은 다운로드 하여 공부 할 수 있 습 니 다.
    4.소결
    자,본 고 는 주로 어린이 들 과 Spring Security 에서 session 병발 문 제 를 처리 할 때 발생 할 수 있 는 구덩이 와 앞 뒤 가 분 리 된 상황 에서 session 병발 문 제 를 어떻게 처리 하 는 지 소개 했다.친구 들 이 GET 가 도 착 했 는 지 모 르 겠 어 요.
    본 논문 의 두 번 째 소절 의 사례 는 GitHub 에서 다운로드 할 수 있 습 니 다.https://github.com/lenve/spring-security-samples
    수확 이 있 으 면 골 라 서 송 이 형 을 격려 하 세 요~

    좋은 웹페이지 즐겨찾기