上一篇:HTTPbasic和表单的认证、基本原理、源码分析
下一篇:https://blog.csdn.net/LawssssCat/article/details/105299270

代码下载:https://github.com/LawssssCat/v-security/tree/v2.0.1

自定义用户认证逻辑

  • 处理用户信息获取逻辑
  • 处理用户校验逻辑
  • 处理密码加密解密

处理用户信息获取逻辑

指定从哪里获取用户信息(数据库?缓存?第三方REST API?)

Spring Security 提供了一个接口(UserDetailsService) 处理用户信息获取


这个接口也非常简单,就是传入用户名,返回一个类 UserDetails (封装用户信息),其次会抛出异常 UsernameNotFoundException

下面,我们实现这个接口

创建接口实现 MyUserDetailsService

package cn.vshop.security.browser;

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
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.Component;

import java.util.Collection;

/** * @author alan smith * @version 1.0 * @date 2020/4/3 17:05 */
@Slf4j
// 注入 spring 容器
@Component
public class MyUserDetailsService implements UserDetailsService {

    // @Autowired
    //private DAO 对象 .... 这里就直接模拟了

    /** * 根据用户名查找用户信息,作为登录的认证的依据 * 因为在spring环境中,查找信息的方式只需要注入即可 * * @param username 用户要的认证的用户名 * @return 认证依据的详细信息 * @throws UsernameNotFoundException 没有 username 对应的用户信息 */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("登录用户名:{}", username);

        // 模拟:查出来的用户密码
        String password = "123456";
        // 授权信息,告诉SpringSecurity,当前用户一旦认证成功,拥有哪些权限
        Collection<? extends GrantedAuthority> authorities = AuthorityUtils
                // 一个工具,把字符串以空格隔开,分别存储为权限
                .commaSeparatedStringToAuthorityList(
                        // 模拟从数据库读出一下权限
                        "admin user");

        // 返回UserDetails接口
        // User是SpringSecurity提供的UserDetails接口实现
        return new User(username, password, authorities);
    }

}

保存,重启项目

访问登录

访问:http://localhost:8080/user

重定向,登录界面,登录

<mark>注意,这时,密码已改为我们写死的 123456 (用户名随意)</mark>

ok!


如果这时候,密码写错,结果如下

处理用户校验逻辑

做这个前,需要先了解上面用到 UserDetails , 我们进入到其源码

package org.springframework.security.core.userdetails;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;

import java.io.Serializable;
import java.util.Collection;

public interface UserDetails extends Serializable {
	// 权限信息
	Collection<? extends GrantedAuthority> getAuthorities();
	// 密码
	String getPassword();
	// 用户名
	String getUsername();

//--------------------- 下面四个布尔值可用来执行用户校验逻辑 --------------

	// 账户是否过期
	boolean isAccountNonExpired();
	// 账户是否被锁定
	boolean isAccountNonLocked();
	// 用户密码过期
	boolean isCredentialsNonExpired();
	// 账户是否可用(是否被删除了)
	boolean isEnabled();
}

需要说明的是:isAccountNonLocked 和 isEnabled 的对比
Spring Security 并没有明确指出两个的使用场景的不同。
但实际开发中,通常如下指定业务场景

  • isAccountNonLocked 用户是否被冻结了(一般可恢复)
  • isEnabled 用户是否被删除了(一般不可恢复)

因此,我们如果要进行用户校验逻辑的业务,只需要在代码中加上相关判断即可。

简单使用如下

修改上面代码中,最后返回的User的构造(使用7个参数的构造)【我们这时候把账号设置成了冻结】

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("登录用户名:{}", username);

        // 模拟:查出来的用户密码
        String password = "123456";
        // 授权信息,告诉SpringSecurity,当前用户一旦认证成功,拥有哪些权限
        Collection<? extends GrantedAuthority> authorities = AuthorityUtils
                // 一个工具,把字符串以空格隔开,分别存储为权限
                .commaSeparatedStringToAuthorityList(
                        // 模拟从数据库读出一下权限
                        "admin user");

        // 返回UserDetails接口
        // User是SpringSecurity提供的UserDetails接口实现
        return new User(
                username,
                password,
                // 账号是否被删除
                true,
                // 账号是否过期
                true,
                // 密码是否过期
                true,
                // 账号是否被冻结
                false,
                authorities);
    }

启动后,再次登录的结果如下

处理密码加密解密

Spring Security 了专门的加密解密的类 ,进行加密解密非常简单,下面我们只需要介绍相关的 接口 和 工具api 即可

接口名:PasswordEncoder
<mark>org.springframework.security.crypto.password</mark> 包下的

接口 PasswordEncoder 实现

有以下六个实现

具体的区别参考:加密算法:PBEncrypt(hash消息摘要:MD5、SHA;对称加密:DES、AES;非对称加密:RSA)

下面用 BCryptPasswordEncoder

BCrypt 其实就还是 hash 散列编码,可以指定 加密次数、加密盐

在注册类中注册加密器

package cn.vshop.security.browser;

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

/** * spring security 提供的 web 应用适配器 * * @author alan smith * @version 1.0 * @date 2020/4/3 12:15 */
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // 指定身份认证方式为表单
        http.formLogin()
                .and()
                // 并且认证请求
                .authorizeRequests()
                // 全部请求,都需要认证
                .anyRequest().authenticated();

    }

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

}

<mark>正常这样就可以了,但是由于我们现在认证的密码部分用的是明文,而要求的是密文,因此本例中还需要在 MyUserDetailsService 中对模拟出来的密码明文进行加密</mark>

对模拟的明文进行加密

接下来,正常登陆即可

登陆,查看日志

可以看到加密后的密码

如果说 账号已锁定,把User那里改回来即可

代码上传至:https://github.com/LawssssCat/v-security/tree/v2.1