Geeks_Z の Blog Geeks_Z の Blog
首页
  • 学习笔记

    • 《HTML》
    • 《CSS》
    • 《JavaWeb》
    • 《Vue》
  • 后端文章

    • Linux
    • Maven
    • 汇编语言
    • 软件工程
    • 计算机网络概述
    • Conda
    • Pip
    • Shell
    • SSH
    • Mac快捷键
    • Zotero
  • 学习笔记

    • 《数据结构与算法》
    • 《算法设计与分析》
    • 《Spring》
    • 《SpringMVC》
    • 《SpringBoot》
    • 《SpringCloud》
    • 《Nginx》
  • 深度学习文章
  • 学习笔记

    • 《PyTorch》
    • 《ReinforementLearning》
    • 《MetaLearning》
  • 学习笔记

    • 《高等数学》
    • 《线性代数》
    • 《概率论与数理统计》
  • 增量学习
  • 哈希学习
GitHub (opens new window)

Geeks_Z

AI小学生
首页
  • 学习笔记

    • 《HTML》
    • 《CSS》
    • 《JavaWeb》
    • 《Vue》
  • 后端文章

    • Linux
    • Maven
    • 汇编语言
    • 软件工程
    • 计算机网络概述
    • Conda
    • Pip
    • Shell
    • SSH
    • Mac快捷键
    • Zotero
  • 学习笔记

    • 《数据结构与算法》
    • 《算法设计与分析》
    • 《Spring》
    • 《SpringMVC》
    • 《SpringBoot》
    • 《SpringCloud》
    • 《Nginx》
  • 深度学习文章
  • 学习笔记

    • 《PyTorch》
    • 《ReinforementLearning》
    • 《MetaLearning》
  • 学习笔记

    • 《高等数学》
    • 《线性代数》
    • 《概率论与数理统计》
  • 增量学习
  • 哈希学习
GitHub (opens new window)
  • Linux

  • 数据结构与算法

  • 算法设计与分析

  • Java

  • Python

  • 设计模式

  • 计算机网络

  • Spring笔记

  • SpringMVC笔记

  • SpringBoot笔记

  • SpringSecurity

    • SpringSecurity
    • SpringSecurity简介
    • SpringSecurity Web 权限方案
    • 新建 Markdown
      • 引入登录页面
      • 设置未授权的请求跳转到登录页
      • Controller
    • 新建 Markdown
    • Handler
    • 注解使用
    • CSRF
    • 访问控制
  • Elasticsearch笔记

  • RabbitMQ笔记

  • Docker笔记

  • MySQL

  • Redis

  • Mybatis

  • MybatisPlus

  • Nginx

  • Kubernetes笔记

  • Git

  • Software

  • 微服务笔记

  • bug

  • BackEndNotes
  • SpringSecurity
Geeks_Z
2023-09-04
目录

新建 Markdown

准备 sql

create table users(
 	id bigint primary key auto_increment,
	username varchar(20) unique not null,
	password varchar(100)
);
-- 密码 123
insert into users values(1,'Tom','123');
-- 密码 321
insert into users values(2,'Jerry','321');
1
2
3
4
5
6
7
8
9

添加依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency><!--mybatis-plus-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.0.5</version>
    </dependency>
    <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.49</version>
    </dependency>
    <!--lombok 用来简化实体类-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

application.yaml

# 配置数据库连接信息
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://security?serverTimezone=GMT%2B8
    username: root
    password: 1234
1
2
3
4
5
6
7

pojo

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("users")
public class User {
    private Integer id;
    private String username;
    private String password;
}
1
2
3
4
5
6
7
8
9

mapper

@Mapper
public interface UserMapper extends BaseMapper<User> {
}
1
2
3

