Spring Security应用开发(09)密码错误次数限制

实现登录时密码错误次数限制功能,就是在登录界面中当用户提交了错误的密码时在数据库中记录下这个错误次数,直到错误次数达到指定次数时,锁定用户账户,此时即便输入正确的密码,也不能登录。

需要完成如下工作:

(1)修改用户表users的结构,增加相关字段。

(2)自定义实现UserDetailsService,用于加载额外的数据字段。

(3)自定义实现AuthenticationProvider,用于捕获登录成功和失败的事件。

(3)修改spring-security.xml文件,配置上述(2)和(3)的信息。

(4)修改登录失败页面,显示具体登录错误信息。

1.1.1. 修改用户表结构

对users表的表结构做如下修改,

增加四个字段:

账户是否过期: expired

账户是否锁定:locked

密码是否过期:passwordexpired

登录失败次数:failtimes

Spring Security在UserDetails接口以及User类中均定义了前3个字段对应的属性,但是在查询数据库时,默认没有查询这三个字段,在创建User实例时均以true进行构造。在自定义UserDetailsService时将仿照JdbcDaoImpl对loadUsersByUsername()方法进行改造。

具体SQL操作如下:

mysql> alter table users add column expired boolean not null;

Query OK, 0 rows affected (0.28 sec)

Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table users add column locked boolean not null;

Query OK, 0 rows affected (0.06 sec)

Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table users add column passwordexpired boolean not null;

Query OK, 0 rows affected (0.08 sec)

Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table users add column failtimes int not null default 0;

Query OK, 0 rows affected (0.34 sec)

Records: 0  Duplicates: 0  Warnings: 0

mysql> desc users;

+-----------------+-------------+------+-----+---------+-------+

| Field           | Type        | Null | Key | Default | Extra |

+-----------------+-------------+------+-----+---------+-------+

| username        | varchar(64) | NO   | PRI | NULL    |       |

| password        | varchar(64) | NO   |     | NULL    |       |

| enabled         | tinyint(1)  | NO   |     | NULL    |       |

| expired         | tinyint(1)  | NO   |     | NULL    |       |

| locked          | tinyint(1)  | NO   |     | NULL    |       |

| passwordexpired | tinyint(1)  | NO   |     | NULL    |       |

| failtimes       | int(11)     | NO   |     | 0       |       |

+-----------------+-------------+------+-----+---------+-------+

7 rows in set (0.00 sec)

mysql> select * from users;

+----------+------------------------------------------+---------+---------+--------

+-----------------+-----------+

| username | password                                 | enabled | expired | locked |

passwordexpired | failtimes |

+----------+------------------------------------------+---------+---------+--------

+-----------------+-----------+

| lisi     | 40bd001563085fc35165329ea1ff5c5ecbdbbeef |       1 |       0 |      0 |

              0 |         0 |

| wangwu   | 40bd001563085fc35165329ea1ff5c5ecbdbbeef |       1 |       0 |      0 |

              0 |         0 |

| zhangsan | 40bd001563085fc35165329ea1ff5c5ecbdbbeef |       1 |       0 |      0 |

              0 |         0 |

+----------+------------------------------------------+---------+---------+--------

+-----------------+-----------+

3 rows in set (0.00 sec)

1.1.2. 实现自定义的UserDetailsService

(1)先定义一个UserDetailsUpdater接口。

此接口类型将作为CustomAuthenticationProvider的登录辅助信息维护对象CustomUserDetailsService的接口类型。

/**

* @ClassName: UserDetailsUpdater

* @Description: 用于维护用户登录辅助信息

* @author http://www.cnblogs.com/coe2coe/

*  

*/

public interface UserDetailsUpdater {

/**

 * 在登录密码错误和登录成功时维护登录辅助信息。

 * @param username  用户名

 * @param success   登录是否成功

 * @throws Exception

 */

void updateUser(String username, boolean success) throws Exception;

}

(2)定义自定义的CustomUserDetailsService类。

