SpringBoot+Shiro 학습 의 암호 화 및 로그 인 실패 횟수 제한 예시

11843 단어 SpringBoot
이 프로젝트 는 지금까지 썼 습 니 다.기본 적 인 모형 이 나 왔 습 니 다.지금까지 지 켜 봐 주신 어린이 신발 에 감 사 드 립 니 다.최근 에 배 운 닭고기 수프 한 마디 를 드 리 겠 습 니 다.잊 지 마 세 요.꼭 메아리 가 있 을 겁 니 다.ui 그림 한 장 더 붙 이기:

전편 사고 문제 해결
전편 에서 저 희 는 같은 계 정의 로그 인 인원수 제한 shiro 차단기 의 작성 만 완 료 했 습 니 다.사용 자 를 수 동 으로 차 는 기능 에 대해 서 는 session 필드 에 key 를 kickout 으로 추가 하 는 불 값 을 사용 하 였 습 니 다.이전에 작 성 된 KickoutSession Control Filter 차단기 로 사용 자 를 차 는 지 여 부 를 판단 하 였 을 뿐 현재 온라인 사용자 목록 의 핵심 코드 를 어떻게 가 져 오 는 지 는 말 하지 않 았 습 니 다.아래 에 붙 이기:

/**
 * <p>
 *      
 * </p>
 *
 * @author z77z
 * @since 2017-02-10
 */
@Service
public class SysUserService extends ServiceImpl<SysUserMapper, SysUser> {
  @Autowired
  RedisSessionDAO redisSessionDAO;

  public Page<UserOnlineBo> getPagePlus(FrontPage<UserOnlineBo> frontPage) {
    //       redis   shiro session Dao,      shiro+redis    
    //    spring     redisSessionDAO
    //    session  .
    Collection<Session> sessions = redisSessionDAO.getActiveSessions();
    Iterator<Session> it = sessions.iterator();
    List<UserOnlineBo> onlineUserList = new ArrayList<UserOnlineBo>();
    Page<UserOnlineBo> pageList = frontPage.getPagePlus();
    //   session
    while (it.hasNext()) {
      //   shiro    session 
      //         
      Session session = it.next();
      //             
      Object obj = session.getAttribute("kickout");
      if (obj != null)
        continue;
      UserOnlineBo onlineUser = getSessionBo(session);
      onlineUserList.add(onlineUser);
    }
    //   List<UserOnlineBo>   mybatisPlus   page  
    int page = frontPage.getPage() - 1;
    int rows = frontPage.getRows() - 1;
    int startIndex = page * rows;
    int endIndex = (page * rows) + rows;
    int size = onlineUserList.size();
    if (endIndex > size) {
      endIndex = size;
    }
    pageList.setRecords(onlineUserList.subList(startIndex, endIndex));
    pageList.setTotal(size);
    return pageList;
  }
  // session   UserOnline  
  private UserOnlineBo getSessionBo(Session session){
    //  session    。
    Object obj = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
    if(null == obj){
      return null;
    }
    //    SimplePrincipalCollection  。
    if(obj instanceof SimplePrincipalCollection){
      SimplePrincipalCollection spc = (SimplePrincipalCollection)obj;
      /**
       *        ,@link SampleRealm.doGetAuthenticationInfo(...)   
       * return new SimpleAuthenticationInfo(user,user.getPswd(), getName()); user   。
       */
      obj = spc.getPrimaryPrincipal();
      if(null != obj && obj instanceof SysUser){
        //  session + user     
        UserOnlineBo userBo = new UserOnlineBo((SysUser)obj);
        //            
        userBo.setLastAccess(session.getLastAccessTime());
        //   ip  
        userBo.setHost(session.getHost());
        //session ID
        userBo.setSessionId(session.getId().toString());
        //session            
        userBo.setLastLoginTime(session.getLastAccessTime());
        //     ttl(ms)
        userBo.setTimeout(session.getTimeout());
        //session    
        userBo.setStartTime(session.getStartTimestamp());
        //    
        userBo.setSessionStatus(false);
        return userBo;
      }
    }
    return null;
  }
}

