Spring Security-全面详解(学习总结---从入门到深化)(上)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: Spring Security是Spring项目组提供的安全服务框架,核心功能包 括认证和授权。它为系统提供了声明式安全访问控制功能,减少了 为系统安全而编写大量重复代码的工作。

Spring Security介绍

2345_image_file_copy_45.jpg

Spring Security是Spring项目组提供的安全服务框架,核心功能包 括认证和授权。它为系统提供了声明式安全访问控制功能,减少了 为系统安全而编写大量重复代码的工作。

认证

认证即系统判断用户的身份是否合法,合法可继续访问,不合法则 拒绝访问。常见的用户身份认证方式有:用户名密码登录、二维码 登录、手机短信登录、脸部识别认证、指纹认证等方式。 认证是为了保护系统的隐私数据与资源,用户的身份合法才能访问 该系统的资源。

授权

授权即认证通过后,根据用户的权限来控制用户访问资源的过程, 拥有资源的访问权限则正常访问,没有权限则拒绝访问。 比如在一 些视频网站中,普通用户登录后只有观看免费视频的权限,而VIP用 户登录后,网站会给该用户提供观看VIP视频的权限。 认证是为了保证用户身份的合法性,授权则是为了更细粒度的对隐 私数据进行划分,控制不同的用户能够访问不同的资源。

举个例子:认证是公司大门识别你作为员工能进入公司,而授权则 是由于你作为公司会计可以进入财务室,查看账目,处理财务数据。 2345_image_file_copy_46.jpg

Spring Security认证_项目搭建

接下来我们先来搭建一个Spring Security项目

1、准备一个名为 mysecurity 的Mysql数据库

2、创建SpringBoot项目,添加依赖

<!-- SpringMVC -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Thymeleaf-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--Spring Security-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Mysql驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<!-- MyBatisPlus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.0</version>
</dependency>
<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<!-- junit -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

3、为SpringBoot项目编写配置文件

server:
  port: 80
#日志格式
logging:
  pattern:
    console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
# 数据源
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql:///mysecurity?serverTimezone=UTC
    username: root
    password01: root

4、在 template 文件夹编写项目主页面 main.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>主页面</title>
</head>
<body>
    <h1>主页面</h1>
</body>
</html>

5、编写访问页面控制器

@Controller
public class PageController {
    @RequestMapping("/{page}")
    public String showPage(@PathVariable String page){
        return page;
   }
}

启动项目,访问项目主页面http://localhost/main,项目会自动 跳转到一个登录页面。这代表Spring Security已经开启了认证 功能,不登录无法访问所有资源,该页面就是Spring Security 自带的登录页面。 我们使用 user 作为用户名,控制台中的字符串作为密码登录,登 录成功后跳转到项目主页面。 在后续的课程中,我们会讲解在真实开发中,如何对登录页 面、登录逻辑等进行自定义配置。

Spring Security认证_内存认证

2345_image_file_copy_47.jpg

在实际开发中,用户数量不会只有一个,且密码是自己设置的。所 以我们需要自定义配置用户信息。首先我们在内存中创建两个用 户,Spring Security会将登录页传来的用户名密码和内存中用户名 密码做匹配认证。

// Security配置类
@Configuration
public class SecurityConfig {
    // 定义认证逻辑
    @Bean
    public UserDetailsService userDetailsService(){
        // 1.使用内存数据进行认证
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        // 2.创建两个用户
        UserDetails user1 = User.withUsername("baizhan").password("123").authorities("admin").build();
        UserDetails user2 = User.withUsername("sxt").password("456").authorities("admin").build();
        // 3.将这两个用户添加到内存中
        manager.createUser(user1);
        manager.createUser(user2);
        return manager;
   }
    //密码编码器,不解析密码
    @Bean
    public PasswordEncoder passwordEncoder()
   {
        return NoOpPasswordEncoder.getInstance();
   }
}

此时进行认证测试,我们可以将登录页传来的用户名密码和内存中 用户名密码做匹配认证。

Spring Security认证_UserDetailsService

2345_image_file_copy_48.jpg

