前面做了多个示例,包括使用jdbc和hibernate两种方式访问数据库获取用户信息和权限信息,其中一些关键步骤如下:
 
我们在SecurityConfig中配置覆盖configure方法时候,可以指定authenticationProvider,也可以不需要指定,直接指定userDetailsService。例如:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
authenticationProvider.setPasswordEncoder(passwordEncoder());
auth.authenticationProvider(authenticationProvider);
//auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
如果没有指定authenticationProvider,则security使用的是实现类DaoAuthenticationProvider。
如果指定自定义的authenticationProvider,为了方便,我们自定义的authenticationProvider也是继承自DaoAuthenticationProvider,只需要重写指定userDetailsService,authenticate方法,例如:
@Autowired
@Qualifier("userDetailsService")
@Override
public void setUserDetailsService(UserDetailsService userDetailsService) {
super.setUserDetailsService(userDetailsService);
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
try {
//调用上层验证逻辑
Authentication auth = super.authenticate(authentication);
//如果验证通过登录成功则重置尝试次数, 否则抛出异常
userDetailsDao.resetFailAttempts(authentication.getName());
return auth;
} catch (BadCredentialsException e) {
//如果验证不通过,则更新尝试次数,当超过次数以后抛出账号锁定异常
userDetailsDao.updateFailAttempts(authentication.getName());
throw e;
} catch (LockedException e){
//该用户已经被锁定,则进入这个异常
String error;
UserAttempts userAttempts =
userDetailsDao.getUserAttempts(authentication.getName());
if(userAttempts != null){
Date lastAttempts = userAttempts.getLastModified();
error = "用户已经被锁定,用户名 : "
+ authentication.getName() + "最后尝试登陆时间 : " + lastAttempts;
}else{
error = e.getMessage();
}
throw new LockedException(error);
}
}

在此方法中,仍然调用的是上层验证方法super.authenticate();在这里可以根据不同的验证异常抛出不同的异常,从而显示不同的用户账号状态,例如用户被锁定、用户失效、账号或者密码过期等,这里例子是多次登录失败锁定了用户。
 
下面我们看看security是如何验证账号的:
验证逻辑实现是在类AbstractUserDetailsAuthenticationProvider,此类实现了接口AuthenticationProvider的接口方法
Authentication authenticate(Authentication authentication)  throws AuthenticationException;

实现方法中首先获取security定义的接口UserDetails,先从缓存userCache中获取,如果不存在,则调用方法retrieveUser。

retrieveUser的方法实现是在类DaoAuthenticationProvider,这个方法中可以看到
UserDetails loadedUser;
try {
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
}
catch (UsernameNotFoundException notFound) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
passwordEncoder.isPasswordValid(userNotFoundEncodedPassword,
presentedPassword, null);
}
throw notFound;
}
.....

此处调用的是自定义的UserDetailsService中loadUserByUsername方法。于是可以看出自定义的UserDetailsService实现类关键是实现loadUserByUsername方法。

 
下面就两种方式的实现进行剖解:
1、使用jdbc方式时,我们自定义的UserDetailsService是继承了类JdbcDaoImpl,可以发现JdbcDaoImpl已经实现了接口UserDetailsService,实现了方法loadUserByUsername。
在实现方法中,关键是调用自己定义的两个方法loadUsersByUsername和createUserDetails。于是自定义的CustomUserDetailsService类只需要覆写这两个方法即可
@Override
protected List<UserDetails> loadUsersByUsername(String username) {
return getJdbcTemplate().query(super.getUsersByUsernameQuery(), new Object[]{username},
(rs, rowNum) -> {
String username1 = rs.getString("username");
String password = rs.getString("password");
boolean enabled = rs.getBoolean("enabled");
boolean accountNonExpired = rs.getBoolean("accountNonExpired");
boolean credentialsNonExpired = rs.getBoolean("credentialsNonExpired");
boolean accountNonLocked = rs.getBoolean("accountNonLocked");
return new User(username1, password, enabled, accountNonExpired, credentialsNonExpired,
accountNonLocked, AuthorityUtils.NO_AUTHORITIES);
});
}
@Override
protected UserDetails createUserDetails(String username, UserDetails userFromUserQuery, List<GrantedAuthority> combinedAuthorities) {
String returnUsername = userFromUserQuery.getUsername();
if (!super.isUsernameBasedPrimaryKey()) {
returnUsername = username;
}
return new User(returnUsername, userFromUserQuery.getPassword(),
userFromUserQuery.isEnabled(),
userFromUserQuery.isAccountNonExpired(),
userFromUserQuery.isCredentialsNonExpired(),
userFromUserQuery.isAccountNonLocked(), combinedAuthorities);
}

在这里我们根据需要指定各个值,例如用户名,密码,是否可用,账号和密码是否过期,是否账号被锁等,所以如果已经设计完成的数据表中字段名称不一致也没有关系,只要含义相同,获取值指定即可。

2、使用hibernate方式时,需要自己实现UserDetailsService接口中的方法loadUserByUsername:
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDao.findByUserName(username);
if (user == null) {
throw new UsernameNotFoundException("该用户不存在:" + username);
}
List<GrantedAuthority> authorities = buildUserAuthority(user.getUserRole());
return buildUserForAuthentication(user, authorities);
}
// 把自定义的User转换成org.springframework.security.core.userdetails.User
private org.springframework.security.core.userdetails.User buildUserForAuthentication(
User user,
List<GrantedAuthority> authorities) {
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
user.isEnabled(), user.isAccountNonExpired(), user.isCredentialsNonExpired(), user.isAccountNonLocked(), authorities);
}
private List<GrantedAuthority> buildUserAuthority(Set<UserRole> userRoles) {
Set<GrantedAuthority> setAuths = new HashSet<>();
// Build user's authorities
for (UserRole userRole : userRoles) {
setAuths.add(new SimpleGrantedAuthority(userRole.getRole()));
}
return new ArrayList<>(setAuths);
}

