Spring Security之认证

发布时间:2024年01月06日

系列文章目录

提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
Spring Security之认证


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

提示:这里可以添加本文要记录的大概内容:

当涉及到构建安全的 Web 应用程序时,Spring Security 是一个广受欢迎且功能强大的框架。它提供了一系列的特性和功能,用于保护应用程序免受未经授权的访问。
在这篇博客中,我们将深入探讨 Spring Security 的认证部分。认证是安全体系的第一道防线,它确保只有经过身份验证的用户才能访问受保护的资源。
我们将从 Spring Security 的基础知识开始,了解它的核心概念和组件。然后,我们将逐步介绍如何使用 Spring Security 实现常见的认证方式,如用户名和密码登录。
无论你是 Spring Security 的新手还是有一定经验的开发者,这篇博客都将为你提供一个深入了解 Spring Security 认证的机会。通过掌握这些知识,你将能够构建更安全、可靠的 Web 应用程序。
让我们一起开启 Spring Security 认证之旅,保护我们的应用程序免受未经授权的访问!


提示:以下是本篇文章正文内容,下面案例可供参考

一、什么是Spring Security

Spring Security 是一个用于保护 Spring 应用程序安全性的框架。它提供了一套全面的安全特性,可以帮助开发人员轻松地构建安全的应用程序。Spring Security 的核心目标是提供一种易于使用、灵活且强大的方式来保护应用程序的安全性。
Spring Security 的主要功能包括身份验证、授权、密码加密、跨域安全性等。它支持多种认证方式,如基本认证、摘要认证、JWT 认证等。同时,Spring Security 还提供了强大的授权功能,允许开发人员根据用户角色和权限来限制对特定资源的访问。
总的来说,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
   password: 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;
   }
}

数据库认证

在实际的项目中,用户通过输入用户名和密码,待验证通过后即登录成功,而用户信息大多保存在数据库中。而用户身份验证的这个过程,我们称之为认证逻辑。
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, 'baizhan', 'baizhan', '13812345678');
INSERT INTO `users` VALUES (2, 'sxt', 'sxt', '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);
   }
}

自定义认证逻辑

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

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

PasswordEncoder

在实际的项目开发中,我们往数据库的密码不会是明文密码,而是经过我们加密后的密码。当用户传入的明文密码后,SpringSecurity使用密码解析器将明文密码加密成密文密码,然后再将数据库中的密文密码进行比对,匹配成功则通过,反之不通过。

创建SecurityConfig配置类,添加SpringSecurity密码解析器(PasswordEncoder)

// Security配置类
@Configuration
public class SecurityConfig {
  
  
    //密码编码器
	@Bean
	public PasswordEncoder passwordEncoder() {
  	return new BCryptPasswordEncoder();
	}
}

自定义登录页面

SpringSecurity给我们提供了登录界面,但是再实际的项目中,登录页面都是用的自己的,这样更能突出项目特色,所以我们要自定义登录界面。
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:跨站请求伪造,通过伪造用户请求访问受信任的站点从而进行非法请求访问,是一种攻击手段。SpringSecurity默认开启CSRF防护,这就限制了除了GET请求以外的大多数请求。我们可以通过关闭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>

会话管理

SpringSecurity提供了会话管理功能,它将用户信息保存再会话中,我们可以通过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();
   }
}

认证成功后的处理

如果在认证成功后,需要处理一些自定义的逻辑,可以使用登陆成功处理器。
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"); //登录失败后跳转的路径

认证失败后的处理

如果在认证成功后,需要处理一些自定义的逻辑,可以使用登陆失败处理器。
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(); //其余所有请求都需要认证

退出登录

当用户退出后,需要清楚认证状态、销毁HttpSession对象,跳转登录界面

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>

退出成功处理器

我们也可以自定义退出成功处理器,在退出后清理一些数据,写法如下:

1.自定义退出成功处理器

public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
  @Override
  public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    System.out.println("清除一些数据...");
    response.sendRedirect("/login.html");
   }
}

2.配置退出成功处理器

// 退出登录配置
http.logout()
   .logoutUrl("/logout") // 退出登录路径
  //         .logoutSuccessUrl("/login.html") // 退出登录后跳转的路径
   .clearAuthentication(true) //清除认证状态,默认为true
   .invalidateHttpSession(true) // 销毁HttpSession对象,默认为 true
   .logoutSuccessHandler(new MyLogoutSuccessHandler()); //自定义退出成功处理器

Remember Me

SpringSecurity提供了“记住我”功能,当使用"记住我"功能登陆后,SpringSecurity会生成一个令牌,令牌一方面保存在数据库里,另一方面生成一个叫remember-me的Cookie保存到客户端。之后客户端访问项目时自动携带令牌,不登录即可完成认证。

1.编写“记住我”配置类

@Configuration
public class RememberMeConfig {
  @Autowired
  private DataSource dataSource;


  // 令牌Repository
  @Bean
  public PersistentTokenRepository getPersistentTokenRepository() {
    // 为Spring Security自带的令牌控制器设置数据源
    JdbcTokenRepositoryImpl jdbcTokenRepositoryImpl = new JdbcTokenRepositoryImpl();
    jdbcTokenRepositoryImpl.setDataSource(dataSource);
    //自动建表,第一次启动时需要,第二次启动时注释掉
//     jdbcTokenRepositoryImpl.setCreateTableOnStartup(true);
    return jdbcTokenRepositoryImpl;
   }
}

2.修改Security配置类

// 记住我配置
http.rememberMe()
     .userDetailsService(userDetailsService)//登录逻辑交给哪个对象
     .tokenRepository(repository) //持久层对象
     .tokenValiditySeconds(30); //保存时间,单位:秒

3.在登录页面添加“记住我”复选框

<form class="form" action="/login" method="post">
  <input type="text" placeholder="用户名" name="username">
  <input type="password" placeholder="密码" name="password">
  <input type="checkbox" name="remember-me" value="true"/>记住我</br>
  <button type="submit">登录</button>
</form>


总结

提示:这里对文章进行总结:

总的来说,Spring Security 为我们提供了一个强大而灵活的框架来保护应用程序的安全性。通过理解和应用所学的知识,我们可以构建更安全、可靠的 Web 应用程序。
希望这篇博客对你有所帮助,让你对 Spring Security 的认证有了更深入的了解。如果你有任何进一步的问题或想要深入探讨其他安全相关主题,请随时留言!

文章来源:https://blog.csdn.net/liubopro666/article/details/135426461
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。