一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - SpringBoot+Shiro學習之密碼加密和登錄失敗次數限制示例

SpringBoot+Shiro學習之密碼加密和登錄失敗次數限制示例

2020-08-28 10:57z77z Java教程

本篇文章主要介紹了SpringBoot+Shiro學習之密碼加密和登錄失敗次數限制示例,可以限制登陸次數,有興趣的同學可以了解一下。

這個項目寫到現在,基本的雛形出來了,在此感謝一直關注的童鞋,送你們一句最近剛學習的一句雞湯:念念不忘,必有回響。再貼一張ui圖片:

SpringBoot+Shiro學習之密碼加密和登錄失敗次數限制示例

前篇思考問題解決

前篇我們只是完成了同一賬戶的登錄人數限制shiro攔截器的編寫,對于手動踢出用戶的功能只是說了采用在session域中添加一個key為kickout的布爾值,由之前編寫的KickoutSessionControlFilter攔截器來判斷是否將用戶踢出,還沒有說怎么獲取當前在線用戶的列表的核心代碼,下面貼出來:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/**
 * <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請求假如沒登錄,那么這個請求給用戶的感覺就是沒有任何反應,而用戶又不知道用戶已經退出了。也就是說在KickoutSessionControlFilter攔截器攔截后,正常如果被踢出,就會跳轉到被踢出的提示頁面,如果是Ajax請求,給用戶的感覺就是沒有感覺,核心解決代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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異常,可以忽略。");
  }
}

這是在KickoutSessionControlFilter這個攔截器里面做的修改。

目標:

  1. 現在項目里面的密碼整個流程都是以明文的方式傳遞的。這樣在實際應用中是很不安全的,京東,開源中國等這些大公司都有泄庫事件,這樣對用戶的隱私造成巨大的影響,所以將密碼加密存儲傳輸就非常必要了。
  2. 密碼重試次數限制,也是出于安全性的考慮。

實現目標一:

shiro本身是有對密碼加密進行實現的,提供了PasswordService及CredentialsMatcher用于提供加密密碼及驗證密碼服務。

我就是自己實現的EDS加密,并且保存的加密明文是采用password+username的方式,減小了密碼相同,密文也相同的問題,這里我只是貼一下,EDS的加密解密代碼,另外我還改了MyShiroRealm文件,再查數據庫的時候加密后再查,而且在創建用戶的時候不要忘記的加密存到數據庫。這里就補貼代碼了。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/**
 * 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小時后可再次重試,如果還是重試失敗,可以鎖定如1天,以此類推,防止密碼被暴力破解。我們使用redis數據庫來保存當前用戶登錄次數,也就是執行身份認證方法:

MyShiroRealm.doGetAuthenticationInfo()的次數,如果登錄成功就清空計數。超過就返回相應錯誤信息。(redis的具體操作可以去看我之前的springboot+redis的一篇博客)根據這個邏輯,修改MyShiroRealm.java如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* 認證信息.(身份驗證) : 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.rar

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。

原文鏈接:http://www.jianshu.com/p/836308f0b81a

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 亚洲精品片 | 蜜汁肉桃全文免费阅读 | 国产精品高清一区二区三区不卡 | 国产yw193.㎝m在线观看 | 国产高清免费午夜在线视频 | 校园春色偷拍自拍 | 青青色综合 | 娇女的呻吟亲女禁忌h16 | 亚洲欧美日韩精品高清 | 日本视频观看 | 国产有码在线 | 色小妹在线 | 日本亚欧乱色视频在线观看 | 99热在线这里只有精品 | 91制片厂制作传媒网站破解 | 国产精品亚洲片在线不卡 | 美女黄板视频 | 日韩精品视频美在线精品视频 | 91日本在线 | juy_661佐佐木明希在线播放 | 日b视频免费看 | 十大网站免费货源 | 沉香如屑西瓜视频免费观看完整版 | mm131亚洲精品久久 | 色综合视频一区二区观看 | 午夜久久免影院欧洲 | 91久久99热青草国产 | 国产精品四虎在线观看免费 | 九九精品视频在线免费观看 | 国产悠悠视频在线播放 | 久久91精品国产91久 | 白丝校花掀起短裙呻吟小说 | 四虎影视在线永久免费观看 | 男人和女人日比 | 天天做日日做天天添天天欢公交车 | 闺蜜的样子小说安沁在线阅读 | 亚洲高清国产品国语在线观看 | 啊啊啊好大视频 | 免费一级日本c片完整版 | 免费日韩 | 欧美8x8x|