SpringSecurity是一个安全框架,可以帮助我们减少重复代码的同时实现对资源的可控访问。
认证及判断用户是否登录,有些资源是用户必须要登录才能使用的,如点赞收藏等。
用户登录后,不同的资源可能被不同程度的管控,比如说VIP视频只有VIP用户才可以观看。这是对用户更细粒度的划分。
总结:认证就好比你想要进入公司,你必须是员工才能进入。进入公司后,你只可以前往你所在的部门,而其它部门你无权访问,这便是授权。
环境搭建:
1、创建数据库mysecurity
2、创建SpringBoot项目
2.1添加依赖
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>3.1.5version>
<relativePath/>
parent>
<groupId>com.examplegroupId>
<artifactId>securitydemoartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>securitydemoname>
<description>securitydemodescription>
<url/>
<licenses>
<license/>
licenses>
<developers>
<developer/>
developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
scm>
<properties>
<java.version>17java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-springsecurity6artifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.49version>
<scope>runtimescope>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.3version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
2.2编写配置文件
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.jdbc.Driver
url: jdbc:mysql:///mysecurity?serverTimezone=UTC
username: root
password: 123456
2.3编写主页面main.html
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>主页面title>
head>
<body>
<h1>主页面h1>
body>
html>
2.4编写控制器方法
@Controller
public class PageController {
@RequestMapping("/{page}")
public String showPage(@PathVariable String page){
return page;
}
}
2.5访问localhost/main 后跳转至登陆页面,证明搭建成功
UserDetailsService用于处理自定义认证逻辑,它可以帮助我们检查用户密码是否正确,以及封装用户信息等操作。
执行流程:用户提交表单,执行重写的loadUserByUsername方法,根据传入的用户名查找数据库(需程序员手动编写),如果用户为空,抛出异常,非空则封装权限,将用户名密码及权限封装为UserDetails对象,框架会将UserDetails中的密码项与表单中的密码自动进行比对。
@Service
// 实现UserDetailsService接口,重写loadUserByUsername方法
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框架用来实现对密码的加密与解密操作的密码解析器,可以保证用户数据在网络传输过程中的安全性。
@SpringBootTest
public class PasswordEncoderTest {
@Test
public void testBCryptPasswordEncoder(){
//创建解析器
PasswordEncoder encoder = new BCryptPasswordEncoder();
//密码加密
String password = encoder.encode("test");
System.out.println("加密后:"+password);
//密码校验
/**
* 参数1:明文密码
* 参数2:加密密码
* 返回值:是否校验成功
*/
boolean result = encoder.matches("test","$2a$10$/MImcrpDO21HAP2amayhme8j2SM0YM50/WO8YBH.NC1hEGGSU9ByO");
System.out.println(result);
}
}
我们使用SpringSecurity后,如果我们未登录访问某些资源,会自动跳转到SpringSecurity默认的登陆页面,但是往往我们需要使用自己的登陆页面。
我们可以在配置类中自定义登陆页面
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// 自定义表单登录
http.formLogin(form -> {
form.loginPage("/login.html") // 自定义登录页面
.usernameParameter("username") // 表单中的用户名项
.passwordParameter("password") // 表单中的密码项
.loginProcessingUrl("/login") //前端调用接口login后,会自动执行UserDetailsService的自定义认证逻辑方法
.successForwardUrl("/index.html") //登录成功后跳转的路径
.failureForwardUrl("/error.html"); //登录失败后跳转的路径
});
// 需要认证的资源
http.authorizeHttpRequests(resp -> {
resp.requestMatchers("/login.html","/error.html").permitAll(); // 不需要认证的资源
resp.requestMatchers("/static/**").permitAll(); // 静态资源不需要认证
resp.anyRequest().authenticated();//其余所有请求都需要认证
});
// 关闭csrf防护
http.csrf(csrf ->{
csrf.disable();
});
return http.build();
}
}
当登录成功/失败或者退出登录时,我们玩玩需要做一些事情,比如说登录成功后需要给前端返回一个token令牌,退出登录时需要清除session会话等。
例:这里以登录成功为例
4.1 自定义登录成功处理器
public class MyLoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
// 拿到当前用户信息
UserDetails userDetails = (UserDetails)authentication.getPrincipal();
//执行操作。。。
}
}
4.2 修改配置类
// 自定义表单登录
http.formLogin(form -> {
form.loginPage("/login.html")
.usernameParameter("username")
.passwordParameter("password")
.loginProcessingUrl("/login")
.successHandler(new MyLoginSuccessHandler()) // 登录成功处理器,登录成功会执行该方法
.failureForwardUrl("/error.html");
});
http.logout(logout -> {
logout.logoutUrl("/logout") // 退出登录路径
.logoutSuccessUrl("/login.html") // 退出登录后跳转的路径
.clearAuthentication(true) //清除认证状态,默认为true
.invalidateHttpSession(true); // 销毁HttpSession对象,默认为true
});
修改认证逻辑
// 自定义认证逻辑
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1.构造查询条件
QueryWrapper<Users> wrapper = new QueryWrapper<Users>().eq("username", username);
// 2.查询用户
Users users = userMapper.selectOne(wrapper);
if (users == null){
return null;
}
// 3.查询用户权限
List<Permission> permissions = userMapper.findPermissions(username);
// 4.将权限集合转为Security的权限类型集合
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
for (Permission permission : permissions) {
grantedAuthorities.add(new SimpleGrantedAuthority(permission.getUrl()));
}
// 5.封装为UserDetails对象
UserDetails userDetails = User.withUsername(users.getUsername())
.password(users.getPassword())
.authorities(grantedAuthorities)
.build();
// 6.返回封装好的UserDetails对象
return userDetails;
}
使用在资源上添加访问权限
@PreAuthorize("hasAnyAuthority('user')")
@GetMapping("/user")
public String test() {
return "";
}