目录
1.11 SpringSecurity集成thymeleaf
1.11.4 PageController(处理要跳转的页面)
一、SpringSecurity01
1.1 认证授权的概念
进入移动互联网时代,大家每天都在刷手机,常用的软件有微信、支付宝、头条,抖音等,下边拿微信来举例子说明认证相关的基本概念,在初次使用微信前需要注册成为微信用户,然后输入账号和密码即可登录微信,输入账号和密码登录微信的过程就是认证。
系统为什么要认证? 认证是为了保护系统的隐私数据与资源,用户的身份合法,方可访问该系统的资源。 认证︰用户认证就是判断一个用户的身份是否合法的过程,用户去访问系统资源时系统要求验证用户的身份信息,身份合法 方可继续访问,不合法则拒绝访问。 常见的用户身份认证方式有: 用户名密码登录,二维码登录,手机短信登录,指纹认证等方式。
1.2 什么是会话
用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保证在会话中。会话就是系统为了保持当前用户的登录状态所提供的机制,常见的有基于session方式、基于token方式等。
1.2.1 基于session的认证
它的交互流程是,用户认证成功后,在服务端生成用户相关的数据保存在session(当前会话)中,发给客户端的sesssion_id存放到 cookie中,这样用户客户端请求时带上 session_id 就可以验证服务器端是否存在 session 数据,以此完成用户的合法校验,当用户退出系统或session过期销毁时,客户端的session_id 也就无效了。
1.2.2 基于ToKen的认证
它的交互流程是,用户认证成功后,服务端生成一个token【也就是uuid】发给客户端,客户端可以放到 cookie 或sessionStorage等存储中,每次请求时带上token,服务端收到token通过验证后即可确认用户身份。
基于session的认证方式由servlet规范定制,服务端要存储session信息需要占用内存资源,客户端需要支持cookie;基于token的方式则一般不需要服务端存储token,并且不限制客户端的存储方式。
如今移动互联网时代更多类型的客户端[pC,android,IOS,]需要接入系统,系统多是采用前后端分离的架构进行实现,所以基于token的方式更适合。
1.3 什么是授权
还拿微信来举例子,微信登录成功后用户即可使用微信的功能,
比如,发红包、发朋友圈、添加好友等,没有绑定银行卡的用户是无法发送红包的,绑定银行卡的用户才可以发红包,发红包功能、发朋友圈功能都是微信的资源即功能资源,用户拥有发红包功能的权限才可以正常使用发送红包功能,拥有发朋友圈功能的权限才可以便用发朋友圈功能,这个根据用户的权限来控制用户使用资源的过程就是授权。
1.3.1 为什么要授权
认证是为了保证用户身份的合法性,授权则是为了更细粒度的对隐私数据进行划分,授权是在认证通过后发生的,控制不同的用户能够访问不同的资源。 授权:授权是用户认证通过根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问。
可以帮你完成认证授权的框架有哪些? 1. shiro框架。----入门简单,功能简单,可以整合web,javase,整合spring框架时比较麻烦。 2. springsecurity框架。---入门复杂,可以和spring或springboot无缝整合,因为他们都是spring全家桶的一部分。
1.3.2 SpringSecurity简介
官网介绍:Spring Security
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Sprirg应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection依赖主入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。 以上解释来源于百度白科。可以一句话来概括,SpringSecurity 是一个安全框架。可以帮我们完成认证,密码加密,授权,,rememberme的功能
1.4 SpringSecurity入门
创建SpringBoot项目 选中服务 web-->spring web Security-->spring Security
1.4.1 pom文件
<?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>
<!--使用低版本的SpringBoot框架-->
<version>2.3.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>testsecurity</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>testsecurity</name>
<description>testsecurity</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<!--springSecurity依赖-->
<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>
1.4.2 主启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
@SpringBootApplication
public class TestsecurityApplication {
public static void main(String[] args) {
SpringApplication.run(TestsecurityApplication.class, args);
}
}
1.4.3 创建控制层
@RestController
public class TestSecurity {
/**
* 简单测试对应的Security的拦截功能
* @return
*/
@GetMapping("index")
public String testSecurity(){
System.out.println("1212121212");
return "hello Security";
}
}
1.4.4 启动项目进行测试
我们发现使用了security后再访问我们自己的接口,security会拦截并跳转到认证页面,认证后才可以访问。默认认证的账号user,密码在控制台。
输入用户名和密码后即可进入对应的页面
密码下面一行显示的是一些列的过滤器链:内容如下
2023-03-08 19:48:32.106 INFO 19272 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: any request, [
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@47f08b81,
org.springframework.security.web.context.SecurityContextPersistenceFilter@5467eea4,
org.springframework.security.web.header.HeaderWriterFilter@726a17c4,
org.springframework.security.web.csrf.CsrfFilter@6075b2d3,
org.springframework.security.web.authentication.logout.LogoutFilter@56da52a7,
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@21325036,
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@368d5c00,
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@b9dfc5a,
org.springframework.security.web.authentication.www.BasicAuthenticationFilter@8a62297,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7a799159,
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@57b9e423,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@2787de58,
org.springframework.security.web.session.SessionManagementFilter@c4c0b41,
org.springframework.security.web.access.ExceptionTranslationFilter@3bcd426c,
org.springframework.security.web.access.intercept.FilterSecurityInterceptor@4bb8855f]
1.5 自定义配置用户
在application.properties文件中进行配置用户名和密码
spring.security.user.name=admin
spring.security.user.password=123
然后再启动项目,这时控制台将不会再打印密码,能进行登录的用户只有在配置文件中配置的这个“admin”用户,当然也只有这一个用户
1.6 配置多用户登录
创建配置类config包下的MySpringSecurity类进行多用户配置,并对用户的密码进行密码加密
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
/**
* @Bean:创建对象并交给spring容器进行管理
* 密码加密器,自定义构建多个用户时,用户的密码必须使用密码加密器进行加密
* PasswordEncoder是一个接口,这里返回的是他的实现类 BCryptPasswordEncoder
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 重写父类的方法进行自定义构建多个用户名和密码,并进行密码加密
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin") //用户名
.password(passwordEncoder().encode("admin")) //调用上面的方法进行密码加密
.authorities("user:delete","user:query","user:insert") //用户具有的权限
.and()
.withUser("lwl")
.password(passwordEncoder().encode("lwl"))
.authorities("user:query","user:export");
}
}
1.6.1 三种基于内存的密码总结
/** * 简单验证Security的拦截功能 * 1、如果没有配置用户和密码,默认的用户名是user,默认的密码会在控制台输出,粘贴到页面即可 * 2、可以在application.properties配置一个用户名和密码(会自动加密) * 配置文件中配置了用户名和密码之后,初始默认的user用户不会再生效 * 3、如果要配置多个用户名和密码,可以在类中进行声明,声明时要对用户进行分配权限和密码加密 * 如果配置文件中和配置类中都定义了用户和密码时,配置文件中的用户将不能再使用 */
1.7 测试密码加密器
/**
* 测试PasswordEncoder加密
*/
public static void main(String[] args) {
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encode1 = passwordEncoder.encode("123456");
System.out.println(encode1);
String encode2 = passwordEncoder.encode("123456");
System.out.println(encode2);
String encode3 = passwordEncoder.encode("123456");
System.out.println(encode3);
/** 结果打印
* $2a$10$qaPo.XRhOQVgbuPf1UzEHOC5lxcQ.xBf2dP57ZfRqLcQAGF1Ym52e
* $2a$10$ybk0lTgVQXEVvOuU8MeUY.8widuR10/NnQeEKwTch95rmbuohS.aS
* $2a$10$zDq28B6dMIMWxT/lL168nOmUFsS58jCBHa1A1AC5Kwu.jP1F7gP/.
*
* 我们发现加密器对同一个内容加密后结果不同。这样做的原因是安全性更高。
*/
//使用123456可以匹配每一个加密后的密码
boolean matches = passwordEncoder.matches("123456", "$2a$10$zDq28B6dMIMWxT/lL168nOmUFsS58jCBHa1A1AC5Kwu.jP1F7gP/.");
System.out.println("matches = " + matches); //matches = true
boolean matches1 = passwordEncoder.matches("123456", "$2a$10$ybk0lTgVQXEVvOuU8MeUY.8widuR10/NnQeEKwTch95rmbuohS.aS");
System.out.println("matches1 = " + matches1); //matches1 = true
}
注意:只要使用同一个密码加密器,解密也是一样的。
1.8 获取当前用户信息
/**
* 第一种:
* 容器会自动注入给参数为 Principal的参数
* @param principal
* @return
*/
@GetMapping("info")
public Principal info(Principal principal){
return principal;
}
/** 第二种
* 登录成功后springSecurity会把当前的用户信息保存到【SecurityContext】中,也就是类似于Session中
* 所有的用户信息都会封装到 Authentication 中
*/
@GetMapping("getInfo")
public Authentication getInfo(){
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
return authentication;
}
两种方法的结果是一样的,对应上面的多用户配置类
1.9 Security的权限控制
拥有的权限(权限表中的user:query)才可以访问对应的接口资源[query]
1. admin--->user:list user:insert user:delete user:update
2. test---->user:list user:export
1.9.1 创建一个UserController类
package com.example.testsecurity.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("user")
public class UserController {
@GetMapping("query")
public String query(){
return "用户查询";
}
@GetMapping("insert")
public String insert(){
return "用户添加";
}
@GetMapping("update")
public String update(){
return "用户修改";
}
@GetMapping("delete")
public String delete(){
return "用户删除";
}
@GetMapping("export")
public String export(){
return "用户导出";
}
}
1.9.2 修改Security配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
/**
* @Bean:创建对象并交给spring容器进行管理
* 密码加密器,自定义构建多个用户时,用户的密码必须使用密码加密器进行加密
* PasswordEncoder是一个接口,这里返回的是他的实现类 BCryptPasswordEncoder
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 重写父类的方法进行自定义构建多个用户名和密码,并进行密码加密
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin") //用户名
.password(passwordEncoder().encode("admin")) //调用上面的方法进行密码加密
.authorities("user:delete","user:query","user:insert","user:update") //用户具有的权限
.and()
.withUser("lwl")
.password(passwordEncoder().encode("lwl"))
.authorities("user:query","user:export");
}
/**
* 权限管理:绑定用户和所拥有的权限
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//登录表单放行,不需要认证即可访问
http.formLogin().permitAll();
/**
* 资源和用户权限进行绑定
*/
http.authorizeRequests()
//想要访问/user/query资源,必须要拥有user:query权限
//也可以设置一个权限,访问多个资源
.antMatchers("/user/query").hasAnyAuthority("user:query")
.antMatchers("/user/update").hasAnyAuthority("user:update")
.antMatchers("/user/delete").hasAnyAuthority("user:delete")
.antMatchers("/user/insert").hasAnyAuthority("user:insert")
.antMatchers("/user/export").hasAnyAuthority("user:export");
/**
* admin的权限authorities("user:delete","user:query","user:insert","user:update")
* 所以admin用户登录时,可以访问[/user/query,/user/update,/user/delete,/user/insert]
* lwl的权限authorities("user:query","user:export")
* lwl用户登录后只可以访问[/user/query,/user/export]
* 如果访问的资源不在自己的权限内,那么就会报错[403:权限不够]
*/
//其他的请求,只需要认证过后都可以访问
http.authorizeRequests().anyRequest().authenticated();
}
}
1.9.3 第二种方法(使用注解)
1、启动类中添加注解
@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启安全注解
public class TestsecurityApplication {
public static void main(String[] args) {
SpringApplication.run(TestsecurityApplication.class, args);
}
}
2、资源中配置注解
package com.example.testsecurity.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("user")
public class UserController {
@GetMapping("query")
@PreAuthorize("hasAuthority('user:query')") //拥有这样的一个权限就可以访问上面的资源
public String query(){
return "用户查询";
}
@GetMapping("insert")
@PreAuthorize("hasAuthority('user:insert')")
public String insert(){
return "用户添加";
}
@GetMapping("update")
@PreAuthorize("hasAuthority('user:update')")
public String update(){
return "用户修改";
}
@GetMapping("delete")
@PreAuthorize("hasAuthority('user:delete')")
public String delete(){
return "用户删除";
}
@GetMapping("export")
@PreAuthorize("hasAuthority('user:export')")
public String export(){
return "用户导出";
}
}
3、权限配置类
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
/**
* @Bean:创建对象并交给spring容器进行管理
* 密码加密器,自定义构建多个用户时,用户的密码必须使用密码加密器进行加密
* PasswordEncoder是一个接口,这里返回的是他的实现类 BCryptPasswordEncoder
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 重写父类的方法进行自定义构建多个用户名和密码,并进行密码加密
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin") //用户名
.password(passwordEncoder().encode("admin")) //调用上面的方法进行密码加密
.authorities("user:delete","user:query","user:insert","user:update") //用户具有的权限
.and()
.withUser("lwl")
.password(passwordEncoder().encode("lwl"))
.authorities("user:query","user:export");
}
}
运行项目进行测试,可以实现第一种同样的效果
1.9.4 权限不足跳转页面(前后端不分离)
1、权限配置类
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
/**
* @Bean:创建对象并交给spring容器进行管理
* 密码加密器,自定义构建多个用户时,用户的密码必须使用密码加密器进行加密
* PasswordEncoder是一个接口,这里返回的是他的实现类 BCryptPasswordEncoder
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 重写父类的方法进行自定义构建多个用户名和密码,并进行密码加密
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin") //用户名
.password(passwordEncoder().encode("admin")) //调用上面的方法进行密码加密
.authorities("user:delete","user:query","user:insert","user:update") //用户具有的权限
.and()
.withUser("lwl")
.password(passwordEncoder().encode("lwl"))
.authorities("user:query","user:export");
}
/**
* 权限管理:绑定用户和所拥有的权限
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//登录表单放行,不需要认证即可访问
http.formLogin().permitAll();
//权限不足时跳转的页面
http.exceptionHandling().accessDeniedPage("/403.html");
//其他的请求,只需要认证过后都可以访问
http.authorizeRequests().anyRequest().authenticated();
}
}
2、前端页面
因为这里不想再配置视图解析器,所以直接把前端页面放置在static中
右键选择新建一个HTML文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
权限不足……请联系管理员进行操作
</body>
</html>
启动项目进行访问,当admin用户访问/user/export资源时,就会因为权限不足跳转到这个前端页面
1.10 基于数据库的权限配置
前面的用户都是基于内存的配置,配置一个基于数据库的用户权限配置
1.10.1 创建项目并创建数据库表
创建项目时选择 Developer Tools --> Lombok Web --> Spring web Security --> spring Security SQL --> MySQL Driver
创建数据库表
/*
Navicat Premium Data Transfer
Source Server : localhost
Source Server Type : MySQL
Source Server Version : 80011
Source Host : localhost:3306
Source Schema : security-study
Target Server Type : MySQL
Target Server Version : 80011
File Encoding : 65001
Date: 14/02/2022 21:33:18
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission` (
`perid` int(11) NOT NULL AUTO_INCREMENT,
`pername` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`percode` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`perid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_permission
-- ----------------------------
INSERT INTO `sys_permission` VALUES (1, '用户查询', 'user:query');
INSERT INTO `sys_permission` VALUES (2, '用户添加', 'user:insert');
INSERT INTO `sys_permission` VALUES (3, '用户修改', 'user:update');
INSERT INTO `sys_permission` VALUES (4, '用户删除', 'user:delete');
INSERT INTO `sys_permission` VALUES (5, '用户导出', 'user:export');
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`roleid` int(11) NOT NULL AUTO_INCREMENT,
`rolename` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`roleid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, '管理员');
INSERT INTO `sys_role` VALUES (2, '测试人员');
INSERT INTO `sys_role` VALUES (3, '普通用户');
-- ----------------------------
-- Table structure for sys_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission` (
`perid` int(11) NULL DEFAULT NULL,
`roleid` int(11) NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_role_permission
-- ----------------------------
INSERT INTO `sys_role_permission` VALUES (2, 1);
INSERT INTO `sys_role_permission` VALUES (1, 1);
INSERT INTO `sys_role_permission` VALUES (3, 1);
INSERT INTO `sys_role_permission` VALUES (4, 1);
INSERT INTO `sys_role_permission` VALUES (2, 2);
INSERT INTO `sys_role_permission` VALUES (1, 2);
INSERT INTO `sys_role_permission` VALUES (3, 2);
INSERT INTO `sys_role_permission` VALUES (1, 3);
INSERT INTO `sys_role_permission` VALUES (5, 3);
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`userid` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`userpwd` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`sex` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`userid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, '张三', NULL, '男', '郑州');
INSERT INTO `sys_user` VALUES (2, '李四', NULL, '男', '北京');
INSERT INTO `sys_user` VALUES (3, '王五', NULL, '女', '杭州');
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`userid` int(11) NOT NULL,
`roleid` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`userid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1);
INSERT INTO `sys_user_role` VALUES (2, 2);
INSERT INTO `sys_user_role` VALUES (3, 3);
SET FOREIGN_KEY_CHECKS = 1;
1.10.2 pom文件
<?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.example</groupId>
<artifactId>securitysql</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>securitysql</name>
<description>securitysql</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<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>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!--mp的代码生成器的依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.2</version>
</dependency>
<!--swagger2的坐标:版本过高会导致后面在排除错误的接口文档时发生错误-->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.9.1.RELEASE</version>
</dependency>
<!--swagger图形化界面-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.7.8</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
mp代码生成器(旧)
package com.example.securitysql;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class GenerateTest {
/**
* <p>
* 读取控制台内容
* </p>
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotBlank(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");//代码生成位置
gc.setAuthor("L");//设置作者
gc.setOpen(false);
gc.setSwagger2(true); //实体属性 Swagger2 注解
mpg.setGlobalConfig(gc);//是否设置为全局配置
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/security?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("lwl@123");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
//pc.setModuleName(scanner("模块名"));
pc.setParent("com.example.securitysql");//设置代码存放的包名
//pc.setXml("");
pc.setEntity("entity");//实体的包
pc.setMapper("dao");//dao的包
pc.setService("service");//service的包
pc.setServiceImpl("service.impl");//实现类的包
mpg.setPackageInfo(pc);
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 如果模板引擎是 freemarker
// String templatePath = "/templates/mapper.xml.ftl";
// 如果模板引擎是 velocity
String templatePath = "/templates/mapper.xml.vm";
// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + "/src/main/resources/mapper/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 配置模板
//不在java文件夹下面写入mapper文件
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);//设置将字段中的_省略,自动将下一个字母转换为大写字母
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
// 公共父类
// 写于父类中的公共字段
// strategy.setSuperEntityColumns("id");//设置是否有公共的父类主键为id字段,不写这一句时,会给主键为id的字段一个@TableId注解
// strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));//输出仅需要的表名
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
mpg.execute();
}
}
或者也可以使用database生成代码
1.10.3 配置类
server.port=8081
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/security?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=lwl@123
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
1.10.4 权限配置类
package com.example.securitysql.config;
import com.example.securitysql.service.MyUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* 如果SpringBoot的版本过高,WebSecurityConfigurerAdapter就过时了
*/
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 密码加密器,自定义构建多个用户时,用户的密码必须使用密码加密器进行加密
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Autowired
private MyUserDetailService myUserDetailService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/**
* userDetailsService代表使用的是数据库
* 传递一个userDetailsService对象,查询数据库完成相应的功能
*/
auth.userDetailsService(myUserDetailService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//放行登录表单
http.formLogin().permitAll();
//其他资源认证即可访问
http.authorizeRequests().anyRequest().authenticated();
}
}
1.10.5 启动类
package com.example.securitysql;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
@SpringBootApplication
@MapperScan(basePackages = "com.example.securitysql.dao")
//方便在后面进行注解资源绑定
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecuritysqlApplication {
public static void main(String[] args) {
SpringApplication.run(SecuritysqlApplication.class, args);
}
}
1.10.6 数据库服务类
package com.example.securitysql.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.securitysql.dao.SysUserMapper;
import com.example.securitysql.entity.SysPermission;
import com.example.securitysql.entity.SysUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class MyUserDetailService implements UserDetailsService {
@Autowired
private SysUserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1、根据用户名username查找用户信息
QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username",username);
SysUser sysUser = userMapper.selectOne(queryWrapper);
if (sysUser!=null){
//2、如果用户不为空,查找用户对应的权限
List<SysPermission> permissionById = userMapper.findPermissionById(sysUser.getUserid());
//3、将权限转变为指定类型的权限集合
//3.1方法一:使用增强for循环
// Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
// for (SysPermission item: permissionById) {
// //这里权限数组中只要权限的权限码
// SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(item.getPercode());
// authorities.add(simpleGrantedAuthority);
// }
/**
* 3.2方法二:使用Stream流
* map:把集合中的元素变成另一种类型
* item -> new SimpleGrantedAuthority(item.getPercode()):将每一个permission类型的值变为SimpleGrantedAuthority类型
* collect(Collectors.toList()):重新收集为集合
*/
List<SimpleGrantedAuthority> authorities = permissionById.stream().map(item -> new SimpleGrantedAuthority(item.getPercode())).collect(Collectors.toList());
/**4、返回指定类型的数据(UserDetails)
* User(String username, String password, Collection<? extends GrantedAuthority> authorities)
* 用户名、密码、一个指定了泛型的集合(集合中是权限)
*/
UserDetails userDetails = new User(sysUser.getUsername(), sysUser.getUserpwd(),authorities);
return userDetails;
}
return null;
}
}
1.10.7 dao层
public interface SysUserMapper extends BaseMapper<SysUser> {
//根据用户id查询权限信息
public List<SysPermission> findPermissionById(Integer userid);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.securitysql.dao.SysUserMapper">
<!--根据用户id查找用户权限:需要三表联查,角色表(sys_role),角色权限表(sys_role_permission),权限表(sys_permission)-->
<select id="findPermissionById" resultType="com.example.securitysql.entity.SysPermission">
select p.* from sys_permission p,sys_role_permission rp,sys_user_role ur
where p.perid=rp.perid and rp.roleid=ur.roleid and ur.userid=#{userid}
</select>
</mapper>
1.10.8 controller层
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
@RestController
@RequestMapping("user")
public class UserController {
/**
* 第一种:
* 容器会自动注入给参数为 Principal的参数
* @param principal
* @return
*/
@GetMapping("info")
public Principal info(Principal principal){
return principal;
}
/** 第二种
* 登录成功后springSecurity会把当前的用户信息保存到【SecurityContext】中,也就是类似于Session中
* 所有的用户信息都会封装到 Authentication 中
*/
@GetMapping("getInfo")
public Authentication getInfo(){
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
return authentication;
}
@GetMapping("query")
@PreAuthorize("hasAuthority('user:query')")
//拥有这样的一个权限就可以访问上面的资源,使用这个注解需要在启动类中加上@EnableGlobalMethodSecurity(prePostEnabled = true)注解
public String query(){
return "用户查询";
}
@GetMapping("insert")
@PreAuthorize("hasAuthority('user:insert')")
public String insert(){
return "用户添加";
}
@GetMapping("update")
@PreAuthorize("hasAuthority('user:update')")
public String update(){
return "用户修改";
}
@GetMapping("delete")
@PreAuthorize("hasAuthority('user:delete')")
public String delete(){
return "用户删除";
}
@GetMapping("export")
@PreAuthorize("hasAuthority('user:export')")
public String export(){
return "用户导出";
}
}
1.10.9 封装后的数据格式
1.11 SpringSecurity集成thymeleaf
前面的项目已经实现了登录效果,在上面项目的基础上,添加或修改,这里想要实现一种,动态按钮效果[不同权限用户登陆后,看到的效果不同]
但是因为springboot内置了tomcat,tomcat不支持jsp模板引擎,在不排除tomcat的情况下,还想使用前端页面,所以这里选择集成thymeleaf实现一个前后端不分离的项目
1.11.1 pom文件
<!--thymeleaf模板引擎依赖
SpringBoot版本过高(2.7.9)时,thymeleaf依赖不兼容
前后端分离项目不使用thymeleaf依赖
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<!--token-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.2.1</version>
</dependency>
1.11.2 权限配置类
package com.example.securitysql.config;
import com.example.securitysql.service.MyUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* 如果SpringBoot的版本过高,WebSecurityConfigurerAdapter就过时了
*/
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 密码加密器,自定义构建多个用户时,用户的密码必须使用密码加密器进行加密
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Autowired
private MyUserDetailService myUserDetailService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/**
* userDetailsService代表使用的是数据库
* 传递一个userDetailsService对象,查询数据库完成相应的功能
*/
auth.userDetailsService(myUserDetailService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//放行登录表单
http.formLogin()
.loginPage("/login.html") //指定自定义的登录页面
.loginProcessingUrl("/login") //放行自己表单的登录处理路径[因为自己的表单提交路径为 /login]
.successForwardUrl("/success") //登陆成功要跳转的路径,这个请求的路径必须为post
.permitAll();
//权限不足时,跳转到权限不足界面
http.exceptionHandling().accessDeniedPage("/403.html");
//禁用csrf的校验
http.csrf().disable();
//其他资源认证即可访问
http.authorizeRequests().anyRequest().authenticated();
}
}
1.11.3 登录页面
创建在static下面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="/login" method="post">
账号:<input type="text" name="username"/><br>
密码:<input type="text" name="password"/><br>
<input type="submit" value="登录"/>
</form>
</body>
</html>
1.11.4 PageController(处理要跳转的页面)
/**
* 因为要使用视图解析器,所以这里就不使用RestController的风格
*/
@Controller
public class PageController {
@PostMapping("/success")
public String successPage(){
/**
* 如果要更改视图解析器的默认地址,可以在application.properties文件中进行配置
* spring.thymeleaf.prefix=前缀
* spring.thymeleaf.suffix=后缀
*/
return "index"; //视图解析器会解析视图,默认会找/templates/index.html
}
}
1.11.5 登录成功处理页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<!--这里是引入了thymeleaf标签,名字定义为sec,否则下面的sec标签不能使用-->
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--这里的名字sec即是上面定义的名字,authorize就是权限-->
<p sec:authorize="hasAuthority('user:insert')"><button onclick="location.href='/user/insert'">增加</button></p >
<p sec:authorize="hasAuthority('user:delete')"> <button onclick="location.href='/user/delete'">删除</button></p >
<p sec:authorize="hasAuthority('user:update')"><button onclick="location.href='/user/update'">修改</button></p >
<p sec:authorize="hasAuthority('user:query')"><button onclick="location.href='/user/query'">查询</button></p >
<p sec:authorize="hasAuthority('user:export')"><button onclick="location.href='/user/export'">导出</button></p >
</body>
</html>
1.11.6 权限不足访问页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
权限不足……请联系管理员进行操作
</body>
</html>
这里的访问错误页面也可以同样编写
启动项目进行测试
随便访问一个资源会跳转到login.html页面,用户登录正确会跳转到index.html页面,因为已经查询到了权限,所以只会显示它拥有的权限,如果访问权限不足的资源,会跳转到403.html页面
1.12 SpringSecurity认证授权[源码分析]
在使用SpringSecurity时验证登录时,为什么自己没有做密码匹配?
1.12.1 结构总览
Spring security所解决的问题就是安全访问控制,而安全访问控制功能其实就是对所有进入系统的请求进行拦截,校验每个请求是否能够访问它所期望的资源。
根据前边知识的学习,可以通过Filter或AoP等技术来实现,SpringSecurity对web资源的保护是靠Filter实现的,所以从这个Filter来入手,逐步深入Spring Security 原理。当初始化Spring Security时,会创建一个名为SpringSecurityFilterChain的 Servlet过滤器,类型为org.springframework.security.web.FilterChainProxy,它实现了javax.servlet.Filter,因此外部的请求会经过此类,
下图是 Spring Security过虑器链结构图:
上图说明
FilterchainProxy是一个代理,真正起作用的是FilterChainProxy 中securityFilterChain所包含的各个Filter,同时这些Filter作为Bean被Spring管理,它们是Spring Security核心,各有各的职责,但他们并不直接处理用户的认证,也不直接处理用户的授权,而是把它们交给了认证管理器(AuthenticationManager)和决策管理器(AccessDecisionManager)
进行处理下图是 FilterChainProxy相关类的UML图示:
spring security 功能的实现主要是由一系列过滤器链相互配合完成
1.12.2 过滤器链中的几个主要过滤器
1、SecurityContextPersistenceFi1ter
这个Filter是整个拦截过程的入口和出口(也就是第一个和最后一个拦截器),会在请求开始时从配置好的 SecurityContextRepesitory 中获取SecurityContext,然后把它设置给securityContextHolder.在请求完成后将SecurityContextHolder持有的SecurityContext再保存到配置好的SecurityContextRepository ,同时清除securityContextHolder所持有的SecurityContext;
2、UsernamePasswordAuthenticationFilter
用于处理来自表单提交的认证。该表单必须提供对应的用户名和密码,其内部还有登录成功或失败后进行处理的AuthenticationSuccessHandler和AuthenticationFailureHandler,这些都可以根据需求做相关改变;
3、FilterSecurityInterceptor
是用于保护web资源的,使用AccessDecisionManager对当前用户进行授权访问;
4、ExceptionTranslationFilter
能够捕获来自Filterchain所有的异常,并进行处理。但是它只会处理两类异常:AuthenticationException和 AccessDeniedException,其它的异常它会继续抛出。
1.12.3 Spring security 认证工作流程
认证过程中用到的类(方法)
UsernamePasswordAuthenticationFilter (attemptAuthentication)
ProviderManager (authenticate)
DaoAuthenticationProvider (retrieveUser)
AbstractUserDetailsAuthenticationProvider (authenticate)
1.12.4 具体工作流程
当项目启动后请求资源,服务器一定会通过一系列过滤器链,其中有一个过滤器(UsernamePasswordAuthenticationFilter)一定会经过,所以就从这个类开始探究认证流程的源码
[双击shift,输入UsernamePasswordAuthenticationFilter进入此类]
[按着ctrl,点击authenticate()进入到接口,再进入到ProviderManager实现类中]
[按着ctrl,点击authenticate,进入到AuthenticationProvider接口,在进入到AbstractUserDetailsAuthenticationProvider实现类中]
[按着ctrl,点击retrieveUser,进入到retrieveUser接口,在进入到DaoAuthenticationProvider实现类中,这里会根据用户名,查询用户信息和用户权限]
如果没有没有自己写的类,会使用内存中的UserDetailsService类(通过实现UserDetailsService接口来识别)
查到对象后会返回上一级调用者类中,没有异常,向下执行check方法
[按着ctrl,点击check方法,进入UserDetailsChecker接口,进入AbstractUserDetailsAuthenticationProvider类(对,还是这个类)]
执行完check方法,会继续执行这个类中的additionalAuthenticationChecks方法
[按着ctrl,进入抽象方法additionalAuthenticationChecks,进入实现类DaoAuthenticationProvider]
这些判断都通过之后,会再回到AbstractUserDetailsAuthenticationProvider中继续向下执行createSuccessAuthentication