栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java > SpringBoot

Spring Security笔记:登录尝试次数限制

SpringBoot 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring Security笔记:登录尝试次数限制

今天在前面一节的基础之上,再增加一点新内容,默认情况下Spring Security不会对登录错误的尝试次数做限制,也就是说允许暴力尝试,这显然不够安全,下面的内容将带着大家一起学习如何限制登录尝试次数。

首先对之前创建的数据库表做点小调整

一、表结构调整

T_USERS增加了如下3个字段:

D_ACCOUNTNONEXPIRED,NUMBER(1) -- 表示帐号是否未过期
D_ACCOUNTNONLOCKED,NUMBER(1), -- 表示帐号是否未锁定
D_CREDENTIALSNONEXPIRED,NUMBER(1) --表示登录凭据是否未过期

要实现登录次数的限制,其实起作用的字段是D_ACCOUNTNONLOCKED,值为1时,表示正常,为0时表示被锁定,另外二个字段的作用以后的学习内容会详细解释。

新增一张表T_USER_ATTEMPTS,用来辅助记录每个用户登录错误时的尝试次数

D_ID 是流水号

D_USERNAME 用户名,外建引用T_USERS中的D_USERNAME

D_ATTEMPTS 登录次数

D_LASTMODIFIED 最后登录错误的日期

 

二、创建Model/DAO/DAOImpl

要对新加的T_USER_ATTEMPTS读写数据,得有一些操作DB的类,这里我们采用Spring的JDBCTemplate来处理,包结构参考下图:

T_USER_ATTEMPTS表对应的Model如下

 1 package com.cnblogs.yjmyzz.model;
 2 
 3 import java.util.Date;
 4 
 5 public class UserAttempts {
 6 
 7     private int id;
 8 
 9     private String username;
10     private int attempts;
11     private Date lastModified;
12 
13     public int getId() {
14         return id;
15     }
16 
17     public void setId(int id) {
18         this.id = id;
19     }
20 
21     public String getUsername() {
22         return username;
23     }
24 
25     public void setUsername(String username) {
26         this.username = username;
27     }
28 
29     public int getAttempts() {
30         return attempts;
31     }
32 
33     public void setAttempts(int attempts) {
34         this.attempts = attempts;
35     }
36 
37     public Date getLastModified() {
38         return lastModified;
39     }
40 
41     public void setLastModified(Date lastModified) {
42         this.lastModified = lastModified;
43     }
44 
45 }

对应的DAO接口

 1 package com.cnblogs.yjmyzz.dao;
 2 
 3 import com.cnblogs.yjmyzz.model.UserAttempts;
 4 
 5 public interface UserDetailsDao {
 6 
 7     void updateFailAttempts(String username);
 8 
 9     void resetFailAttempts(String username);
10 
11     UserAttempts getUserAttempts(String username);
12 
13 }