UserDetailsService

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;

    /**
     * 作用: 进行用户名的判断
     * 运行流程: 这个方法会传入一个用户名, 我们可以根据用户名进行查询, 并将查询结果交给SpringSecurity,
     *          SpringSecurity会帮我们进行密码的判断操作, 我们需要负责用户名的判断。
     * 总结: SpringSecurity会根据我们返回的UserDetails对象进行密码的判断, 但是并不会判断用户名,
     *       因此我们需要编写用户名的判断操作
     * @param username 登录时传入的用户名
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 调用userMapper方法, 根据用户名查询数据库
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", username);
        User user = userMapper.selectOne(queryWrapper);
        // 判断
        if(user == null){
            throw new UsernameNotFoundException("用户不存在");
        }
        // 设置用户拥有的权限, 多个权限之间使用逗号分割
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
        return new org.springframework.security.core.userdetails.User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()), // BCryptPasswordEncoder在对密码进行对比时, 一定是拿明文和密文进行对别, 所有我们这里进行手动加密, 因为我们数据库中存的就是明文, 如果数据库中存的是密文就不需要我们手动进行加密了;
                auths);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

SecurityConfig

/**
 * 1.创建一个配置类继承自WebSecurityConfigurerAdapter
 * 2.重写configure(AuthenticationManagerBuilder auth)方法, 给auth传入一个UserDetailsService接口的实现类
 * 3.编写一个UserDetailsService的实现类, 重写loadUserByUsername方法, 进行用户名判断, 并返回UserDetails对象交给SpringSecurity进行密码的判断
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;

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

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

基于数据库记住我

原理

  1. SpringSecurity 实现自动登录是基于 Cookie技术,但是 Cookie 是存在浏览器中的,用 Cookie 存储密码是不安全的;因此第一次登入时 SpringSecurity 通过 Cookie 存入加密且唯一的唯一标识,再向数据库中添加一条数据 ( 包括唯一标识和用户信息字段 )

  2. 再次访问会获取到这个 Cookie 信息,通过这个信息查询数据库中对应的数据,如果查询到用户信息直接进行登录。

    image-20230904085058347

创建表

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;
1
2
3
4
5
6
7
8

注意:表结构必须一模一样,包括表名。也可以让 SpringSecurity 自动创建。

// JdbcTokenRepositoryImpl类中的部分属性
public class JdbcTokenRepositoryImpl extends JdbcDaoSupport implements PersistentTokenRepository {

   /** Default SQL for creating the database table to store the tokens */
   public static final String CREATE_TABLE_SQL = "create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, "
         + "token varchar(64) not null, last_used timestamp not null)";

   /** The default SQL used by the <tt>getTokenBySeries</tt> query */
   public static final String DEF_TOKEN_BY_SERIES_SQL = "select username,series,token,last_used from persistent_logins where series = ?";

   /** The default SQL used by <tt>createNewToken</tt> */
   public static final String DEF_INSERT_TOKEN_SQL = "insert into persistent_logins (username, series, token, last_used) values(?,?,?,?)";

   /** The default SQL used by <tt>updateToken</tt> */
   public static final String DEF_UPDATE_TOKEN_SQL = "update persistent_logins set token = ?, last_used = ? where series = ?";

   /** The default SQL used by <tt>removeUserTokens</tt> */
   public static final String DEF_REMOVE_USER_TOKENS_SQL = "delete from persistent_logins where username = ?";

   private String tokensBySeriesSql = DEF_TOKEN_BY_SERIES_SQL;

   private String insertTokenSql = DEF_INSERT_TOKEN_SQL;

   private String updateTokenSql = DEF_UPDATE_TOKEN_SQL;

   private String removeUserTokensSql = DEF_REMOVE_USER_TOKENS_SQL;

   private boolean createTableOnStartup;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

可以看到 JdbcTokenRepositoryImpl 类中定义了默认的 建表、CRUD 的 SQL 语句,SpringSecurity 就是根据这个类实现自动登录中的数据库操作的。

编写自动登录配置类

package org.hong.springsecurity01.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.sql.DataSource;

@Configuration
public class BrowserSecurityConfig {
    // 注入数据源
    @Autowired
    private DataSource dataSource;
	
