Spring Security快速入门(二)自定义登录逻辑

Spring Security快速入门(二)自定义登录逻辑

  • 一、UserDetailsService接口
  • 二、User类
    • 1. User类中authorities字段
  • 三、PasswordEncoder接口
  • 四、实现自定义登录逻辑
    • 1. 编写配置类
    • 2. 自定义逻辑
  • 五、总结

从上一篇博客中可以看见,当我们什么都没有配置的时候,账号和密码都是由Spring Security定义生成的。而再实际项目中账号和密码都是从数据库中查询出来的。所以我们需要通过自定义逻辑控制认证登录。

一、UserDetailsService接口

需要自定义逻辑时,我们只需编写一个类实现 UserDetailsService 接口即可。接口定义如下:
Spring Security快速入门(二)自定义登录逻辑_第1张图片
这个接口中,只有一个方法。我们将这个方法拆分成四个部分。

UserDetails:返回值,也是一个接口,具体定义如下:
Spring Security快速入门(二)自定义登录逻辑_第2张图片
我们可以从它的实现类和方法观察出,这个类其实就是一个用户类。

loadUserByUsername:方法名,见名知意,就是将我们根据username加载用户,一般都是从数据库中查询用户。
String username:参数,就是登录页面传递过来的用户名,查询条件。
UsernameNotFoundException:异常,如果在加载用户时,找不到指定用户名的具体信息,则抛出该异常。

二、User类

User类的全限定名是:org.springframework.security.core.userdetails.User。我们可以用这个类来接收loadUserByUsername()方法的返回值。我们来看一下这个类的结构:
Spring Security快速入门(二)自定义登录逻辑_第3张图片
有两个构造方法,实际上,上面的三参构造,其实就是调用下面的七参构造,默认给其他几个属性默认赋上了true
在这里插入图片描述
此类中,最重要的三个构造参数如下,也是我们必须传入的参数:

  • username:用户名
  • password:密码
  • authorities:用户具有的权限。此处不允许为 null

此处的用户名就是客户端传递过来的username, 密码则是从数据库中查询出来的密码。Spring Security会根据User中的password属性和客户端传递过来的password进行比较。如果相同,则表示认证通过,如果不相同则认证失败。

1. User类中authorities字段

authorities里面的权限对于后面学习授权是很有必要的,包含的所有内容为此用户具有的权限,如有里面没有包含某个权限,而在做某个事情时必须包含某个权限则会出现 403(禁止访问)。

通常都是通过AuthorityUtils.commaSeparatedStringToAuthorityList(“”) 来创建 authorities 集合对象的。参数是一个字符串,多个权限使用逗号分隔。

三、PasswordEncoder接口

实现自定义登录逻辑,Spring Security 要求容器中必须有 PasswordEncoder 实例。所以当自定义登录逻辑时要求必须给容器注入 PaswordEncoder 的bean对象。

我们先来看一下这个接口的结构:
Spring Security快速入门(二)自定义登录逻辑_第4张图片

  • encode() :把参数按照特定的解析规则进行解析。
  • matches() :验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹配,则返回 true;如果不匹配,则返回 false。第一个参数表示需要被解析的密码(rawPassword)。第二个参数表示存储的密码(encodedPassword)。
  • upgradeEncoding() :如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回 false。默认返回 false。

我们来看一下这个接口的一些实现类。
Spring Security快速入门(二)自定义登录逻辑_第5张图片

BCryptPasswordEncoder 是 Spring Security 官方推荐的密码解析器,平时多使用这个解析器。 是对 bcrypt 强散列方法的具体实现。是基于Hash算法实现的单向加密。可以通过strength控制加密强度,默认 10,可选范围是4-31。

四、实现自定义登录逻辑

当 进 行 自 定 义 登 录 逻 辑 时 需 要 用 到 之 前 讲 解 的 UserDetailsService 和PasswordEncoder 。但是 Spring Security 要求:当进行自定义登录逻辑时容器内必须有PasswordEncoder 实例。所以不能直接 new 对象。

1. 编写配置类

将PasswordEncoder实例放入容器中。

package com.linqibin.springsecuritystudy.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

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

2. 自定义逻辑

在 Spring Security 中实现 UserDetailsService 就表示为用户详情服务。在这个类的loadUserByUsername()方法中编写用户认证逻辑。

package com.linqibin.springsecuritystudy.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.linqibin.springsecuritystudy.entity.UserEntity;
import com.linqibin.springsecuritystudy.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        QueryWrapper<UserEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", username);

        // 根据前端传进来的username查询用户
        UserEntity user = this.userService.getOne(queryWrapper);
        if (user == null) {
            throw new UsernameNotFoundException("该用户不存在");
        }

        System.out.println(user);

        return new User(username, user.getPassword(),
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin, normal"));
    }
}

在以上代码中,我们提前在数据库中准备了一个用户,用户的密码是经过BCryptPasswordEncoder加密过的。
在这里插入图片描述

使用MybatisPlus根据用户名查询用户,如果未查到,则抛出异常。

如果查到了该用户,我们则返回一个实现了UserDetailsService接口的实体类,这里一定要注意,返回的类型必须是实现了这个接口的实体。将客户端传递过来的username和从数据库中查询出来的密码,以及权限(此例中我们的权限是自己手写的)作为参数。

用户加载成功后,Spring Security会将客户端传递过来的密码使用我们配置类中所注入的一个PasswordEncoder来加密,之后与用户中的密码进行比对,比对成功则认证通过。

五、总结

认证过程就是一个简单的比对密码的过程,Spring Security先根据用户名去数据库中查询用户,将用户的密码和权限相关加载进内存;再将客户端传进来的密码用我们的注入到Spring容器中的PasswordEncoder进行加密,随后比较。

所以我们需要做的就只是:①编写用户认证逻辑 ②注入一个PasswordEncoder实例到Spring容器中。仅此而已。

你可能感兴趣的:(Spring,Security)