Spring Security 个人学习笔记

Spring Security 学习笔记

本篇博客是基于B站尚硅谷 Spring Security 框架教程学习并整理的个人学习笔记,作者是初学者,笔记中若有错误的地方欢迎大家留言评论、批评指正。

目录

  • Spring Security 学习笔记
    • 一、基本原理
      • 1.1 过滤器加载过程
      • 1.2 两个重要接口
        • 1.2.1 UserDetailsService
        • 1.2.2 PasswordEncoder
    • 二、Web权限基础实例
      • 2.1 引入相关依赖
      • 2.2 创建数据库和数据库表
      • 2.3 创建实体类并配置数据库连接
      • 2.4 整合Mybatis-Plus创建 DAO 层
      • 2.5 在 Service 层中调用 DAO 层接口
      • 2.6 编写 Spring Security 配置类
      • 2.7 自定义登录页和403页面
      • 2.8 编写控制类进行测试
      • 2.9 通过注解的方式实现权限和角色的控制
        • 2.9.1 启动注解
        • 2.9.2 配置注解
        • 2.9.3 设置角色和权限
      • 2.10 用户注销
        • 2.10.1 在配置类中添加退出的配置
        • 2.10.2 测试
      • 2.11 实现记住我功能
        • 2.11.1 原理
        • 2.11.2 功能实现
    • 三、微服务权限基础实例

一、基本原理

Spring Scurity 本质是一个过滤器链

1.1 过滤器加载过程

  1. 使用 SpringSecurity 配置过滤器 DelegatingFilterProxy;

  2. 在 DelegatingFilterProxy 的 doFilter() 方法中调用了 initDelegate() 方法初始化成员变量 delegate;

     delegateToUse = this.initDelegate(wac);
     this.delegate = delegateToUse;
    
  3. 在 initDelegate() 方法中通过 getTargetBeanName() 从 Spring 容器中获取到 Filter 的实现类 FilterChainProxy;

  4. 在 FilterChainProxy 中的 doFilter() 方法中,最终调用了doFilterInternal()方法;

  5. 在 doFilterInternal() 方法中以列表的形式获取到了过滤器链;

    List<Filter> filters = this.getFilters((HttpServletRequest)firewallRequest);
    

    Spring Security 个人学习笔记_第1张图片

1.2 两个重要接口

1.2.1 UserDetailsService

使用方法

  1. 创建类继承 UsernamePasswordAuthenticationFilter 类,重写 attemptAuthentication()、successfulAuthentication() 和 unsuccessfulAuthentication() 方法;
  2. 创建类实现 UserDetailsService 接口,编写查询数据库中用户名和密码的过程,返回 User 对象,这个 User 对象是安全框架 (Spring Security) 提供的对象;
1.2.2 PasswordEncoder

数据加密接口,用于加密 User 对象里面的密码。

二、Web权限基础实例

2.1 引入相关依赖

创建 Spring Boot 项目,并在 pom.xml 文件中导入相关依赖

<dependencies>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-securityartifactId>
    dependency>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-testartifactId>
        <scope>testscope>
    dependency>
    
    <dependency>
        <groupId>com.baomidougroupId>
        <artifactId>mybatis-plus-boot-starterartifactId>
        <version>3.3.0version>
    dependency>
    
    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
        <version>8.0.25version>
    dependency>
    
    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
        <version>1.16.20version>
    dependency>
dependencies> 

2.2 创建数据库和数据库表

在数据库中建立一个数据库,然后执行下列 sql 语句创建表

drop table if exists `tb_user_info`;
create table `tb_user_info` (
	`id` int(11) auto_increment,
    `username` varchar(20) not null,
    `password` varchar(20) not null,
    primary key(`id`)
)engine=INNODB default charset=utf8;

2.3 创建实体类并配置数据库连接

在项目文件夹下新建包 pojo,然后创建 UserInfo.java

@Data
@AllArgsConstructor
@NoArgsConstructor
//以上三个是 lombok 插件的注解,自动生成构造方法和各个属性的 get/set 方法
@TableName("tb_user_info")
public class UserInfo {
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    private String username;
    private String password;
}

