目录
项目总结
新建一个项目
pom.xml
application.properties配置文件
User实体类
UserMapper映射接口
UserService访问数据库中的用户信息
WebSecurityConfig配置类
MyAuthenticationFailureHandler登录失败后
MyAuthenticationSuccessHandlerw登录成功后
WebSecurityController控制器
home.html主页
login.html登录页面
resource.html资源页面
SpringSecurityApplication启动类
SpringSecurityApplicationTests测试类
项目测试
参考文章:
- 1、http://t.csdnimg.cn/BRfSZ
- 国内目前常用的两个数据库操作框架:Spring Data JPA和MyBatis,只需掌握一个即可
- Spring Security整合MyBatis的特点:
1. 灵活性
- 自定义认证与授权逻辑:通过实现
UserDetailsService
接口,你可以完全控制用户认证和授权的逻辑,可以从数据库中加载用户信息并进行权限分配。- 自定义加密方式:Spring Security 提供了各种密码加密器,如
BCryptPasswordEncoder
,你可以选择或自定义适合项目的加密方式。2. 高效的数据访问
- MyBatis 的持久化层:利用 MyBatis 强大的 SQL 映射功能,可以高效地执行复杂的数据库查询和更新操作。MyBatis 支持动态 SQL,方便实现复杂的查询需求。
- 缓存机制:MyBatis 提供一级缓存和二级缓存机制,可以显著提高数据访问效率。
3. 安全性
- 强大的安全功能:Spring Security 提供了全面的安全解决方案,包括认证、授权、攻击防护(如 CSRF、Session Fixation)、安全策略配置等。
- 细粒度的权限控制:通过 Spring Security,可以对 URL 路径、方法调用、领域对象等进行细粒度的权限控制,确保应用的各个部分都得到充分保护。
4. 良好的扩展性
- 插件支持:MyBatis 支持多种插件,可以轻松扩展功能,比如分页插件、日志插件等。
- Spring Security 的过滤器链:Spring Security 的过滤器链机制允许你自定义和扩展安全过滤器,满足特定需求。
5. 简化开发
- 注解支持:使用注解可以大大简化配置,如
@Mapper
注解用于 MyBatis 的 Mapper,@EnableWebSecurity
用于启用 Spring Security。- 配置简洁:结合 Spring Boot,可以通过少量的配置文件(如
application.properties
)快速集成 Spring Security 和 MyBatis。6. 性能优化
- 懒加载:MyBatis 支持懒加载,可以在需要时才加载关联数据,减少不必要的数据传输。
- 批量操作:MyBatis 支持批量插入、更新和删除操作,提升数据库操作性能。
项目总结
- 项目特点:
- 多用户认证与授权:用户信息存储在数据库里,有用户登录时,调取数据库里的用户信息进行验证
- 密码加密
- 保护资源:登录成功后才能访问指定资源
- 运行流程:
1、用户请求登录页面:
- 用户访问
/login
URL,Spring Security 自动处理这个请求并返回登录页面。2、用户提交登录表单:
- 用户在登录页面输入用户名和密码,然后提交表单。
- 表单提交到
/login
(默认 URL),由 Spring Security 处理。3、Spring Security 处理认证请求:
- Spring Security 捕获表单提交请求,提取用户名和密码。
- 使用配置的
UserDetailsService
加载用户信息。4、UserService 加载用户信息:
- UserService 调用
UserMapper
从数据库中查询用户信息。- 如果找到用户,将数据封装到
User
对象中返回给 Spring Security。5、密码验证:
- Spring Security 使用配置的
PasswordEncoder
对用户提交的密码进行加密,并与数据库中的加密密码进行比对。- 如果密码匹配,认证成功;否则,认证失败。
6、授权过程:
- 认证成功后,Spring Security 根据配置的权限规则(如 URL 访问控制)决定用户可以访问哪些资源。
- 如果用户尝试访问受保护的资源,Spring Security 会检查用户是否具有相应的权限。
7、处理用户请求:
- 如果用户有权限访问请求的资源,Spring Security 将请求转发给相应的控制器。
- 控制器处理请求,执行相应的业务逻辑,并返回视图或数据。
8、返回响应:
- 控制器返回的结果通过视图解析器或直接以 JSON 格式返回给用户。
- 最终用户在浏览器中看到响应结果。
新建一个项目
项目结构:
创建数据库、表
CREATE DATABASE security;
USE security;
CREATE TABLE `t_users`(
`id` INT AUTO_INCREMENT NOT NULL,
`username` VARCHAR(50) NOT NULL,
`password` VARCHAR(512) NOT NULL,
`enabled` TINYINT(1) NOT NULL DEFAULT '1',
`locked` TINYINT(1) NOT NULL DEFAULT '0',
`mobile` VARCHAR(11) NOT NULL,
`roles` VARCHAR(500),
PRIMARY KEY(id)
);
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.study</groupId>
<artifactId>spring_security</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring_security</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<!--添加MySQL数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--为了使用@Data注解-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--以便 Thymeleaf 能够处理 Spring Security 标签-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</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-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties配置文件
# 配置视图解析器,表示到resources/templates目录寻找后缀为.html,并且名字和视图名称一致的文件,springboot自动帮你配好了
#spring.mvc.view.prefix=classpath:/templates/
#spring.mvc.view.suffix=.html
# 数据库的连接配置
spring.datasource.url=jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=true
spring.datasource.username=root
spring.datasource.password=admin
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
User实体类
package com.study.spring_security.entity;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
/**
* UserDetails是一个用于提供核心用户信息的接口
*/
@Data
public class User implements UserDetails {
private Long id;
private String username;
private String password;
private boolean enabled;
private boolean locked;
private String mobile;
private String roles;
/**
* 返回授予用户的权限,该方法不能返回null
* AuthorityUtils是一个工具类,该类的静态方法commaSeparatedStringToAuthorityList()
* 可以将以逗号分隔的字符串表示形式的权限转换为GrantedAuthority对象数组
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return AuthorityUtils.commaSeparatedStringToAuthorityList(roles);
}
/**
* 指示用户账户是否已过期,过期的账户无法进行身份验证
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 指示用户是否被锁定或解锁,无法对被锁定的用户进行身份验证
*/
@Override
public boolean isAccountNonLocked() {
return !locked;
}
/**
* 指示用户的凭据(密码)是否已过期,过期的凭据阻止身份验证
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 指示用户是被启用还是被禁用,无法对被禁用的用户进行身份验证
*/
@Override
public boolean isEnabled() {
return enabled;
}
}
UserMapper映射接口
package com.study.spring_security.mapper;
import com.study.spring_security.entity.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
@Mapper//标注该接口为MyBatis映射器
public interface UserMapper {
@Select("select * from t_users where username=#{username}")
User getByUsername(String username);
@Insert("insert into t_users(username,password,mobile,roles)"+
" values (#{username},#{password},#{mobile},#{roles})")
//插入数据后,获取自增长的主键值
@Options(useGeneratedKeys = true,keyProperty = "id")
int saveUser(User user);
}
UserService访问数据库中的用户信息
package com.study.spring_security.service;
import com.study.spring_security.entity.User;
import com.study.spring_security.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* 在loadUserByUsername()方法中访问自定义数据库的用户表和角色表,然后返回一个UserDetails对象即可
*/
@Service
public class UserService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.getByUsername(username);
//该方法不允许返回空,如果没有找到用户,或者用户没有授予的权限,则抛出异常
if(user==null){
throw new UsernameNotFoundException("用户不存在!");
}
return user;
}
}
WebSecurityConfig配置类
package com.study.spring_security.config;
import com.study.spring_security.config.handler.MyAuthenticationFailureHandler;
import com.study.spring_security.config.handler.MyAuthenticationSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@EnableWebSecurity//声明这是一个Spring Security安全配置类,无需再另加注解@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyAuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
private MyAuthenticationFailureHandler authenticationFailureHandler;
//配置当前项目的登录和拦截信息
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//允许所有用户访问/home,/login
.antMatchers("/home","/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")//指定登录页面
.successHandler(authenticationSuccessHandler)//验证成功的处理器
.failureHandler(authenticationFailureHandler)//验证失败的处理器
//身份验证成功后默认重定向的页面
.defaultSuccessUrl("/home",true).permitAll()
.and()
.logout().permitAll();
}
/**
* 密码编码器
* 当用户登录时,应用程序会获取用户输入的明文密码,并使用相同的 PasswordEncoder 对其进行编码,然后与存储在数据库中的哈希值进行比较。
* 由于密码是用不可逆的哈希算法进行编码的,因此即使有人获取了哈希值,也无法轻易地反推出原始密码。
* 这种设计提高了密码存储的安全性。
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
MyAuthenticationFailureHandler登录失败后
package com.study.spring_security.config.handler;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 身份验证失败后的处理
*/
@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter out = response.getWriter();
out.write("登录失败!");
out.close();
}
}
MyAuthenticationSuccessHandlerw登录成功后
package com.study.spring_security.config.handler;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 身份验证成功后的处理
*/
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
PrintWriter out = response.getWriter();
out.write("登录成功!");
out.close();
}
}
WebSecurityController控制器
package com.study.spring_security.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 1.将configure(HttpSecurity http)方法中设置的不同的URL映射到不同的页面
* 2.方法返回的是视图名称,需要视图解析器将视图名称解析成实际的HTML文件
* 然后访问url就可以跳转到HTML页面了,否则返回的只是一个字符串
* 3.在application.properties配置文件中配置视图解析器,springboot已经默认配置好了,你不用写了
*/
@Controller
public class WebSecurityController {
/**
* 登录后跳转到home.html页面
*/
@GetMapping("/home")
public String home(){
return "home";
}
/**
* 登录页面
*/
@GetMapping("/login")
public String login(){
return "login";//login.html
}
/**
* 当访问/resource时,会重定向到/login,登录后才可以访问受保护的页面resource.html
*/
@GetMapping("/resource")
public String resource(){
return "resource";//resource.html
}
}
home.html主页
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h2>这是首页!</h2>
</body>
</html>
login.html登录页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<div>
<!--当Spring Security验证失败时,会在URL上附加error查询参数,形式为:http://localhost:8080/login?error-->
<div class="error" th:if="${param.error}">用户名或密码错误</div>
<form th:action="@{/login}" method="post">
<div>
<input
name="username"
placeholder="请输入用户名"
type="text"
/>
</div>
<div>
<input
name="password"
placeholder="请输入密码"
type="password"
/>
</div>
<div class="submit">
<input type="submit" value="登录"/>
</div>
</form>
</div>
</body>
</html>
resource.html资源页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8">
<title>资源页面</title>
</head>
<body>
<h2>欢迎用户<span sec:authentication="name"></span></h2>
<form th:action="@{/logout}" method="post">
<input type="submit" value="退出"/>
</form>
</body>
</html>
SpringSecurityApplication启动类
package com.study.spring_security;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
public class SpringSecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSecurityApplication.class, args);
}
}
SpringSecurityApplicationTests测试类
package com.study.spring_security.mapper;
import com.study.spring_security.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* 单元测试类的包结构与UserMapper接口一致,
* 前者为test/java/com/study/spring_security/mapper
* 后者为main/java/com/study/spring_security/mapper
*/
@SpringBootTest
class SpringSecurityApplicationTests {
@Autowired
private UserMapper userMapper;
/**
* 创建两个用户zhang和admin
*/
@Test
void saveUser(){
User user = new User();
user.setUsername("zhang");
//使用BCryptPasswordEncoder对提交的密码加密
user.setPassword(new BCryptPasswordEncoder().encode("1234"));
user.setMobile("18323415151");
user.setRoles("ROLE_USER");
userMapper.saveUser(user);
user=new User();
user.setUsername("admin");
user.setPassword(new BCryptPasswordEncoder().encode("1234"));
user.setMobile("16813512343");
user.setRoles("ROLE_USER,ROLE_ADMIN");
userMapper.saveUser(user);
}
}
项目测试
- 先登录mysql,
- 创建数据库、表
- 启动测试类,往数据库添加两条用户信息
- 启动启动类,访问网址:http://localhost:8080/login
- 使用用户信息登录
- 若登录失败,则返回
- 若登录成功,跳转到主页
- 访问资源页面:http://localhost:8080/resource,打印出了用户名,点击退出按钮,退出登录,资源页面受保护,必须登录成功后才能访问