从Spring Security的JdbcDaoImpl类继承,同时实现了UserDetailsUpdater接口。

/**

* @ClassName: CustomUserDetailsService

* @Description: (1)从数据库中加载安全相关的用户信息,添加了SpringSecurity默认不包含的3个字段。

*               (2)实现UserDetailsUpdater,维护登录辅助信息。

* @author http://www.cnblogs.com/coe2coe/

*  

*/

public class CustomUserDetailsService extends JdbcDaoImpl implements UserDetailsUpdater {

/**

 * 从数据库查询用户信息。

 */

@Override

protected List<UserDetails> loadUsersByUsername(String username) {

return getJdbcTemplate().query(this.getUsersByUsernameQuery(),

new String[] { username }, new RowMapper<UserDetails>() {

@Override

public UserDetails mapRow(ResultSet rs, int rowNum)

throws SQLException {

String username = rs.getString("username");

String password = rs.getString("password");

boolean enabled = rs.getBoolean("enabled");

boolean locked = rs.getBoolean("locked");

boolean expired = rs.getBoolean("expired");

boolean passwordExpired = rs.getBoolean("passwordexpired");

return new User(username, password, enabled,

!expired,!passwordExpired,!locked,

AuthorityUtils.NO_AUTHORITIES);

}

});

}

/**

 * 主要作用是使SpringSecurity最终使用的UserDetails不必要与从数据库查询出的UserDetails完全相同。

 * 提供了一个间接的中间层。

 */

@Override

protected UserDetails createUserDetails(String username, UserDetails userFromUserQuery,

List<GrantedAuthority> combinedAuthorities) {

String returnUsername = userFromUserQuery.getUsername();

if (!this.isUsernameBasedPrimaryKey()) {

returnUsername = username;

}

return new User(returnUsername,

userFromUserQuery.getPassword(),

userFromUserQuery.isEnabled(),

userFromUserQuery.isAccountNonExpired(),

userFromUserQuery.isCredentialsNonExpired(),

userFromUserQuery.isAccountNonLocked(),

combinedAuthorities);

}

@Override

public void updateUser(String username, boolean success) throws Exception {

if(success){

this.getJdbcTemplate().update(sqlUnlockUser, username);

}

else{

this.getJdbcTemplate().update(sqlIncreaseFailTimes,username);

if(this.getJdbcTemplate().queryForObject(sqlQueryFailTimes, Integer.class,username) >= maxFailTimesBeforeLock)

{

this.getJdbcTemplate().update(sqlLockUser,username);

}

}

}

//最大的失败次数

private int     maxFailTimesBeforeLock = 5;

//解锁账户

private String  sqlUnlockUser = "update users set locked = false,failtimes=0 where username=?";

//锁定账户

private String  sqlLockUser = "update users set locked = true where username=? and locked = false";

//增加失败次数

private String  sqlIncreaseFailTimes = "update users set failtimes = failtimes + 1 where username=?";

//查询失败次数。

private String  sqlQueryFailTimes = "select failtimes from users where username=?";

public String getSqlUnlockUser() {

return sqlUnlockUser;

}

public void setSqlUnlockUser(String sqlUnlockUser) {

this.sqlUnlockUser = sqlUnlockUser;

}

public String getSqlLockUser() {

return sqlLockUser;

}

public void setSqlLockUser(String sqlLockUser) {

this.sqlLockUser = sqlLockUser;

}

public String getSqlIncreaseFailTimes() {

return sqlIncreaseFailTimes;

}

public void setSqlIncreaseFailTimes(String sqlIncreaseFailTimes) {

this.sqlIncreaseFailTimes = sqlIncreaseFailTimes;

}

}

自定义CustomAuthenticationProvider类

主要作用是在用户登录时出现密码错误时以及登录成功时进行自定义的处理。

/**

* @ClassName: CustomAuthenticationProvider

* @Description: 自定义的一个用户认证提供者。

* @author http://www.cnblogs.com/coe2coe/

*  

*/