在实际项目中,认证逻辑是需要自定义控制的。将 UserDetailsService 接口 的实现类放入Spring容器即可自定义认证逻辑。 InMemoryUserDetailsManager 就是 UserDetailsService 接口的一个实现类,它将登 录页传来的用户名密码和内存中用户名密码做匹配认证。当然我们 也可以自定义 UserDetailsService 接口的实现类。

public interface UserDetailsService {
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

UserDetailsService 的实现类必须重写 loadUserByUsername 方法,该方法定义了 具体的认证逻辑,参数 username 是前端传来的用户名,我们需要根据 传来的用户名查询到该用户(一般是从数据库查询),并将查询到 的用户封装成一个UserDetails对象,该对象是Spring Security提供 的用户对象,包含用户名、密码、权限。Spring Security会根据 UserDetails对象中的密码和客户端提供密码进行比较。相同则认证 通过,不相同则认证失败。

2345_image_file_copy_50.jpg

Spring Security认证_数据库认证

2345_image_file_copy_51.jpg

接下来我们连接数据库进行认证:

1、准备数据库数据

CREATE TABLE `users`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255),
  `password` varchar(255) ,
  `phone` varchar(255) ,
  PRIMARY KEY (`id`)
);
INSERT INTO `users` VALUES (1, 'bazhn','bazhn', '13812345678');
INSERT INTO `users` VALUES (2, 'xt','xt', '13812345678');

2、编写用户实体类

@Data
public class Users {
    private Integer id;
    private String username;
    private String password;
    private String phone;
}

3、编写dao接口

public interface UsersMapper extends BaseMapper<Users> {}

4、在SpringBoot启动类中添加 @MapperScan 注解,扫描Mapper文件夹

@SpringBootApplication
@MapperScan("com.itbaizhan.myspringsecurity.mapper")
public class MysecurityApplication {
    public static void main(String[] args)
     {
        SpringApplication.run(MysecurityApplication.class, args);
   }
}

5、创建 UserDetailsService 的实现类,编写自定义认证逻辑

@Service
public class MyUserDetailsService
implements UserDetailsService {
    @Autowired
    private UsersMapper usersMapper;
    // 自定义认证逻辑
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1.构造查询条件
        QueryWrapper<Users> wrapper = new QueryWrapper<Users>().eq("username",username);
        // 2.查询用户
        Users users = usersMapper.selectOne(wrapper);
        // 3.封装为UserDetails对象
        UserDetails userDetails = User
               .withUsername(users.getUsername())
               .password(users.getPassword())
               .authorities("admin")
               .build();
        // 4.返回封装好的UserDetails对象
        return userDetails;
   }
}

6、测试连接数据库认证

Spring Security认证_PasswordEncoder

在实际开发中,为了数据安全性,在数据库中存放密码时不会存放 原密码,而是会存放加密后的密码。而用户传入的参数是明文密 码。此时必须使用密码解析器才能将加密密码与明文密码做比对。 Spring Security中的密码解析器是 PasswordEncoder

Spring Security要求容器中必须有 PasswordEncoder 实例,之前使用的

NoOpPasswordEncoder 是 PasswordEncoder 的实现类,意思是不解析密码,使用 明文密码。

Spring Security官方推荐的密码解析器是 BCryptPasswordEncoder 。接下来 我们学习 BCryptPasswordEncoder 的使用。

@SpringBootTest
public class PasswordEncoderTest {
    @Test
    public void testBCryptPasswordEncoder(){
        //创建解析器
        PasswordEncoder encoder = new BCryptPasswordEncoder();
        //密码加密
        String password = encoder.encode("baizhan");
        System.out.println("加密后:"+password);
        //密码校验
        /**
         * 参数1:明文密码
         * 参数2:加密密码
         * 返回值:是否校验成功
         */
        boolean result = encoder.matches("baizhan","$2a$10$/MImcrpDO21HAP2amayhme8j2SM0YM50/WO8YBH.NC1hEGGSU9ByO");
        System.out.println(result);
   }
}

在开发中,我们将 BCryptPasswordEncoder 的实例放入Spring容器即可,并 且在用户注册完成后,将密码加密再保存到数据库。

