Spring Security实战之三~使用数据库进行验证

1.前言

这一篇的内容主要是介绍如何利用数据库中存储的信息进行验证。Spring Security内置了一些类,利用这些类可以很方便的将数据库中存储的用户、密码、角色信息导入系统中进行验证。由于涉及到数据库了,因此阅读本文需要的基础知识比前两章要多一些:

  • 熟练掌握Java

  • 掌握Spring Boot基础知识

  • 了解MyBatis的基本使用方法

  • 了解MySQL的一些基本使用方法

  • 一点点前端知识包括html/css/javascript

  • 了解一点后端框架thymeleaf

2.需求以及示例代码

第一篇文章中,实现了以下需求:

  1. 网站分为首页、登录页、用户页面、管理员页面和报错页面;

  2. 使用用户名加密码登录,登录错误要报错;

  3. 不同的用户拥有不同的权限,不同的权限可以访问不同的网页;

  4. 首页和登录页不需要任何权限;

  5. 用户页面需要USER权限;

  6. 管理员页面需要ADMIN权限;

  7. 如果用户没有登录,则访问需要权限的页面时自动跳转登录页面。

第二篇中加入了以下三个功能:

  1. 修改用户名密码的参数名称

  2. 通过自定义一个AuthenticationProvider在系统中加入一个后门

  3. 将验证身份信息展示到前端

本文将加入以下功能:

  1. 从数据库中读取用户名、密码,与前端输入的信息进行对比验证;

  2. 验证通过后,登陆用户会得到数据库中存储的角色信息。

3.原理介绍

