【SpringBoot高级篇】SpringSecurity认证 (三)

工作原理

Spring Security所解决的问题就是安全访问控制,而安全访问控制功能其实就是对所有进入系统的请求进行拦截, 校验每个请求是否能够访问它所期望的资源。根据前边知识的学习,可以通过FilterAOP等技术来实现,Spring Security对Web资源的保护是靠Filter实现的,所以从这个Filter来入手,逐步深入Spring Security原理。

当初始化Spring Security时,会创建一个名为SpringSecurityFilterChain的Servlet过滤器,类型为 org.springframework.security.web.FilterChainProxy,它实现了javax.servlet.Filter,因此外部的请求会经过此 类,下图是Spring Security过虑器链结构图:
在这里插入图片描述
FilterChainProxy是一个代理,真正起作用的是FilterChainProxy中SecurityFilterChain所包含的各个Filter,同时 这些Filter作为Bean被Spring管理,它们是Spring Security核心,各有各的职责,但他们并不直接处理用户的认 证,也不直接处理用户的授权,而是把它们交给了认证管理器(AuthenticationManager)和决策管理器 (AccessDecisionManager)进行处理

spring Security功能的实现主要是由一系列过滤器链相互配合完成。
在这里插入图片描述

  • SecurityContextPersistenceFilter 这个Filter是整个拦截过程的入口和出口(也就是第一个和最后一个拦截 器),会在请求开始时从配置好的 SecurityContextRepository 中获取 SecurityContext,然后把它设置给 SecurityContextHolder。在请求完成后将 SecurityContextHolder 持有的 SecurityContext 再保存到配置好 的 SecurityContextRepository,同时清除 securityContextHolder 所持有的 SecurityContext;
  • UsernamePasswordAuthenticationFilter 用于处理来自表单提交的认证。该表单必须提供对应的用户名和密 码,其内部还有登录成功或失败后进行处理的 AuthenticationSuccessHandler 和 AuthenticationFailureHandler,这些都可以根据需求做相关改变;
  • FilterSecurityInterceptor 是用于保护web资源的,使用AccessDecisionManager对当前用户进行授权访问;
  • ExceptionTranslationFilter 能够捕获来自 FilterChain 所有的异常,并进行处理。但是它只会处理两类异常: AuthenticationException 和 AccessDeniedException,其它的异常它会继续抛出。

认证方式

在这里插入图片描述

内存用户信息认证

基于内存UserDetailsService,不使用密码编码器,使用字符串进行比较

 @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager detailsManager = new InMemoryUserDetailsManager();
        detailsManager.createUser(User.withUsername("zhangsan").password("123").authorities("save","update").build());
        detailsManager.createUser(User.withUsername("lisi").password("456").authorities("save").build());

        return detailsManager;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

PasswordEncoder

DaoAuthenticationProvider认证处理器通过UserDetailsService获取到UserDetails后,它是如何与请求 Authentication中的密码做对比呢?

在这里Spring Security为了适应多种多样的加密类型,又做了抽象,DaoAuthenticationProvider通过 PasswordEncoder接口的matches方法进行密码的对比,而具体的密码对比细节取决于实现:


public interface PasswordEncoder {
	// 加密方法
    String encode(CharSequence var1);

    boolean matches(CharSequence var1, String var2);

    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

而Spring Security提供很多内置的PasswordEncoder,能够开箱即用,使用某种PasswordEncoder只需要进行如 下声明即可,如下:

@Bean
public PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
}

NoOpPasswordEncoder采用字符串匹配方法,不对密码进行加密比较处理,密码比较流程如下

  1. 用户输入密码(明文 )
  2. DaoAuthenticationProvider获取UserDetails(其中存储了用户的正确密码)
  3. DaoAuthenticationProvider使用PasswordEncoder对输入的密码和正确的密码进行校验,密码一致则校验通 过,否则校验失败。

NoOpPasswordEncoder的校验规则拿 输入的密码和UserDetails中的正确密码进行字符串比较,字符串内容一致 则校验通过,否则 校验失败

实际项目中推荐使用BCryptPasswordEncoder, Pbkdf2PasswordEncoder, SCryptPasswordEncoder等。

连接数据库用户信息认证

Spring Boot整合SpringSecurity(二)的基础上进行的操作

创建数据库

创建user_db数据库

CREATE DATABASE `user_db` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';

创建t_user表

CREATE TABLE `t_user` (
  `id` bigint NOT NULL COMMENT '用户id',
  `username` varchar(64) NOT NULL,
  `password` varchar(64) NOT NULL,
  `fullname` varchar(255) NOT NULL COMMENT '用户姓名',
  `mobile` varchar(11) DEFAULT NULL COMMENT '手机号',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC

pom

添加依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.2</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>

application.yml

application.yml配置数据源

spring:
  application:
    name: security-springboot
  mvc:
    view:
      prefix: /WEB-INF/views/
      suffix: .jsp
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/user_db?serverTimezone=UTC
    username: root
    password: root

entity

在entity包下定义UserDto实体类

@Data
@TableName("t_user")
public class UserDto {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String username;
    private String password;
    private String fullname;
    private String mobile;
}

mapper

在mapper包下定义UserMapper接口

public interface UserMapper extends BaseMapper<UserDto> {
}

service

在service包下定义UserService接口

public interface UserService extends IService<UserDto> {
}

在service/impl包下定义UserServiceImpl接口

实现UserDetailsService类,重写loadUserByUsername方法,更具username查询数据库,构建User用户信息认证,并返回

@Service
@Log4j2
public class UserServiceImpl extends ServiceImpl<UserMapper, UserDto> implements UserService, UserDetailsService {

    @Autowired(required = false)
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("登录的账号:{}",username);
        //根据账号去数据库查询
        QueryWrapper<UserDto> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username",username);
        UserDto userDto = userMapper.selectOne(queryWrapper);

        if (userDto == null){
            throw new UsernameNotFoundException("用户名或密码不存在");
        }

        //权限使用静态数据,后面去查数据库
        UserDetails userDetails = User.withUsername(userDto.getFullname())
                .password(userDto.getPassword())
                .authorities("save","update")
                .build();

        return userDetails;
    }
}

使用BCryptPasswordEncoder

1.在安全配置类中定义BCryptPasswordEncoder

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

2.UserDetails中的密码存储BCrypt格式
在这里插入图片描述

3.数据库中的密码应该存储BCrypt格式
在这里插入图片描述
测试数据

@SpringBootTest
class WebSecurityConfigTest {
    @Autowired
    private UserMapper userMapper;
    @Test
    public void testBCryptPasswordEncoder() {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

        UserDto userDto = new UserDto();

       //userDto.setUsername("张三");
        userDto.setUsername("lisi");
        // 使用BCryptPasswordEncoder对密码进行加密存储到数据库
        //userDto.setPassword(passwordEncoder.encode("123"));
        userDto.setPassword(passwordEncoder.encode("456"));
        //userDto.setFullname("张三");
        userDto.setFullname("李四");
        userDto.setMobile("15096000000");
        userMapper.insert(userDto);
    }
}

测试

启动测试,当输入用户名会去数据库中读取,不存在抛出异常,其他测试正常
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

李熠漾

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

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

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

打赏作者

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

抵扣说明:

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

余额充值