Shiro+Redis 로그 인 횟수 동결 예시

개술
만약 에 우리 가 이런 장면 이 필요 하 다 고 가정 합 니 다.만약 에 사용자 가 비밀 번 호 를 5 번 연속 으로 잘못 지면 누군가가 일 을 하고 있다 는 것 을 설명 할 수 있 기 때문에 이 계 정의 로그 인 기능 을 잠시 동결 해 야 합 니 다.
Shiro 통합 JWT 에 대해 서 는 여 기 를 볼 수 있 습 니 다:Springboot Shiro+JWT 인증 실현
만약 에 우리 프로젝트 에 shiro 를 사용 했다 고 가정 하면 Shiro 는 완벽 한 인터페이스 구동 디자인 과 대상 지향 원칙 위 에 세 워 진 것 이기 때문에 각종 사용자 정의 행 위 를 지원 하기 때문에 우 리 는 Shiro 프레임 워 크 의 인증 모듈 과 redis 를 결합 하여 이 기능 을 실현 할 수 있 습 니 다.
사고의 방향
우리 의 대체적인 사고방식 은 다음 과 같다.
image-20200225151338221
  • 사용자 로그 인
  • Shiro 는 Redis 에 가서 계 정의 로그 인 오류 횟수 가 규정된 범 위 를 초과 하 는 지 확인 합 니 다(이른바 동결)
  • Shiro 비밀번호 비교
  • 로그 인 에 실패 하면 Redis 에 가서 기록:로그 인 오류 횟수+1
  • 비밀번호 가 정확 하면 로그 인 에 성공 하고 Redis 의 로그 인 오류 기록 을 삭제 합 니 다
  • 전기 준비
    Shiro 를 사용 해 야 하 는 것 외 에 우 리 는 Redis 를 사용 해 야 합 니 다.여 기 는 RedisTemplate 를 먼저 설정 해 야 합 니 다.(이것 이 중요 하지 않 기 때문에 코드 와 설정 방법 을 글 의 마지막 에 붙 여야 합 니 다)또한 Controller 층 에서 로그 인 인터페이스의 이상 처 리 는 이전의 로그 인 오 류 를 제외 하고 계 정 동결 류 의 이상 도 추가 해 야 합 니 다.코드 는 다음 과 같 습 니 다.
    
     @PostMapping(value = "/login")
     public AccountVO login(String userName, String password){
      
      //    
      Subject subject = SecurityUtils.getSubject();
      try {
       //  shiro            
       subject.login(new UsernamePasswordToken(userName, password));
      } catch (ExcessiveAttemptsException e1) {
       //           
       throw new AccountLockedException();
      } catch (Exception e) {
       //       
       throw new LoginFailed();
      }
      //      
      AccountVO account = accountService.getAccountByUserName(userName);
      //         
      return account;
     }
    
    사용자 정의 Shiro 인증 관리자
    HashedCredentialsMatcher
    위의 Controller 층 에서 subject.login 방법 을 호출 하면 사용자 정의 Realm 에 들 어가 서 Shiro 현재 Security Manager 에서 정의 하 는 HashedCredentialsMatcher 인증 관리자 의 doCredentialsMatch 방법 에 천천히 들 어가 비밀 번 호 를 매 칭 합 니 다.원본 코드 는 다음 과 같 습 니 다.
    
     /**
      * This implementation first hashes the {@code token}'s credentials, potentially using a
      * {@code salt} if the {@code info} argument is a
      * {@link org.apache.shiro.authc.SaltedAuthenticationInfo SaltedAuthenticationInfo}. It then compares the hash
      * against the {@code AuthenticationInfo}'s
      * {@link #getCredentials(org.apache.shiro.authc.AuthenticationInfo) already-hashed credentials}. This method
      * returns {@code true} if those two values are {@link #equals(Object, Object) equal}, {@code false} otherwise.
      *
      * @param token the {@code AuthenticationToken} submitted during the authentication attempt.
      * @param info the {@code AuthenticationInfo} stored in the system matching the token principal
      * @return {@code true} if the provided token credentials hash match to the stored account credentials hash,
      *   {@code false} otherwise
      * @since 1.1
      */
     @Override
     public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
      Object tokenHashedCredentials = hashProvidedCredentials(token, info);
      Object accountCredentials = getCredentials(info);
      return equals(tokenHashedCredentials, accountCredentials);
     }
    원본 의 논 리 는 매우 간단 하 다 는 것 을 알 수 있다.두 가지 일 을 해서 비밀 번 호 를 얻 었 다.비밀 번 호 를 비교 해 보 자.
    Redis 를 연결 해 야 하기 때문에 로그 인 하기 전에 동결 검 사 를 한 번 하고 로그 인 에 실패 할 때마다 redis 에 대한 쓰기 작업 을 해 야 하기 때문에 지금 은 인증 관리 자 를 다시 써 서 Security Manager 에 설정 해 야 합 니 다.
    CustomMatcher
    저 희 는 customer Matcher 를 사용자 정의 합 니 다.이 종 류 는 HashedCredentials Matcher 를 계승 하고 doCredentials Match 방법 만 다시 썼 습 니 다.이 안에 저희 만 의 논 리 를 추 가 했 습 니 다.코드 는 다음 과 같 습 니 다.
    
    import com.imlehr.internship.redis.RedisStringService;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.ExcessiveAttemptsException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
    import org.springframework.beans.factory.annotation.Autowired;
    
    /**
     * @author Lehr
     * @create: 2020-02-25
     */
    public class CustomMatcher extends HashedCredentialsMatcher {
    
    	//   redis  key     
     private static final String PREFIX = "USER_LOGIN_FAIL:";
    
     @Autowired
     RedisStringService redisUtils;
    
     @Override
     public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
    
      //          
    
      //           
      UsernamePasswordToken myToken = (UsernamePasswordToken) token;
    
      String userName = myToken.getUsername();
    
      //         
      Integer errorNum = 0;
    
      //           
      String errorTimes = (String)redisUtils.get(PREFIX+userName);
    
      if(errorTimes!=null && errorTimes.trim().length()>0)
      {
       //              
       errorNum = Integer.parseInt(errorTimes);
      }
    
      //              
      if (errorNum >= 10) {
       //         
       throw new ExcessiveAttemptsException();
      }
    
      //             
      boolean matched = super.doCredentialsMatch(token, info);
    
      if(matched)
      {
       //      
       redisUtils.remove(PREFIX+userName);
      }
      else{
       //             
       redisUtils.set(PREFIX+userName,String.valueOf(++errorNum),60*30L);
      }
    
      return matched;
     }
    }
    우선,저 희 는 AuthenticationToken 에서 이전에 저 장 된 사용자 의 로그 인 정 보 를 받 았 습 니 다.이 대상 은 사실 Controller 층 에 있 습 니 다.
    
    subject.login(new UsernamePasswordToken(userName, password));
    이 단계 에서 너의 실례 화 된 대상
    그리고 사용자 의 로그 인 이름 에 고정 접 두 사 를 붙 여(userName 과 다른 홈 키 의 충돌 을 방지 하기 위해)Redis 에서 오류 횟수 를 가 져 옵 니 다.계 정 이 동결 되 었 는 지 여 부 를 판단 하 는 논 리 는 현재 사용자 의 오류 로그 인 횟수 가 특정한 규정 치 를 초과 하 는 지 여 부 를 보 는 것 입 니 다.여기 서 저 희 는 5 번 으로 정 합 니 다.
    다음은 사용자 가 동결 되 지 않 았 고 로그 인 작업 을 수행 할 수 있 음 을 설명 합 니 다.그래서 우 리 는 부모 류 의 검증 방법 을 직접 호출 하여 암호 비교(전에 언급 한 세 줄 코드)를 하여 암호 비교 결 과 를 얻 었 습 니 다.
    일치 하면 로그 인 에 성공 하고 트 루 로 돌아 가면 됩 니 다.로그 인 에 성공 하면 모든 오류 횟수 기록 을 없 앨 수 있 습 니 다.위의 코드 는 이렇게 합 니 다.
    비교 결과 가 다 르 면 오류 기록 을 한 번 더 추가 하고 false 로 돌아 갑 니 다.
    테스트
    첫 로그 인:페이지 결과:
    image-20200225153743161
    Redis 중:
    image-20200225154251776
    그리고 10 회 연속 오류:
    페이지 결과:
    image-20200225154434428
    Redis 중:
    image-20200225154406113
    그리고 30 분 을 기 다 렸 어 요.
    오류 비밀번호 로그 인 다시 시도:
    image-20200225153743161
    다시 한 번 오 류 를 보 고 했 습 니 다.이때 Redis 에 서 는 이전의 기록 이 만 료 되 어 자동 으로 소각 되 었 기 때문에 다시 오 류 를 일 으 키 면 또 한 번 오류 기록 을 추가 합 니 다.
    image-20200225154645626
    현재 올 바른 로그 인 을 시도 합 니 다:
    image-20200225154735645
    성공 로그 인
    Redis 보기:
    image-20200225154807011
    🎉Done!
    RedisTemplate 코드 첨부
    설정 클래스
    
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    @Configuration
    public class RedisConfig {
    
     @Bean
     public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
     {
    	//             
      StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
      JdkSerializationRedisSerializer ser = new JdkSerializationRedisSerializer();
    
      RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
      template.setConnectionFactory(redisConnectionFactory);
    
      template.setKeySerializer(stringRedisSerializer);
      template.setValueSerializer(ser);
      return template;
     }
    
     @Bean
     public RedisStringService myStringRedisTemplate()
     {
      return new RedisStringService();
     }
    }
    도구 클래스 RedisStringService
    Value 를 처리 할 수 있 는 도구 클래스 는 String 입 니 다.바로 제 가 customer Matcher 에서 Autowired 를 사용 하 는 클래스 입 니 다.
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.data.redis.core.ValueOperations;
    import org.springframework.stereotype.Service;
    
    import java.util.concurrent.TimeUnit;
    
    public class RedisStringService {
    
     @Autowired
     protected StringRedisTemplate redisTemplate;
    
     /**
      *   redis  (   expire    )
      * @param key
      * @param value
      * @return
      */
     public boolean set(final String key, String value){
      boolean result = false;
      try {
       ValueOperations operations = redisTemplate.opsForValue();
       operations.set(key, value);
       result = true;
      } catch (Exception e) {
       e.getMessage();
      }
      return result;
     }
    
     /**
      *   redis  (  expire    )
      * @param key
      * @param value
      * @param expire
      * @return
      */
     public boolean set(final String key, String value, Long expire){
      boolean result = false;
      try {
       ValueOperations operations = redisTemplate.opsForValue();
       operations.set(key, value);
       redisTemplate.expire(key, expire, TimeUnit.SECONDS);
       result = true;
      } catch (Exception e) {
       e.getMessage();
      }
      return result;
     }
    
    
     /**
      *   redis  
      * @param key
      * @return
      */
     public Object get(final String key){
      Object result = null;
      try {
       ValueOperations operations = redisTemplate.opsForValue();
       result = operations.get(key);
      } catch (Exception e) {
       e.getMessage();
      }
      return result;
     }
    
     /**
      *   redis         key
      * @param key
      * @return
      */
     public boolean exists(final String key){
      boolean result = false;
      try {
       result = redisTemplate.hasKey(key);
      } catch (Exception e) {
       e.getMessage();
      }
      return result;
     }
    
     /**
      * redis  key     value
      * @param key
      * @return
      */
     public boolean remove(final String key){
      boolean result = false;
      try {
       if(exists(key)){
        redisTemplate.delete(key);
       }
       result = true;
      } catch (Exception e) {
       e.getMessage();
      }
      return result;
     }
    
     /**
      * redis  keys       value
      * @param keys
      * @return
      */
     public void remove(final String... keys){
      for(String key : keys){
       remove(key);
      }
     }
    }
    여기에 Shiro+Redis 로그 인 횟수 동결 에 관 한 글 이 소개 되 었 습 니 다.Shiro+Redis 로그 인 동결 에 관 한 더 많은 내용 은 이전 글 을 검색 하거나 아래 글 을 계속 찾 아 보 세 요.앞으로 도 많은 응원 부 탁 드 리 겠 습 니 다!

    좋은 웹페이지 즐겨찾기