코드 에 주석 이 비교적 완벽 하고 소스 코드 를 다운로드 하여 볼 수도 있 습 니 다.이렇게 결합 해서 보면 이해 하기 쉽 고 모 르 는 것 은 댓 글 에 메 시 지 를 남기 면 반드시 돌아 갑 니 다!
Ajax 요청 에 대한 최적화:여기 에는 전제 가 있 습 니 다.저 희 는 Ajax 가 페이지 redirect 와 forward 점프 를 할 수 없다 는 것 을 알 고 있 습 니 다.그래서 Ajax 요청 이 로그 인하 지 않 았 다 면 이 요청 은 사용자 에 게 아무런 반응 이 없 는 것 처럼 느껴 집 니 다.사용 자 는 사용자 가 이미 종료 되 었 는 지 모 릅 니 다.즉,KickoutSession Control Filter 차단기 가 차단 되면 정상적으로 차 이면 차 인 알림 페이지 로 넘 어 갑 니 다.Ajax 요청 이 라면 사용자 에 게 감각 이 없다 는 느낌 을 줍 니 다.핵심 해결 코드 는 다음 과 같 습 니 다.

Map<String, String> resultMap = new HashMap<String, String>();
//     Ajax  
if ("XMLHttpRequest".equalsIgnoreCase(((HttpServletRequest) request).getHeader("X-Requested-With"))) {
  resultMap.put("user_status", "300");
  resultMap.put("message", "          ,     !");
  //  json 
  out(response, resultMap);
}else{
  //   
  WebUtils.issueRedirect(request, response, kickoutUrl);
}

private void out(ServletResponse hresponse, Map<String, String> resultMap)
  throws IOException {
  try {
    hresponse.setCharacterEncoding("UTF-8");
    PrintWriter out = hresponse.getWriter();
    out.println(JSON.toJSONString(resultMap));
    out.flush();
    out.close();
  } catch (Exception e) {
    System.err.println("KickoutSessionFilter.class   JSON  ,    。");
  }
}