//密码编码器
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

2345_image_file_copy_52.jpg

Spring Security认证_自定义登录页面

2345_image_file_copy_53.jpg

虽然Spring Security给我们提供了登录页面,但在实际项目中,更 多的是使用自己的登录页面。Spring Security也支持用户自定义登 录页面。用法如下:

1、编写登录页面

2、在Spring Security配置类自定义登录页面

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
    //Spring Security配置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 自定义表单登录
        http.formLogin()
           .loginPage("/login.html") //自定义登录页面
           .usernameParameter("username")// 表单中的用户名项
           .passwordParameter("password")// 表单中的密码项
           .loginProcessingUrl("/login")
          // 登录路径,表单向该路径提交,提交后自动执行UserDetailsService的方法
           .successForwardUrl("/main")//登录成功后跳转的路径
           .failureForwardUrl("/fail");//登录失败后跳转的路径
          // 需要认证的资源
          http.authorizeRequests().antMatchers("/login.html").permitAll() 
          //登录页不需要认证
           .anyRequest().authenticated();
          //其余所有请求都需要认证
        //关闭csrf防护
        http.csrf().disable();
   }
    @Override
    public void configure(WebSecurity web) throws Exception {
        // 静态资源放行
      web.ignoring().antMatchers("/css/**");
   }
}

CSRF防护: CSRF:跨站请求伪造,通过伪造用户请求访问受信任的站点 从而进行非法请求访问,是一种攻击手段。 Spring Security 为了防止CSRF攻击,默认开启了CSRF防护,这限制了除了 GET请求以外的大多数方法。我们要想正常使用Spring Security需要突破CSRF防护。

解决方法一:关闭CSRF防护:

http.csrf().disable();

解决方法二:突破CSRF防护:

CSRF为了保证不是其他第三方网站访问,要求访问时携带参 数名为_csrf值为令牌,令牌在服务端产生,如果携带的令牌 和服务端的令牌匹配成功,则正常访问。

<form class="form" action="/login" method="post">
<!-- 在表单中添加令牌隐藏域 -->
   <input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="${_csrf}"/>
   <input type="text" placeholder="用户名" name="username">
   <input type="password" placeholder="密码" name="password">
   <button type="submit">登录</button>
</form>

Spring Security认证_会话管理

2345_image_file_copy_54.jpg

用户认证通过后,有时我们需要获取用户信息,比如在网站顶部显 示:欢迎您,XXX。Spring Security将用户信息保存在会话中,并 提供会话管理,我们可以从 SecurityContext 对象中获用户信息, SecurityContext 对象与当前线程进行绑定。

获取用户信息的写法如下:

@RestController
public class MyController {
    // 获取当前登录用户名
    @RequestMapping("/users/username")
    public String getUsername(){
        // 1.获取会话对象
        SecurityContext context = SecurityContextHolder.getContext();
        // 2.获取认证对象
        Authentication authentication = context.getAuthentication();
        // 3.获取登录用户信息
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        return userDetails.getUsername();
   }
}

Spring Security认证_认证成功后的处理方式

登录成功后,如果除了跳转页面还需要执行一些自定义代码时, 如:统计访问量,推送消息等操作时,可以自定义登录成功处理器。

1、自定义登录成功处理器

public class MyLoginSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest
request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        // 拿到登录用户的信息
        UserDetails userDetails = (UserDetails)authentication.getPrincipal();
        System.out.println("用户名:"+userDetails.getUsername());
        System.out.println("一些操作...");
        // 重定向到主页
        response.sendRedirect("/main");
   }
}

2、配置登录成功处理器

http.formLogin() // 使用表单登录
   .loginPage("/login.html") // 自定义登录页面
   .usernameParameter("username") // 表单中的用户名项
   .passwordParameter("password") // 表单中的密码项
   .loginProcessingUrl("/login") // 登录路径,表单向该路径提交,提交后自动执行 UserDetailsService的方法
    //.successForwardUrl("/main") //登录成功后跳转的路径
   .successHandler(new MyLoginSuccessHandler()) //登录成功处理器
   .failureForwardUrl("/fail"); //登录失败后跳转的路径

