1.初始认证授权框架——Spring Security

1.引言        

        在企业级Java Web项目的开发中,我们少不了对访问控制部分的代码的开发,比如登录,而这部分又往往涉及到应用的安全问题,所以SpringSecurity框架就应运而生了。

        Spring Security是主要解决认证(Authenticate)和授权(Authorization)的框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IOC,DI 技术(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。我们在使用过程中,只需简单的配置,自己编写逻辑部分。就可以让框架自动去实现验证功能。

2.使用

        首先,我们在maven项目的pom.xml中引入依赖spring-boot-starter-security。
        !注意:以上依赖项是带有自动配置的,一旦添加此依赖,整个项目中所有的访问,默认都是必须先登录才可以访问的,在浏览器输入任何此服务的URL,都会自动跳转到默认的登录页面。
其中默认的用户名是user,默认的密码是启动项目时自动生成的随机密码,在服务器端的控制台可以看到此密码。

        当登录后,会自动跳转到此前尝试访问的页面。Spring Security默认使用Session机制保存用户的登录状态,所以,重启服务后,登录状态会消失。在不重启的情况下,可以通过 /logout 访问“退出登录”页面,确定后也可以清除登录状态。

        但是我们在实际开发中,如果频繁去使用自动生成的密码,是有很多的不方便之处的,对于我们测试也造成了很多障碍,于是我们需要添加自己的测试用户名和密码到数据库中,以便测试登录。这就必须来了解下Spring Security中的BCrypt 算法工具类。

关于BCrypt    

        在Spring Security中,内置了BCrypt算法的工具类,此工具类可以实现使用BCrypt算法对密码进行加密、验证密码的功能。

        BCrypt算法使用了随机盐,所以,多次使用相同的原文进行加密,得到的密文都将是不同的,并且,使用的盐值会作为密文的一部分,也就不会影响验证密码了。
        在Spring Security框架中,定义了PasswordEncoder接口,表示“密码编码器”,并且使用BCryptPasswordEncoder实现了此接口。
 

使用步骤

1.创建配置类,并显示配置Bean方法

        ·说完了上面的这些,我们来看下具体的使用步骤,这里拿我们要开发管理员的新增,以及登录功能,首先在config包下建一个Security对应的配置类:
        

@Configuration
public class SecurityConfiguration { 
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
}

        这里显示的配置Bean方法,以方便我们后续获取PasswordEncoder对象。例如在Service层中,我们通过自动依赖注入,来获取到对象。

@Autowired
private PasswordEncoder passwordEncoder;

        获取到对象之后,我们就可以对管理员用户输入的密码进行加密处理,例如我们现在处理添加管理员信息的业务:

 public void addNew(AdminAddNewDTO adminAddNewDTO) {
        // 日志
        log.debug("开始处理【添加管理员】的业务,参数:{}", adminAddNewDTO);
        // 从参数中获取尝试添加的管理员的用户名
        String username = adminAddNewDTO.getUsername();
        // 调用adminMapper对象的countByUsername()方法进行统计
        int countByUsername = adminMapper.countByUsername(username);
        // 判断统计结果是否大于0
        if (countByUsername > 0) {
            // 是:日志,抛出ServiceException
            String message = "添加管理员失败,用户名【" + username + "】已经被占用!";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERR_CONFLICT, message);
        }

        // 创建新的Admin对象
        Admin admin = new Admin();
        // 调用BeanUtils.copyProperties()方法将参数的属性值复制到以上Admin对象中
        BeanUtils.copyProperties(adminAddNewDTO, admin);
        // 补全Admin对象的属性值:loginCount >>> 0
        admin.setLoginCount(0);
        // 对密码进行加密处理
        String rawPassword = admin.getPassword();
        String encodedPassword = passwordEncoder.encode(rawPassword);
        admin.setPassword(encodedPassword);
        // 日志
        log.debug("即将插入管理员数据:{}", admin);
        // 调用adminMapper对象的insert()方法插入数据,并获取返回的受影响的行数
        int rows = adminMapper.insert(admin);
        // 判断受影响的行数是否不等于1
        if (rows != 1) {
            // 是:日志,抛出ServiceException
            String message = "添加管理员失败,服务器忙,请稍后再次尝试!";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERR_INSERT, message);
        }
    }