이것 은 KickoutSession Control Filter 라 는 차단기 에서 수정 한 것 입 니 다.
목표:
  • 현재 프로젝트 안의 비밀 번 호 는 전체 절 차 를 명문 으로 전달 하고 있다.이렇게 하면 실제 응용 에서 매우 안전 하지 않다.경 동,개원 중국 등 대기업 들 은 모두 라 이브 러 리 유출 사건 이 있어 사용자 의 프라이버시 에 큰 영향 을 미 치기 때문에 암호 화 저장 소 를 전송 하 는 것 이 필요 하 다.
  • 암호 재 시도 횟수 제한 도 안전성 을 고려 한 것 이다.
  • 목표 달성 1:
    shiro 자 체 는 암호 화 를 실현 하고 PasswordService 와 Credential Matcher 를 제공 하여 암호 화 암호 화 암호 화 및 암호 화 서 비 스 를 제공 합 니 다.
    제 가 바로 EDS 암호 화 를 실 현 했 고 저 장 된 암호 화 명문 은 password+username 방식 으로 비밀 번 호 를 줄 였 습 니 다.똑 같 고 비밀문서 도 똑 같은 문제 입 니 다.여기 서 저 는'EDS 의 암호 화 복호화 코드'를 붙 였 을 뿐 입 니 다.그리고 저 는 MyShiroRealm 파일 도 바 꾸 었 습 니 다.라 이브 러 리 를 조사 할 때 암호 화 한 다음 에 확인 하 겠 습 니 다.또한 사용 자 를 만 들 때 잊 지 말 아야 할 암호 화 를 데이터베이스 에 저장 합 니 다.여기 보조 코드 입 니 다.
    
    /**
     * DES    
     * 
     * @author z77z
     * @datetime 2017-3-13
     */
    public class MyDES {
      /**
       * DES    
       */
      private static final byte[] DES_KEY = { 21, 1, -110, 82, -32, -85, -128, -65 };
    
      /**
       *     ,  (DES)
       * 
       * @param data
       *              
       * @return       
       */
      @SuppressWarnings("restriction")
      public static String encryptBasedDes(String data) {
        String encryptedData = null;
        try {
          // DES               
          SecureRandom sr = new SecureRandom();
          DESKeySpec deskey = new DESKeySpec(DES_KEY);
          //         ,     DESKeySpec     SecretKey  
          SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
          SecretKey key = keyFactory.generateSecret(deskey);
          //     
          Cipher cipher = Cipher.getInstance("DES");
          cipher.init(Cipher.ENCRYPT_MODE, key, sr);
          //   ,            
          encryptedData = new sun.misc.BASE64Encoder().encode(cipher.doFinal(data.getBytes()));
        } catch (Exception e) {
          // log.error("    ,    :", e);
          throw new RuntimeException("    ,    :", e);
        }
        return encryptedData;
      }
    
      /**
       *     ,  (DES)
       * 
       * @param cryptData
       *          
       * @return       
       */
      @SuppressWarnings("restriction")
      public static String decryptBasedDes(String cryptData) {
        String decryptedData = null;
        try {
          // DES               
          SecureRandom sr = new SecureRandom();
          DESKeySpec deskey = new DESKeySpec(DES_KEY);
          //         ,     DESKeySpec     SecretKey  
          SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
          SecretKey key = keyFactory.generateSecret(deskey);
          //     
          Cipher cipher = Cipher.getInstance("DES");
          cipher.init(Cipher.DECRYPT_MODE, key, sr);
          //            ,   
          decryptedData = new String(cipher.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(cryptData)));
        } catch (Exception e) {
          // log.error("    ,    :", e);
          throw new RuntimeException("    ,    :", e);
        }
        return decryptedData;
      }
    
      public static void main(String[] args) {
        String str = "123456";
        // DES    
        String s1 = encryptBasedDes(str);
        System.out.println(s1);
        // DES    
        String s2 = decryptBasedDes(s1);
        System.err.println(s2);
      }
    }
    
    
    목표 달성
    1 시간 동안 비밀 번 호 를 최대 5 번 까지 재 시도 하고 시도 횟수 가 5 번 을 넘 으 면 1 시간 동안 잠 그 고 1 시간 후에 다시 시도 할 수 있 으 며,재 시도 에 실패 하면 하루 처럼 잠 그 는 것 으로 유추 해 암호 가 폭력 적 으로 풀 리 지 않도록 할 수 있다.저 희 는 redis 데이터 베 이 스 를 사용 하여 현재 사용자 의 로그 인 횟수 를 저장 합 니 다.즉,인증 방법 을 실행 하 는 것 입 니 다.
    MyShiroRealm.do GetAuthenticationInfo()의 횟수 는 로그 인 에 성공 하면 계 수 를 비 웁 니 다.초과 하면 해당 오류 정 보 를 되 돌려 줍 니 다.(redis 의 구체 적 인 조작 은 이전 spring boot+redis 의 블 로 그 를 볼 수 있 습 니 다)이 논리 에 따라 MyShiroRealm.자바 를 다음 과 같이 수정 할 수 있 습 니 다.
    
    /**
    *     .(    ) : Authentication          
     * 
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
        AuthenticationToken authcToken) throws AuthenticationException {
    
    
      System.out.println("      :MyShiroRealm.doGetAuthenticationInfo()");
    
      UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
      String name = token.getUsername();
      String password = String.valueOf(token.getPassword());
      //    ,    
      ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
      opsForValue.increment(SHIRO_LOGIN_COUNT+name, 1);
      //    5 ,          
      if(Integer.parseInt(opsForValue.get(SHIRO_LOGIN_COUNT+name))>=5){
        opsForValue.set(SHIRO_IS_LOCK+name, "LOCK");
        stringRedisTemplate.expire(SHIRO_IS_LOCK+name, 1, TimeUnit.HOURS);
      }
      if ("LOCK".equals(opsForValue.get(SHIRO_IS_LOCK+name))){
        throw new DisabledAccountException("            5 ,        !");
      }
      Map<String, Object> map = new HashMap<String, Object>();
      map.put("nickname", name);
      //             password+name
      String paw = password+name;
      String pawDES = MyDES.encryptBasedDes(paw);
      map.put("pswd", pawDES);
      SysUser user = null;
      //                 
      List<SysUser> userList = sysUserService.selectByMap(map);
      if(userList.size()!=0){
        user = userList.get(0);
      } 
      if (null == user) {
        throw new AccountException("        !");
      }else if(user.getStatus()==0){
        /**
         *      status   。     <code>DisabledAccountException</code>
         */
        throw new DisabledAccountException("            !");
      }else{
        //    
        //       last login time
        user.setLastLoginTime(new Date());
        sysUserService.updateById(user);
        //      
        opsForValue.set(SHIRO_LOGIN_COUNT+name, "0");
      }
      return new SimpleAuthenticationInfo(user, password, getName());
    }
    
    
    demo 다운로드 주소:springboot_mybatisplus_jb51.rar
    이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

    좋은 웹페이지 즐겨찾기