在方法中使用hibernate的方式获取自定义的User实例,然后转换成security中的org.springframework.security.core.userdetails.User即可,org.springframework.security.core.userdetails.User是接口UserDetails的实现类。

 
附上所有示例的代码的github地址: https://github.com/hongxf1990/spring-security-learning 
 
嘿嘿,如果觉得以上实例项目中可以借鉴的话,不妨打个赏吧
                     

 
 

spring security使用数据库验证的逻辑处理的更多相关文章

  1. spring security结合数据库验证用户-XML配置方式

    之前的用户信息我们都是使用的内存用户,测试例子可以,实际中使用肯定不行,需要结合数据库进行验证用户.这就是本节的重点: 项目目录如下:  在之前的项目中的依赖中添加两个依赖: <dependen ...

  2. spring security结合数据库验证用户-注解方式

    项目目录结构如下: 首先数据库的建立和数据导入,以及一些类的依赖参考XML配置方式,需要修改一些配置. 一.在AppConfig文件中添加DataSource的配置 @Bean(name = &quo ...

  3. spring security关闭http验证 和 springboot 使用h2数据库

    spring security关闭http验证 最近在跑demo的过程中,访问swagger页面的时候需要验证登录,记得在之前写的代码中是关闭了security验证,无需登录成功访问,直接在appli ...

  4. Spring Security 概念基础 验证流程

    Spring Security 概念基础 验证流程 认证&授权 认证:确定是否为合法用户 授权:分配角色权限(分配角色,分配资源) 认证管理器(Authentication Manager) ...

  5. 自定义Spring Security的身份验证失败处理

    1.概述 在本快速教程中,我们将演示如何在Spring Boot应用程序中自定义Spring Security的身份验证失败处理.目标是使用表单登录方法对用户进行身份验证. 2.认证和授权(Authe ...

  6. spring boot系列--spring security (基于数据库)登录和权限控制

    先说一下AuthConfig.java Spring Security的主要配置文件之一 AuthConfig 1 @Configuration 2 @EnableWebSecurity 3 publ ...

  7. Spring Security在登录验证中增加额外数据(如验证码)

    在使用Spring Security框架过程中,经常会有这样的需求,即在登录验证时,附带增加额外的数据,如验证码.用户类型等.下面将介绍如何实现. 注:我的工程是在Spring Boot框架基础上的, ...

  8. Spring Security使用数据库数据完成认证--练气后期2

    写在前面 没错,这篇文章还是练气后期!但作者我相信筑基指日可待! 在前一篇文章当中,我们简单地分析了一下Spring Security的认证流程,知道了如果想要实现对自己用户数据(账户.角色.权限)的 ...

  9. spring security使用数据库资源

    国内对权限系统的基本要求是将用户权限和被保护资源都放在数据库里进行管理,在这点上Spring Security并没有给出官方的解决方案,为此我们需要对Spring Security进行扩展.. 数据库 ...

随机推荐

  1. Duilib教程-控件练习

    一.控件消息的响应. 在HelloDuilib例子中,程序不能退出,在这里,我将添加一个关闭按钮,当点击它时,调用PostQuitMessage进行退出. 首先在界面的右上角添加一个关闭按钮,并取名为 ...

  2. spring 项目升级到spring cloud记录 数据源配置

    用的阿里的数据源  增加pom <dependency> <groupId>com.alibaba</groupId> <artifactId>drui ...

  3. $routeParams传递路由参数

    $routeParams传的值是一个对象数组. 案例: index.html <!DOCTYPE html> <html lang="zh-cn" data-ng ...

  4. 讨论cocos2d-x字体绘制原理和应用方案

    转自:http://blog.csdn.net/langresser_king/article/details/9012789 个人一直认为,文字绘制是cocos2d-x最薄弱的环节.对于愤怒的小鸟之 ...

  5. salt常用命令、模块、执行

    一.salt常用命令 salt 该命令执行salt的执行模块,通常在master端运行,也是我们最常用到的命令 salt [options] '<target>' <function ...

  6. delphi xe-intarweb 功能记录

    IWServerController.HistoryEnabled := True; //使浏览器后退.前进按钮有效TIWForm2.Create(WebApplication).Show;  //建 ...

  7. Android REST webservice 类

    App与后台交互,后台使用的是Jersey RESTful 服务.在APP端使用Android 内部集成的HttpClient接口,无需引入第三方jar包, import org.apache.htt ...

  8. <2014 05 14> Android平台下2D/3D开发攻略

    Android通过OpenGL包含了对高性能2D和3D图形的支持,尤其支持OpenGLES API.OpenGL是一个跨平台的图形API,提供了软件操作3D图形硬件的接口.OpenGLES是一个专用于 ...

  9. 面向对象 - 1.软件开发/2.异常处理/3.try...except的详细用法

    1.软件开发 软件的开发其实一整套规范,我们所学的只是其中的一小部分,一个完整的开发过程,需要明确每个阶段的任务,在保证一个阶段正确的前提下再进行下一个阶段的工作,称之为软件工程 面向对象的软件工程包 ...

  10. SQL小练习

    1.现在有两张表订单表TB_ORDER,包括字段:order_id(订单号),username(用户名),amount(订单金额),order_time(下单时间), product_id(商品ID) ...