在 resource/application.yml 配置如下信息

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/数据库名?useUnicode=true&characterEncoding=UTF-8&serverTimeZone=Asia/Shanghai
    username: 访问数据库的用户名
    password: 访问数据库的密码
    driver-class-name: com.mysql.cj.jdbc.Driver

2.4 整合Mybatis-Plus创建 DAO 层

在项目文件夹下新建包 dao/mapper,然后创建 UserInfoMapper.java

@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}

继承 Mybatis-Plus 中提供的 BaseMapper 类会自动完成简单 CRUD 的接口创建。

2.5 在 Service 层中调用 DAO 层接口

在项目文件夹下新建包 service,然后创建自定义的 MyUserDetailsService.java

@Service("myUserDetailsService")
public class MyUserDetailsService implements UserDetailsService {
    @Resource
    private UserInfoMapper userInfoMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 调用 userInfoMapper 方法,根据用户名查询数据库
        QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
        wrapper.eq("username", username);
        UserInfo userInfo = userInfoMapper.selectOne(wrapper);
        // 判断
        if(userInfo == null) {
            throw new UsernameNotFoundException("用户不存在!");
        }

        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,superAdmin,ROLE_admin,ROLE_superAdmin");
        return new User(userInfo.getUsername(),
                new BCryptPasswordEncoder().encode(userInfo.getPassword()), auths);
    }
}

自定义的 MyUserDetailsService 实现 UserDetailsService 接口可以调用 DAO 层的方法查询数据库中的用户信息,然后交由 Spring Security 进行验证。

2.6 编写 Spring Security 配置类

在项目文件夹下新建包 config,然后创建 WebSecurityConfig.java

@Configuration	
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    private UserDetailsService myUserDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置没有权限访问跳转自定义页面
        http.exceptionHandling().accessDeniedPage("/403.html");
        http.formLogin()							//自定义登录页面
                .loginPage("/login.html")			//登录页面设置
                .loginProcessingUrl("/user/login")	//登录访问路径
                .defaultSuccessUrl("/test/index").permitAll()	//登录成功之后,跳转路径
                .and().authorizeRequests()
                .antMatchers("/","user/login").permitAll()		//直接放行的页面/资源
                //.antMatchers("/test/hello").hasAnyAuthority("admin,superAdmin")
                //.antMatchers("/test/hello").hasAnyRole("admin,superAdmin")
                .antMatchers("/test/hello").hasRole("visitor")
                .anyRequest().authenticated()   //任何请求都要授权
                .and().csrf().disable();    	//关闭跨域请求防护
    }
}

代码中是通过配置的方式是实现权限和角色的控制

.antMatchers("URL").hasAuthority("权限名")
.antMatchers("URL").hasAnyAuthority("权限1,权限2...")
.antMatchers("URL").hasRole("角色名")
.antMatchers("URL").hasAnyRole("角色1,角色2...")

在自定义的 MyUserDetailsService 类中的 loadUserByUsername() 方法中编写权限和角色的集合,以 “ROLE_” 为前缀的是角色名。

List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,superAdmin,ROLE_admin,ROLE_superAdmin");

2.7 自定义登录页和403页面

在 resource/static文件夹下创建 login.html 和 403.html

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页title>
head>
<body>
    <form action="/user/login" method="post">
        用户名:<input name="username" type="text">
        br>
        密  码:<input name="password" type="password">
        br>
        <input type="submit" name="login_btn" value="登录"/>
    form>
body>
html>
DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>无权限title>
head>
<body>
  <h1>您没有相关权限,无法访问!h1>
body>
html>

2.8 编写控制类进行测试

在项目文件夹下新建包 controller,然后创建 TestController.java

@RestController
@RequestMapping("/test")
public class TestController {

    @RequestMapping("/hello")
    public String hello() {
        return "Hello Spring Security!";
    }

    @RequestMapping("/index")
    public String index() {
        return "我是起始页!";
    }
}

2.9 通过注解的方式实现权限和角色的控制

以上代码中是通过在 WebSecurityConfig 配置类中通过配置的方式进行权限和角色的控制,下面则使用注解进行权限和角色的控制。(个人认为注解可以简化代码,更方便理清代码的逻辑)

2.9.1 启动注解

在启动类/配置类中开启注解