以及DAO接口的实现

  1 package com.cnblogs.yjmyzz.dao.impl;
  2 
  3 import java.sql.ResultSet;
  4 import java.sql.SQLException;
  5 import java.util.Date;
  6 
  7 import javax.annotation.PostConstruct;
  8 import javax.sql.DataSource;
  9 
 10 import org.springframework.beans.factory.annotation.Autowired;
 11 import org.springframework.dao.EmptyResultDataAccessException;
 12 import org.springframework.jdbc.core.RowMapper;
 13 import org.springframework.jdbc.core.support.JdbcDaoSupport;
 14 import org.springframework.stereotype.Repository;
 15 import org.springframework.security.authentication.LockedException;
 16 import com.cnblogs.yjmyzz.dao.UserDetailsDao;
 17 import com.cnblogs.yjmyzz.model.UserAttempts;
 18 
 19 @Repository
 20 public class UserDetailsDaoImpl extends JdbcDaoSupport implements
 21         UserDetailsDao {
 22 
 23     private static final String SQL_USERS_UPDATE_LOCKED = "UPDATE t_users SET d_accountnonlocked = ? WHERe d_username = ?";
 24     private static final String SQL_USERS_COUNT = "SELECT COUNT(*) FROM t_users WHERe d_username = ?";
 25 
 26     private static final String SQL_USER_ATTEMPTS_GET = "SELECt d_id id,d_username username,d_attempts attempts,d_lastmodified lastmodified FROM t_user_attempts WHERe d_username = ?";
 27     private static final String SQL_USER_ATTEMPTS_INSERT = "INSERT INTO t_user_attempts (d_id,d_username, d_attempts, d_lastmodified) VALUES(t_user_attempts_seq.nextval,?,?,?)";
 28     private static final String SQL_USER_ATTEMPTS_UPDATe_ATTEMPTS = "UPDATE t_user_attempts SET d_attempts = d_attempts + 1, d_lastmodified = ? WHERe d_username = ?";
 29     private static final String SQL_USER_ATTEMPTS_RESET_ATTEMPTS = "UPDATE t_user_attempts SET d_attempts = 0, d_lastmodified = null WHERe d_username = ?";
 30 
 31     private static final int MAX_ATTEMPTS = 3;
 32 
 33     @Autowired
 34     private DataSource dataSource;
 35 
 36     @PostConstruct
 37     private void initialize() {
 38         setDataSource(dataSource);
 39     }
 40 
 41     @Override
 42     public void updateFailAttempts(String username) {
 43         UserAttempts user = getUserAttempts(username);
 44         if (user == null) {
 45             if (isUserExists(username)) {
 46                 // if no record, insert a new
 47                 getJdbcTemplate().update(SQL_USER_ATTEMPTS_INSERT,
 48                         new Object[] { username, 1, new Date() });
 49             }
 50         } else {
 51 
 52             if (isUserExists(username)) {
 53                 // update attempts count, +1
 54                 getJdbcTemplate().update(SQL_USER_ATTEMPTS_UPDATE_ATTEMPTS,
 55                         new Object[] { new Date(), username });
 56             }
 57 
 58             if (user.getAttempts() + 1 >= MAX_ATTEMPTS) {
 59                 // locked user
 60                 getJdbcTemplate().update(SQL_USERS_UPDATE_LOCKED,
 61                         new Object[] { false, username });
 62                 // throw exception
 63                 throw new LockedException("User Account is locked!");
 64             }
 65 
 66         }
 67     }
 68 
 69     @Override
 70     public void resetFailAttempts(String username) {
 71         getJdbcTemplate().update(SQL_USER_ATTEMPTS_RESET_ATTEMPTS,
 72                 new Object[] { username });
 73 
 74     }
 75 
 76     @Override
 77     public UserAttempts getUserAttempts(String username) {
 78         try {
 79 
 80             UserAttempts userAttempts = getJdbcTemplate().queryForObject(
 81                     SQL_USER_ATTEMPTS_GET, new Object[] { username },
 82                     new RowMapper() {
 83                         public UserAttempts mapRow(ResultSet rs, int rowNum)
 84                                 throws SQLException {
 85 
 86                             UserAttempts user = new UserAttempts();
 87                             user.setId(rs.getInt("id"));
 88                             user.setUsername(rs.getString("username"));
 89                             user.setAttempts(rs.getInt("attempts"));
 90                             user.setLastModified(rs.getDate("lastModified"));
 91 
 92                             return user;
 93                         }
 94 
 95                     });
 96             return userAttempts;
 97 
 98         } catch (EmptyResultDataAccessException e) {
 99             return null;
100         }
101 
102     }
103 
104     private boolean isUserExists(String username) {
105 
106         boolean result = false;
107 
108         int count = getJdbcTemplate().queryForObject(SQL_USERS_COUNT,
109                 new Object[] { username }, Integer.class);
110         if (count > 0) {
111             result = true;
112         }
113 
114         return result;
115     }
116 
117 }

观察代码可以发现,对登录尝试次数的限制处理主要就在上面这个类中,登录尝试次数达到阈值3时,通过抛出异常LockedException来通知上层代码。

 

三、创建CustomUserDetailsService、LimitLoginAuthenticationProvider

 1 package com.cnblogs.yjmyzz.service;
 2 
 3 import java.sql.ResultSet;
 4 import java.sql.SQLException;
 5 import java.util.List;
 6 
 7 import org.springframework.jdbc.core.RowMapper;
 8 import org.springframework.security.core.GrantedAuthority;
 9 import org.springframework.security.core.authority.AuthorityUtils;
