Spring Security 个人学习笔记

Spring Security 学习笔记

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

一、基本原理

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);
    

    SpringSecurity过滤器加载过程

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>
    <!--Spring Web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--Spring Security-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!--Spring Test-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <!--Mybatis-Plus-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.3.0</version>
    </dependency>
    <!--Mysql 驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.25</version>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.20</version>
    </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 原理

记住我实现原理

记住我底层原理

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>
    

三、微服务权限基础实例

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Vesuvius688

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

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

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

打赏作者

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

抵扣说明:

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

余额充值