在第二章中介绍了验证的流程,因此可知,要加入想自定义的验证功能,就是向ProviderManager中加入一个自定义的AuthenticationProvider实例。为了加入使用数据库进行验证的DaoAuthenticationProvider类(这个类在我们的代码中是透明的)实例,可以使用AuthenticationManagerBuilder类的userDetailsService(UserDetailsService)方法。代码如下:

 @Override
    public void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(new CustomAuthenticationProvider(loadUserDetailsServiceImpl));
    }
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    /**
     * 日志打印接口
     */
    private LogUtil log = LogUtil.getLogger(CustomAuthenticationProvider.class);

    private UserDetailsService userDetailsService;

    public CustomAuthenticationProvider(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    /**
     * 登录密码校验接口
     * @param authentication 权限信息
     * @return 权限信息
     * @throws AuthenticationException 鉴权异常
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String userName = authentication.getName();

        log.info("CustomAuthenticationProvider.authenticate|||加载用户信息");
        // 加密后的密码
        String passwordEncrypt = CommonUtil.encrypt(authentication.getCredentials().toString());

        // 认证逻辑
        UserDetails userDetails = userDetailsService.loadUserByUsername(userName);

        if (null != userDetails) {
            if (passwordEncrypt.equals(userDetails.getPassword())) {

                // 生成令牌 这里令牌里面存入了:name,password 当然你也可以放其他内容
                return new UsernamePasswordAuthenticationToken(userName, passwordEncrypt, userDetails.getAuthorities());
            } else {
                log.warn("authenticate|{}|{}|密码验证失败", userName,
                        OperateLogConstants.AUTHENTICATION_SECRET_KEY);
                throw new BadCredentialsException(String.valueOf(ExceptionConstants.PASSWORD_IS_INCORRECT));
            }
        } else {
            log.warn("authenticate|{}|{}|用户信息未找到", userName,
                    OperateLogConstants.AUTHENTICATION_SECRET_KEY);
            throw new UsernameNotFoundException(String.valueOf(ExceptionConstants.OPERATOR_DOES_NOT_EXIST));
        }
    }

    /**
     * 是否可以提供输入类型的认证服务
     *
     * @param authentication 认证组件
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}

a、用户提交用户名、密码被SpringFilterChain中的UsernamePasswordAuthenticationFilter过滤器获取到,封装为请求Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。

b、然后过滤器将Authentication提交至认证管理器(AuthenticationManager)进行认证

c、认证成功后,AuthenticationManager身份管理器返回一个被填充数据(权限信息、身份信息)的Authentication实列。

d、SecurityContextHolder安全上下文容器将第三步填充的数据(Authentication),通过SecurityContextHolder.getContext().setAuthentication()方法,设置到其中,可以看出Authenticationmanager

接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,他的实现类为Providermanager,而Spring Security支持多种认证方式,因此ProviderManager维护一个List<AuthenticationProvider>列表,存放多种认证方式,最终实际的工作是由AuthenticationProvider完成的,咱们知道web表单对应的AuthenticationProvider实现类为DaoAuthenticationProvider,他的内部又维护着一个UserDetailService负责UserDetails的获取,最终AuthenticationProvider将UserDetails填充到Authentication。

调试代码从UsernamePasswordAuthenticationFilter开始跟踪,最后认证流程在AbstractUserDetailsAuthenticationProvider的authenticate方法中,获取用户在retrieveUser方法,密码比较在additionAuthenticationChecks方法

几个核心组件流程

4.代码解释

1.要实现数据库验证,第一步是设计mysql数据库的库表结构,为了清晰起见,本文只使用了一个表,相关sql语句在项目的user.sql文件中:

CREATE DATABASE `mysecurity` DEFAULT CHARACTER SET utf8;
​
USE `mysecurity`;
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
--  Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) DEFAULT NULL COMMENT '姓名',
  `address` varchar(64) DEFAULT NULL COMMENT '联系地址',
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '账号',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '密码',
  `roles` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '身份',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- ----------------------------
--  Records of `user`
-- ----------------------------
BEGIN;
INSERT INTO `user` VALUES ('1', 'Adam', 'beijing', 'adam','$2a$10$9SIFu8l8asZUKxtwqrJM5ujhWarz/PMnTX44wXNsBHfpJMakWw3M6', 'ROLE_USER');
INSERT INTO `user` VALUES ('2', 'SuperMan', 'shanghang', 'super','$2a$10$9SIFu8l8asZUKxtwqrJM5ujhWarz/PMnTX44wXNsBHfpJMakWw3M6', 'ROLE_USER,ROLE_ADMIN');
COMMIT;

运行此sql文件,就会建立一个user表,并插入两条记录,注意记录中的密码字段已经用BCryptPasswordEncoder加过密了,就是pwd加密后的字符串。

2.接下来就是实现UserDetails接口,MyUserBean类中唯一值得注意的就是getAuthorities方法的实现,它将数据库中的roles字段取出来分解为多个SimpleGrantedAuthority对象加入List中。

3.接下来用MyBatis实现MyUserBean的mapper接口,接口中仅定义了selectByUsername方法。

4.用MyUserDetailsService实现UserDetailsService接口,使用MyUserMapper来进行数据查询,并实现UserDetailsService的loadUserByUsername方法即可。

5.在SecurityConfiguration类中调用auth.userDetailsService(myUserDetailsService)方法,在验证链中加入一个DaoAuthenticationProvider。

如此就实现了使用数据库进行Spring Security的验证,你可以试试在数据库中加入新的记录,并使用新加入的用户登录。

5.小结

使用数据库进行验证其实只需要掌握两个接口即可,即UserDetailsService和UserDetails。除此外还要设计好自己的数据库表格。本文中为了简单,把用户名、密码和角色存储在一张表中,而实际上应该将用户名-密码和角色分开存储,便于实现动态的权限管理。这部分内容我们将会在下一篇文章中详细介绍。

另,Spring Security实际上提供了一套默认的数据库表格和具体的实现类,如果觉得自己的系统在验证功能上没有特殊性,也可以直接使用它的库表结构和实现类。

到此为止,验证功能就讲完了,下一章开始介绍鉴权,那也将是非常有意思的内容。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值