10 import org.springframework.security.core.userdetails.User;
11 import org.springframework.security.core.userdetails.UserDetails;
12 import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl;
13 import org.springframework.stereotype.Service;
14 
15 @Service("userDetailsService")
16 public class CustomUserDetailsService extends JdbcDaoImpl {
17     @Override
18     public void setUsersByUsernameQuery(String usersByUsernameQueryString) {
19         super.setUsersByUsernameQuery(usersByUsernameQueryString);
20     }
21 
22     @Override
23     public void setAuthoritiesByUsernameQuery(String queryString) {
24         super.setAuthoritiesByUsernameQuery(queryString);
25     }
26 
27     // override to pass get accountNonLocked
28     @Override
29     public List loadUsersByUsername(String username) {
30         return getJdbcTemplate().query(super.getUsersByUsernameQuery(),
31                 new String[] { username }, new RowMapper() {
32                     public UserDetails mapRow(ResultSet rs, int rowNum)
33                             throws SQLException {
34                         String username = rs.getString("username");
35                         String password = rs.getString("password");
36                         boolean enabled = rs.getBoolean("enabled");
37                         boolean accountNonExpired = rs
38                                 .getBoolean("accountNonExpired");
39                         boolean credentialsNonExpired = rs
40                                 .getBoolean("credentialsNonExpired");
41                         boolean accountNonLocked = rs
42                                 .getBoolean("accountNonLocked");
43 
44                         return new User(username, password, enabled,
45                                 accountNonExpired, credentialsNonExpired,
46                                 accountNonLocked, AuthorityUtils.NO_AUTHORITIES);
47                     }
48 
49                 });
50     }
51 
52     // override to pass accountNonLocked
53     @Override
54     public UserDetails createUserDetails(String username,
55             UserDetails userFromUserQuery,
56             List combinedAuthorities) {
57         String returnUsername = userFromUserQuery.getUsername();
58 
59         if (super.isUsernamebasedPrimaryKey()) {
60             returnUsername = username;
61         }
62 
63         return new User(returnUsername, userFromUserQuery.getPassword(),
64                 userFromUserQuery.isEnabled(),
65                 userFromUserQuery.isAccountNonExpired(),
66                 userFromUserQuery.isCredentialsNonExpired(),
67                 userFromUserQuery.isAccountNonLocked(), combinedAuthorities);
68     }
69 }

为什么需要这个类?因为下面这个类需要它:

 1 package com.cnblogs.yjmyzz.provider;
 2 
 3 import java.util.Date;
 4 
 5 import org.springframework.security.authentication.BadCredentialsException;
 6 import org.springframework.security.authentication.LockedException;
 7 import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
 8 import org.springframework.security.core.Authentication;
 9 import org.springframework.security.core.AuthenticationException;
10 import org.springframework.stereotype.Component;
11 
12 import com.cnblogs.yjmyzz.dao.UserDetailsDao;
13 import com.cnblogs.yjmyzz.model.UserAttempts;
14 
15 @Component("authenticationProvider")
16 public class LimitLoginAuthenticationProvider extends DaoAuthenticationProvider {
17     UserDetailsDao userDetailsDao;
18 
19     @Override
20     public Authentication authenticate(Authentication authentication)
21             throws AuthenticationException {
22 
23         try {
24 
25             Authentication auth = super.authenticate(authentication);
26 
27             // if reach here, means login success, else exception will be thrown
28             // reset the user_attempts
29             userDetailsDao.resetFailAttempts(authentication.getName());
30 
31             return auth;
32 
33         } catch (BadCredentialsException e) {
34 
35             userDetailsDao.updateFailAttempts(authentication.getName());
36             throw e;
37 
38         } catch (LockedException e) {
39 
40             String error = "";
41             UserAttempts userAttempts = userDetailsDao
42                     .getUserAttempts(authentication.getName());
43             if (userAttempts != null) {
44                 Date lastAttempts = userAttempts.getLastModified();
45                 error = "User account is locked! 

Username : " 46                         + authentication.getName() + "
Last Attempts : " 47                         + lastAttempts; 48             } else { 49                 error = e.getMessage(); 50             } 51  52             throw new LockedException(error); 53         } 54  55     } 56  57     public UserDetailsDao getUserDetailsDao() { 58         return userDetailsDao; 59     } 60  61     public void setUserDetailsDao(UserDetailsDao userDetailsDao) { 62         this.userDetailsDao = userDetailsDao; 63     } 64 }

这个类继承自org.springframework.security.authentication.dao.DaoAuthenticationProvider,而DaoAuthenticationProvider里需要一个UserDetailsService的实例,即我们刚才创建的CustomUserDetailService

LimitLoginAuthenticationProvider这个类如何使用呢?该配置文件出场了

 

四、spring-security.xml

 1 
 7 
 8     
 9         
10         
11         
12         
15         
16         
17     
18 
19     
21         
22     
23 
24     
26         
28         
30         
31     
32 
33     
35         
36         
37         
38     
39 
40     
42         
43     
44 
45 
46     
47         
48     
49 
50 

跟之前的变化有点大,47行是核心,为了实现47行的注入,需要33-38行,而为了完成authenticationProvider所需的一些property的注入,又需要其它bean的注入,所以看上去增加的内容就有点多了,但并不难理解。

 

五、运行效果

连续3次输错密码后,将看到下面的提示

这时如果查下数据库,会看到

错误尝试次数,在db中已经达到阀值3

而且该用户的“是否未锁定”字段值为0,如果要手动解锁,把该值恢复为1,并将T_USER_ATTEMPTS中的尝试次数,改到3以下即可。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/235902.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号