Spring Security认证_认证失败后的处理方式

登录失败后,如果除了跳转页面还需要执行一些自定义代码时, 如:统计失败次数,记录日志等,可以自定义登录失败处理器。

1、自定义登录失败处理器

public class MyLoginFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
        System.out.println("记录失败日志...");
        response.sendRedirect("/fail");
   }
}

2、配置登录失败处理器

http.formLogin() // 使用表单登录
   .loginPage("/login.html") // 自定义登录页面
   .usernameParameter("username") // 表单中的用户名项
   .passwordParameter("password") // 表单中的密码项
   .loginProcessingUrl("/login") // 登录路径,表单向该路径提交,提交后自动执行UserDetailsService的方法
    //.successForwardUrl("/main") //登录成功后跳转的路径
   .successHandler(new MyLoginSuccessHandler()) //登录成功处理器
    //.failureForwardUrl("/fail") //登录失败后跳转的路径
   .failureHandler(new MyLoginFailureHandler()); //登录失败处理器
    // 需要认证的资源
http.authorizeRequests()
   .antMatchers("/login.html").permitAll() //登录页不需要认证
   .antMatchers("/fail").permitAll() //失败页不需要认证
   .anyRequest().authenticated(); //其余所有请求都需要认证

Spring Security认证_退出登录

在系统中一般都有退出登录的操作。退出登录后,Spring Security 进行了以下操作:

1、清除认证状态

2、销毁HttpSession对象

3、跳转到登录页面

在Spring Security中,退出登录的写法如下:

1、配置退出登录的路径和退出后跳转的路径

// 退出登录配置
http.logout()
   .logoutUrl("/logout") // 退出登录路径
   .logoutSuccessUrl("/login.html") // 退出登录后跳转的路径
   .clearAuthentication(true) //清除认证状态,默认为true
   .invalidateHttpSession(true); // 销毁HttpSession对象,默认为true

2、在网页中添加退出登录超链接

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>主页面</title>
</head>
<body>
  <h1>主页面</h1>
  <a href="/logout">退出登录</a>
</body>
</html>
目录
相关文章
|
22天前
|
JSON 安全 Java
什么是JWT?如何使用Spring Boot Security实现它?
什么是JWT?如何使用Spring Boot Security实现它?
80 5
|
22天前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
40 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
19天前
|
Java 数据库连接 数据库
从入门到精通---深入剖析Spring DAO
在Java企业级开发中,Spring框架以其强大的功能和灵活性,成为众多开发者的首选。Spring DAO(Data Access Object)作为Spring框架中处理数据访问的重要模块,对JDBC进行了抽象封装,极大地简化了数据访问异常的处理,并能统一管理JDBC事务。本文将从概述、功能点、背景、业务点、底层原理等多个方面深入剖析Spring DAO,并通过多个Java示例展示其应用实践,同时指出对应实践的优缺点。
21 1
|
23天前
|
监控 Java 数据安全/隐私保护
如何用Spring Boot实现拦截器:从入门到实践
如何用Spring Boot实现拦截器:从入门到实践
38 5
|
1月前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
64 9
|
2月前
|
前端开发 Java 数据库
SpringBoot学习
【10月更文挑战第7天】Spring学习
42 9
|
1月前
|
Java Kotlin 索引
学习Spring框架特性及jiar包下载
Spring 5作为最新版本,更新了JDK基线至8,修订了核心框架,增强了反射和接口功能,支持响应式编程及Kotlin语言,引入了函数式Web框架,并提升了测试功能。Spring框架可在其官网下载,包括文档、jar包和XML Schema文档,适用于Java SE和Java EE项目。
33 0
|
2月前
|
XML Java 数据格式
Spring学习
【10月更文挑战第6天】Spring学习
27 1
|
XML Java 数据格式
Spring_总结_02_依赖注入
一、前言 本文承接上一节:Spring_总结_01_Spring概述 在上一节中,我们了解了Spring的最根本使命、四大原则、六大模块以及Spring的生态。 这一节我们开始了解Spring的第二大原则中的依赖注入,这属于六大模块中的核心容器部分。
1709 0
|
2月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
232 2