我们在加密之后,调用dao层将封装的信息添加到数据库后,这段密码实际的显示是以密文形式存在的。
 

 注意:一旦在Spring容器中已经存在PasswordEncoder对象,Spring Security会自动使用它,所以,会导致之前控制台默认的随机密码不可用(你提交的随机密码会被加密后再进行对比,而Spring Security默认的密码并不是密文,所以对比会失败)。

        很多同学会问:这个密文有什么意义呢?它可以保证除了注册用户之外的其他人无法看到真正的密码。一旦加密后,它的破解过程是十分繁琐和困难的,几乎是不可能完成的任务,其次,我们还可以在加密的算法基础上,再加盐(salt)处理(关于加盐不懂的或者感兴趣的同学可以自行百度)。所以我们在做登录验证时,其实是通过密文二者之间进行比对的,而并不是将数据库中的密文破译后比对的。
登录
     

2.自定义实现类进行比对验证   

        使用Spring Security时,应该自定义类,实现UserDetailsService接口,在此接口中,有UserDetails loadUserByUsername(String username)方法,Spring Security会自动使用登录时输入的用户名来调用此方法,此方法返回的结果中应该包含与用户名匹配的相关信息,例如密码等,接下来,Spring Security会自动使用自动装配的密码编码器对密码进行验证。
 

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        log.debug("根据用户名{}从数据库查询用户信息……", s);
        // 调用AdminMapper对象,根据用户名(参数值)查询管理员信息
        AdminLoginInfoVO loginInfo = adminMapper.getLoginInfoByUsername(s);
        // 判断是否查询到有效结果
        if (loginInfo == null) {
            // 根据用户名没有找到任何管理员信息
            String message = "登录失败,用户名不存在!";
            log.warn(message);
            throw new UsernameNotFoundException(message);
        }        
            UserDetails userDetails = User.builder()
                    .username(loginInfo.getUsername())
                    .password(loginInfo.getPassword())
                    .accountExpired(false) // 账号是否已经过期
                    .accountLocked(false) // 账号是否已经锁定
                    .credentialsExpired(false) // 认证是否已经过期
                    .disabled(false) // 是否已经禁用
                    .authorities("这是临时使用的且无意义的权限值") // 权限,注意,此方法的参数值不可以为null
                    .build();
            return userDetails;
        } 
    }
}

        这里解释一下上面这段代码,UserDetails是SpringSecurity中的一个接口,被认证信息,以及认证结果都是这个UserDetails里面的对象。里面定义了一个抽象方法loadByUsername(), 另外框架中还有一个User类:是UserDetails接口的官方写的实现类,构造方法有三个参数:username,password,authorities,我们需要向spring security提供一个UserDetails对象。

        !!!!!!!!!!!!!!!!这个框架中的接口和类有点多,而且名字有点长,我们可以慢慢来消化,把知识点按照自己的理解去丰满起来,而不是这么抽象的理解!!!!!!!!!!!!!!!

      我们先来看一下User的相关源码,注意这个User是框架官方写的实现类,别和我们自己命名的某些实体类搞混。以助于我们理解上面代码段的意义。首先我们看下图,可以看到User就是UserDetails的一个实现类,这个UserDetails就是一个抽象出来的接口,这个接口继承了Serializable可序列化接口,所以看到User也间接实现了这个接口,有SerialVersionUId,然后这个接口里面有一些抽象方法,我们看到User里面也定义了这些对应的常量,并重写了接口中的方法,返回自己的属性。

 

         然后我们看到User这个类中有一个builder()方法,它返回的是一个UserBuilder()的对象,这个对象中有很多方法,返回值都是UserBuilder本身,所以可以通过链式表达去打点调用方法,最后调用build()方法,返回一个UserDetails

 

 简单的功能先介绍到这里,下面会再加一篇文章来介绍JWT

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leon_coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值