    /**
     * PersistentTokenRepository组件用于创建、保存、修改、删除Token的操作
     * SpringSecurity会使用此组件来实现rememberme功能
     * @return
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        // 注入数据源
        jdbcTokenRepository.setDataSource(dataSource);
        // 自动创建表,第一次执行会创建,之后执行就要删除掉, 不然报错!
        jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

修改安全配置类

修改 configure(HttpSecurity http) 方法

// 自动注入persistentTokenRepository
@Autowired
private PersistentTokenRepository persistentTokenRepository;
http.rememberMe()
    .tokenRepository(persistentTokenRepository) // 开启自动登录功能
    .tokenValiditySeconds(60)// 设置有效时长, 单位秒; 默认为两周
    .userDetailsService(userDetailsService); // 绑定userDetailsServic, 自动登录也会运行这个service, 绑定用户的权限、角色...
1
2
3
4
5
6
7

页面添加记住我复选框

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/user/login" method="post">
        用户名:<input type="text" name="username"/><br/>
        密码:<input type="password" name="password"/><br/>
        <!-- name值必须为remember-me -->
        <input type="checkbox" name="remember-me">自动登录<br/>
        <input type="submit" value="登录"/>
    </form>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

注意:自动登录的选项的 name 值必须为 remember-me。

修改remember-me功能提交的的name值

http.rememberMe()
        .rememberMeParameter("remember");
1
2

自定义用户登录页面

引入登录页面

<!-- login.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/user/login" method="post">
        用户名:<input type="text" name="username"/><br/>
        密码:<input type="password" name="password"/><br/>
        <input type="submit" value="登录"/>
    </form>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

注意:表单的提交方式必须是 POST,用户名和密码必须是 username 和 password 。

原理:

在执行登录时会经过 UsernamePasswordAuthenticationFilter 过滤器,下面是部分属性。

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

   public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";

   public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";

   // 这个属性设置了登录页默认的访问路径\\login和访问方式POST
   private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login","POST");

   // 使用这个参数获取用户名
   private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;

   // 使用这个参数获取密码
   private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;

   private boolean postOnly = true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

自定义用户名和密码的name

SecurityConfig 配置类新增方法

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
        .usernameParameter("pwd") // 设置获取登录名时的依据
        .passwordParameter("user"); // 设置获取密码时的依据
}
1
2
3
4
5
6

设置未授权的请求跳转到登录页

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;

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

    // 注意: 在设置了登录表单的url和可以直接访问的url后, 如果不进一步调用permitAll()方法, 则只是将这些url设置进去了,
    //       但是还是需要权限才能访问, 而想要访问就需要登录, 但是访问登录页面页面需要权限, 就会造成尴尬的情况。
    //       因此在设置了某些登录表单的url必须进一步调用permitAll()方法。
    // permitAll(): 使用permitAll()将配置授权,以便在该特定路径上允许所有请求
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 登录表单的设置
        http.formLogin()
            .loginPage("/login.html") // 配置登录页的url
            .loginProcessingUrl("/user/login") // 设置提交登录的url, 我们不需要编写这个url对应的控制器
            .defaultSuccessUrl("/index") // 登录成功的跳转路径, redirect方式; successForwardUrl()方法为forword方式
            .failureUrl("/hello") // 登录失败的跳转路径, redirect方式; failureForwardUrl()方法为forword方式
			.permitAll(); // 将这些路径授予访问权限
            
        // 设置可以直接访问的请求
        http.authorizeRequests()
            .antMatchers("/", "/hello", "/user/login") // 表示请求路径
            .permitAll() // 将这些路径授予访问权限
            
        // 设置其余请求都需要进行登录
        http.authorizeRequests()
            .anyRequest() // 其他请求
            .authenticated(); // 需要认证

        // 关闭csrf防护, 可以先理解为防火墙
        http.csrf().disable();
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

Controller

@RestController
public class TestController {
    @GetMapping("/hello")
    public String hello(){
        return "Hello Security!";
    }

    @GetMapping("/index")
    public String index(){
        return "hello index!";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

运行项目,进行测试,测试成功!!!

上次更新: 2024/03/29, 08:56:31
SpringSecurity Web 权限方案
新建 Markdown

← SpringSecurity Web 权限方案 新建 Markdown→

最近更新
01
并行训练
03-29
02
tensor维度转换
03-26
03
ResNet源码解读
03-23
更多文章>
Theme by Vdoing | Copyright © 2022-2024 Geeks_Z | MIT License
京公网安备 11010802040735号 | 京ICP备2022029989号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式