public class CustomAuthenticationProvider extends DaoAuthenticationProvider {

@Override

public Authentication authenticate(Authentication auth) throws AuthenticationException {

System.out.println("authenticate begin------");

Authentication  authResult = null;

try{

authResult =  super.authenticate(auth);

try{//验证成功,重置密码错误次数。

this.userDetailsUpdater.updateUser(auth.getName(), true);

}

catch(Exception exp){

exp.printStackTrace();

}

}

catch(BadCredentialsException ex){//密码错误,增加密码错误次数,达到最大次数时锁定账户。

    System.out.println("BadCredentialsException:" + auth.getName());

  try{

this.userDetailsUpdater.updateUser(auth.getName(), false);

}

catch(Exception exp){

exp.printStackTrace();

}

throw ex;

}

catch(AuthenticationException ex){

System.out.println("AuthenticationException:" + auth.getName());

System.out.println(auth.getDetails());

System.out.println(auth.getPrincipal());

   throw ex;

}

System.out.println("authenticate end--------");

return authResult;

}

private UserDetailsUpdater  userDetailsUpdater;

public UserDetailsUpdater getUserDetailsUpdater() {

return userDetailsUpdater;

}

public void setUserDetailsUpdater(UserDetailsUpdater userDetailsUpdater) {

this.userDetailsUpdater = userDetailsUpdater;

}

}

1.1.3. 修改spring-security.xml文件

主要目的是将上述的自定义CustomUserDetailsService和CustomAuthenticationProvider类进行配置,并配置到AuthenticationManager中。

<!-- 用户和角色的对应关系 -->

 <sec:authentication-manager>

 <!-- 指定AuthenticationProvider为自定义的CustomAuthenticationProvider -->

   <sec:authentication-provider ref="authenticationProvider"  />

 </sec:authentication-manager>

<!-- 自定义的CustomUserDetailsService -->

<beans:bean  id="userDetailsService"  class="com.test.security.CustomUserDetailsService" >

  <beans:property name="dataSource" ref="dataSource"></beans:property>

  <beans:property name="usersByUsernameQuery"

   value="select * from users where username=?"

   ></beans:property>

</beans:bean>

<!-- 自定义的CustomAuthenticationProvider

     将UserDtailsService和UserDetailsUpdater注入其中。

 -->

<beans:bean id="authenticationProvider" class="com.test.security.CustomAuthenticationProvider">

 <beans:property name="userDetailsService" ref="userDetailsService"></beans:property>

 <beans:property name="passwordEncoder" ref="passwordEncoder"></beans:property>

 <beans:property name="userDetailsUpdater" ref="userDetailsService"></beans:property>

</beans:bean>

<!-- 仍然是使用SHA摘要算法处理密码 -->

 <beans:bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder"></beans:bean>

1.1.4. 修改登录失败页面

目的是希望在登录失败时显示具体登录错误信息。

Spring Security将登录失败时的异常对象存放在requst对象的属性中。

在login_failed.jsp中增加如下代码:

<p>${SPRING_SECURITY_LAST_EXCEPTION.message}</p>

  

登录密码错误时显示密码错误:

当累计5次密码错误之后,再次登录时显示账户已锁定:

1.1.5. 总结

有几个需要注意的地方:

(a)使用的密码摘要算法的类名可以在Spring Security的源代码中找到。

(b)原始的Spring Security加载用户表users中的信息时,没有加载登录辅助信息,所以进行了自定义,编写了新的加载过程。

(c)CustomAuthenticationProvider的bean定义中,属性userDetailsService是Spring Security的DaoAuthenticationProvider所要求的;属性userDetailsUpdater是自行添加的。二者都将指向同一个bean对象userDetailsService,该bean对象直接调用JdbcTemplate的方法操纵数据库。

时间: 2024-10-27 18:09:00

Spring Security应用开发(09)密码错误次数限制的相关文章

shiro的权限控制应用,集成spring项目,密码错误次数过多短时间锁定