//分别开启 @Secured 和 @PreAuthorize、PostAuthorize 注解
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@SpringBootApplication
public class SpringSecurityStudyApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringSecurityStudyApplication.class, args);
    }
}
2.9.2 配置注解

在 Controller 层的方法上使用注解,设置角色/权限

@RestController
@RequestMapping("/test")
public class TestController {
	//@Secured 只作用于用户角色
    @RequestMapping("/add")
    @Secured({"ROLE_admin","ROLE_superAdmin"})
    public String add() {
        return "添加用户!";
    }

    //@PreAuthorize 作用于角色和权限,是在执行方法前验证权限/角色
    @RequestMapping("/del")
    //@PreAuthorize("hasAnyAuthority('admin','visitor')")
    @PreAuthorize("hasAnyRole('ROLE_admin', 'visitor')")    //角色名不加前缀也可以
    public String del() {
        return "删除用户!";
    }
	
    //@PostAuthorize 作用于角色和权限,是在执行方法后才验证权限/角色
    @RequestMapping("/upd")
    @PostAuthorize("hasAnyAuthority('admin','visitor')")
    //@PostAuthorize("hasAnyRole('ROLE_admin', 'visitor')")    //角色名不加前缀也可以
    public String upd() {
        //若用户没有相关权限/角色,也会执行下面的语句,但是在网页中会跳转到 403.html
        System.out.println("执行了更新用户的方法!");
        return "更新用户!";
    }
}
2.9.3 设置角色和权限

在自定义的 MyUserDetailsService 中设置用户角色和权限

@Service("myUserDetailsService")
public class MyUserDetailsService implements UserDetailsService {
    @Resource
    private UserInfoMapper userInfoMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 调用 userInfoMapper 方法,根据用户名查询数据库
        QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
        wrapper.eq("username", username);
        UserInfo userInfo = userInfoMapper.selectOne(wrapper);
        // 判断
        if(userInfo == null) {
            throw new UsernameNotFoundException("用户不存在!");
        }
		//!!!在此设置用户角色和权限!!!
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,superAdmin,visitor,ROLE_admin,ROLE_superAdmin,ROLE_visitor");
        return new User(userInfo.getUsername(),
                new BCryptPasswordEncoder().encode(userInfo.getPassword()), auths);
    }
}

2.10 用户注销

2.10.1 在配置类中添加退出的配置

在 WebSecurityConfig 中的 configure 方法中添加

//配置退出
http.logout().logoutUrl("/logout")
    .logoutSuccessUrl("/login.html");
2.10.2 测试
  1. 创建一个登录成功页,添加退出的超链接

    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>成功页title>
    head>
    <body>
      <h1>登录成功!h1>
      <a href="/logout">退出a>
    body>
    html>
    
  2. 修改配置类中登录成功跳转到的页面URL

    //登录成功之后,跳转路径
    .defaultSuccessUrl("/loginSuccess.html").permitAll()
    
  3. 登录成功后在成功页面点击退出,再去访问其它URL

2.11 实现记住我功能

2.11.1 原理

Spring Security 个人学习笔记_第2张图片

Spring Security 个人学习笔记_第3张图片

2.11.2 功能实现
  1. 在数据库中创建表

    drop table if exists `persistent_logins`;
    create table `persistent_logins`(
    	`username` varchar(64) not null,
    	`series` varchar(64) not null,
    	`token` varchar(64) not null,
    	`last_used` timestamp not null default current_timestamp on update current_timestamp,
    	primary key(`series`)
    )engine=INNODB default charset=utf8;
    
  2. 在 WebSecurityConfig 配置类中注入数据源,配置数据库操作对象

    @Resource
    private DataSource dataSource;	//注入数据源
    
    //配置数据库操作对象
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        return jdbcTokenRepository;
    }
    
  3. 在 WebSecurityConfig 配置类的 configure() 方法中配置 remember me

    .and().rememberMe()
        .tokenRepository(persistentTokenRepository())
        .tokenValiditySeconds(60)   //有效时长, 单位是秒
        .userDetailsService(myUserDetailsService)
    
  4. 在登录页面中添加记住我复选框

    <input name="remember-me" type="checkbox"/> 记住我
    br>
    

三、微服务权限基础实例

学完微服务后再学习记录…

你可能感兴趣的:(个人笔记,spring,boot,web安全,安全)