SpringSecurity 学习笔记

Posted dingwen_blog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringSecurity 学习笔记相关的知识,希望对你有一定的参考价值。

一、基本概念

1.认证

用户认证就是判断一个用户的身份是否合法的过程,用户去访问系统资源时系统要求验证用户的身份信息,身份合法方可继续访问,不合法则拒绝访问。常见的用户身份认证方式有:用户名密码登录,二维码登录,手机短信登录,指纹认证等方式。

2.会话

用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保证在会话中。会话就是系统为了保持当前用户的登录状态所提供的机制,常见的有基于session方式、基于token方式等。

3.授权

授权是用户认证通过根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问。

3.1 授权方式

  • RBAC基于资源的访问控制

RBAC基于资源的访问控制(Resource-Based Access Control)是按资源(或权限)进行授权。可扩展性好,当人员角色发生改变时无需修改代码。

  • RBAC基于角色的访问控制

RBAC基于角色的访问控制(Role-Based Access Control)是按角色进行授权

二、与Shiro对比

1.Shiro

Apache 旗下的轻量级权限控制框架

  • 轻量级。Shiro 主张的理念是把复杂的事情变简单。针对对性能有更高要求
    的互联网应用有更好表现
  • 通用性
    • 不局限于 Web 环境,可以脱离 Web 环境使用
    • 在 Web 环境下一些特定的需求需要手动编写代码定制

2.Spring Security

Spring Security 是 Spring 家族中的一个安全管理框架

  • 和 Spring 无缝整合
  • 全面的权限控制
  • 专门为 Web 开发而设计
    • 旧版本不能脱离 Web 环境使用
    • 新版本对整个框架进行了分层抽取,分成了核心模块和 Web 模块。单独
      引入核心模块就可以脱离 Web 环境。
  • 重量级

三、Spring Security原理

Spring Security 本质是一个过滤器链

1.重点理解过滤器

  • FilterSecurityInterceptor:是一个方法级的权限过滤器, 基本位于过滤链的最底部

    • super.beforeInvocation(fi) 表示查看之前的 filter 是否通过
    • fi.getChain().doFilter(fi.getRequest(), fi.getResponse());表示真正的调用后台的服务
    • ExceptionTranslationFilter:是个异常过滤器,用来处理在认证授权过程中抛出的异常
  • UsernamePasswordAuthenticationFilter:对/login 的 POST 请求做拦截,校验表单中用户 名,密码

2.UserDetailsService

完成用户名、密码、角色权限的认证服务接口

  • 返回值 UserDetails :系统默认的用户的主体 :用户信息
  • 方法参数username:通过用户名查询用户信息

2.PasswordEncoder

Spring Security 的密码加密解密器

  • BCryptPasswordEncoder:BCryptPasswordEncoder 是 Spring Security 官方推荐的密码解析器,是对 bcrypt 强散列方法的具体实现。是基于 Hash 算法实现的单 向加密。可以通过 strength 控制加密强度,默认 10。

  • 示例

        /**
         * 密码编码器测试
         */
        @Test
        void bCryptPasswordEncoderTest(){
            BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(10);
            String encodePassword = bCryptPasswordEncoder.encode("123456");
            log.info("加密之后的密码:{}",encodePassword);
            boolean matches = bCryptPasswordEncoder.matches("123456",encodePassword);
            log.info("密码比较结果:{}",matches);
    
        }
    

四、SpringSecurity Web 权限方案

1. 设置登录系统的账号、密码

默认用户名为:user,密码见项目启动之后的控制台打印。每次都不一样。

1.1 方式一:通过配置文件修改

  security:
    user:
      name: dingwen
      password: 123456

1.2 方式二: 通过配置类自定义内存用户

package com.dingwen.spsest.config;

import com.dingwen.spsest.service.impl.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;

/**
 * security 配置类
 *
 * @author dingwen
 * 2021.05.17 15:31
 */

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 用户详细信息服务
     *
     * @return {@link UserDetailsService}
     */
    @Bean
    public UserDetailsService userDetailsService() {

        // 内存用户测试
        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
        // 逗号分隔的字符串到授权列表
        List<GrantedAuthority> authorityList = Authority    Utils.commaSeparatedStringToAuthorityList("admin,user");
        // 内存用户
        inMemoryUserDetailsManager.createUser(User.withUsername("dingwen")
                .password(bCryptPasswordEncoder()
                        .encode("123456"))
                .authorities(authorityList)
                .build());
        return inMemoryUserDetailsManager;
    
    }

    /**
     * 配置
     *
     * @param http http
     * @throws Exception 异常
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin() //表单登录
                .and()
                .authorizeRequests() // 认证配置
                .anyRequest() // 任何请求
                .authenticated(); // 都需要认证通过
    }
}

2.实现数据库认证来完成用户登录

项目结构

在这里插入图片描述

2.1 整合Mybatis-plus

2.1.1 maven 依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.dingwen</groupId>
    <artifactId>spring-security-study</artifactId>
    <version>1.0</version>
    <name>spring-security-study</name>
    <description>spring-security-study</description>
    <properties>
        <java.version>1.8</java.version>
        <mybatis.plus.boot.starter.version>3.4.2</mybatis.plus.boot.starter.version>
        <mysql.connector.java.version>8.0.15</mysql.connector.java.version>
    </properties>
    <dependencies>
        <!--boot-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

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

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

        <!--security-->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>


        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.connector.java.version}</version>
        </dependency>

        <!--mybatis - plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis.plus.boot.starter.version}</version>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.1.2 配置类

package com.dingwen.spsest.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * mybatis-plus 配置
 *
 * @author dingwen
 * 2021.05.11 17:00
 */