以前对shiro都是一知半解,最近系统学了一遍shiro并集成到了在做的项目中. 下面就详细向大家描述一下shiro的用法. 首先是对spring的配置文件,如下: 1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3

Spring boot +Spring Security + Thymeleaf 认证失败返回错误信息

spring boot以其众多友谊的特性,如零配置.微服务等,吸引了很多的粉丝.而其与Spring Security安全框架的无缝结合,使其具备的安全的特性.在此基础上使用Thymeleaf模板引擎进行渲染,静动态结合,让页面开发更加简单.直观. 通过表单提交登录的用户名和密码是登录接口比较常见的一种设计.在初学的过程中,我也不例外的采用个这种方式.表单设计见下图. 登录成功,完成正常的主页面跳转,这个不存在问题.存在问题的是,登录失败了该咋办呢?我就在考虑,由于thymeleaf的局部刷新操作

Spring Security OAuth2 开发指南。

官方原文:http://projects.spring.io/spring-security-oauth/docs/oauth2.html 翻译及修改补充:Alex Liao. Spring OAuth2.0 提供者实现原理: Spring OAuth2.0提供者实际上分为: 授权服务 Authorization Service. 资源服务 Resource Service. 虽然这两个提供者有时候可能存在同一个应用程序中,但在Spring Security OAuth中你可以把 他它们各自放在

Spring Security应用开发(05)自定义表单认证

Spring Security自动产生的登录页面非常简陋,但是Spring Security提供了丰富的自定义功能. 1.1.1. 密码摘要处理 数据库中的密码字段可以使用SHA摘要算法处理后再保存,而不是以明文保存. mysql> select * from users; +----------+----------+---------+ | username | password | enabled | +----------+----------+---------+ | lisi | 1

Spring Security应用开发(04)HTTP basic认证

Spring Security默认是使用form-login表单认证方式. <!-- 默认使用表单认证 --> <sec:form-login /> Spring Security还提供了HTTP basic认证的配置的方式,只要在http标签中使用空的http-basic标签即可启用HTTP basic认证方式. <!-- 角色和URL模式的对应关系 --> <sec:http auto-config="true" use-expressio

Spring Security应用开发(02)基于XML配置的用户登录

1.1. 基于XML配置的登录功能 经过一系列配置之后,可以使用Spring Security内置功能实现最基本的用户登录功能以及角色验证功能,这种内置的功能没有任何实用价值,仅仅用于了解Spring Security的工作方式. (1)配置web.xml. 主要是为Spring MVC和Spring Security提供一些入口,以便有机会进行Spring MVC以及Spring Security的初始化和过滤处理等工作. <servlet> <servlet-name>spri

Spring Security应用开发(19)基于方法的授权(三)AOP

本文介绍使用AOP的配置方式来实现基于方法的授权. (1)首先使用Spring Security提供的protect-pointcut进行配置. protect-pointcut结点配置访问符合指定条件的方法锁需要的角色列表. <!-- 使用AOP的方式来定义方法级别的访问控制 --> <sec:global-method-security> <sec:protect-pointcut access="ROLE_USER,ROLE_ADMIN" expre

spring security使用哈希加密的密码

之前我们都是使用MD5 Md5PasswordEncoder 或者SHA ShaPasswordEncoder 的哈希算法进行密码加密,在spring security中依然使用只要指定使用自定义加密算法就行,现在推荐spring使用的BCrypt BCryptPasswordEncoder,一种基于随机生成salt的根据强大的哈希加密算法. 首先我们使用spring提供的加密方法对密码 123456 进行加密: 1.使用MD5加密: package com.petter.util; impor

Spring Security应用开发(14) 重要概念之授权相关概念

1.1.1. Secure Object Secure Object指的是一个Method Invovation 或者一个URL资源. 1.1.2. GrantedAuthority GrantedAuthority用于表达指定的用户获得的权限(即角色名称). public interface GrantedAuthority extends Serializable { //返回一个表达已经经过授权的字符串. //如果不符合授权条件则返回null. String getAuthority();