@Configuration
// 注意此处配置了包扫描,无需再启动类中再配置
@MapperScan("com.dingwen.spsest.mapper")
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();

        // 自动分页插件
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));

        // 防止全表更新、删除插件
        mybatisPlusInterceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());

        return  mybatisPlusInterceptor;
    }
}

2.1.3 项目配置文件配置

spring:
  datasource:
    url: jdbc:mysql://192.168.233.128:3306/spring-security-study?characterEncoding=utf-8&useSSL=true&serverTimezone=GMT
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
  global-config:
    banner: false
  mapper-locations: classpath*:mapper/*.xml

logging:
  config: classpath:log/logback-spring.xml

2.2 准备SQL

-- 用户表
	-- 密码: 123456
create table if not exists `sss_user`(
	`id` bigint primary key auto_increment comment '用户表主键ID',
	`username` varchar(20) unique not null comment '用户名',
	`password` varchar(100) not null comment '密码'
)engine=innodb auto_increment=10 default charset=utf8 comment='用户表';


insert into `sss_user` (`username`,`password`) values
('admin','$2a$10$U1Ef55PpIqaUHBqmip8Lc.ld22RtMRtNEsVFLk0kw5XTIBCAm84Eu'),
('xiaoming','$2a$10$U1Ef55PpIqaUHBqmip8Lc.ld22RtMRtNEsVFLk0kw5XTIBCAm84Eu'),
('lihua','$2a$10$U1Ef55PpIqaUHBqmip8Lc.ld22RtMRtNEsVFLk0kw5XTIBCAm84Eu'),
('lucy','$2a$10$U1Ef55PpIqaUHBqmip8Lc.ld22RtMRtNEsVFLk0kw5XTIBCAm84Eu');

2.3 准备用户实体

package com.dingwen.spsest.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.io.Serializable;

/**
 * 用户实体
 *
 * @author dingwen
 * 2021.05.18 11:07
 */
@Getter
@Setter
@ToString
@TableName(value = "sss_user")
public class UserEntity implements Serializable {
    private static final long serialVersionUID = 9127742447760082647L;

    /**
     * id
     */
    @TableId(value = "id",type = IdType.AUTO)
    private Integer id;
    /**
     * 用户名
     */
    @TableField(value = "username")
    private String username;
    /**
     * 密码
     */
    @TableField("password")
    private String password;

}

2.4 用户查询服务

package com.dingwen.spsest.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.dingwen.spsest.entity.MenuEntity;
import com.dingwen.spsest.entity.RoleEntity;
import com.dingwen.spsest.entity.UserEntity;
import com.dingwen.spsest.mapper.MenuMapper;
import com.dingwen.spsest.mapper.RoleMapper;
import com.dingwen.spsest.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * user service impl
 *
 * @author dingwen
 * 2021.05.18 11:15
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {


    /**
     * 用户映射器
     */
    private final UserMapper userMapper;

    

    @Autowired
    public UserDetailsServiceImpl(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    /**
     * 根据用户名查询用户
     *
     * @param username 用户名
     * @return {@link UserDetails}
     * @throws UsernameNotFoundException 找不到用户
     */
    @Override
    public UserDetails loadUserByUsername(String username)以上是关于SpringSecurity 学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

SpringSecurity学习笔记:搭建最简单的SpringSecurity应用

SpringSecurity - 学习笔记 - 会话管理之并发控制:同一账号只允许在一个设备登录

SpringSecurity - 学习笔记 - 会话管理之并发控制:同一账号只允许在一个设备登录

史上最全最精简的学习路线图!王者笔记!

[原创]java WEB学习笔记61:Struts2学习之路--通用标签 property,uri,param,set,push,if-else,itertor,sort,date,a标签等(代码片段

字节大神强推千页PDF学习笔记,成功定级腾讯T3-2