SpringSecurity前后端分离(动态鉴定)
一、解释认证流程
1.原始认证流程
原始认证过程通常与Session一起使用,但是前后端分离后能使用Session了
SpringSecurity默认认证程序如下图所示(此图为B站UP主三更草堂讲SpringSecurity课程的图)

DaoAuthenticationProvider继承AbstractUserDetailsAuthenticationProvider抽象类,而AbstractUserDetailsAuthenticationProvider抽象类又实现了AuthenticationProvider这个接口。
AuthenticationProvider接口和AuthenticationManager接口都有authenticate()这个方法认证流程:
1.输入用户名和密码
2、
UsernamePasswordAuthenticationFilter将用户名和密码包装成密码Authentication对象然后再调用
AuthenticationManager接口中的authenticate()方法进行认证,在AuthenticationManager接口实现类ProviderManager重写也被调用authenticate()认证方法。抽象类。AbstractUserDetailsAuthenticationProvider中重写了authenticate()方法4、
AbstractUserDetailsAuthenticationProvider的authenticate()该方法调用抽象法retrieveUser()方法5、
DaoAuthenticationProvider在重写方法retrieveUser()里调用了loadUserByUsername()方法6、
loadUserByUsername()方法会返回UserDetails对象认证成功返回上一层
2.前后端分离认证过程
在前后端分离后,我们要求在认证成功或失败时返回相应的状态代码,然后我们不再使用它Session经常使用认证管理jwt(JSON Web Token)认证引出了两种前后分离的写法
(图为B站UP主三更草堂讲SpringSecurity课程的图)
无论使用以下哪种写法,这里都需要
UsernamePasswordAuthenticationFilter在前面加一个过滤器Token认证,如果Token认证成功意味着用户已登录;Token认证失败表明未登录或登录已过期。
2.1、继承UsernamePasswordAuthenticationFilter的写法
认证流程:
1.输入用户名和密码
2、
MyUsernamePasswordAuthenticationFilter将用户名和密码包装成密码Authentication对象然后再调用
AuthenticationManager接口中的authenticate()认证方法,在AuthenticationManager接口实现类ProviderManager重写也被调用authenticate()认证方法。抽象类。AbstractUserDetailsAuthenticationProvider中重写了authenticate()方法4、
AbstractUserDetailsAuthenticationProvider的authenticate()该方法调用抽象法retrieveUser()方法5、
DaoAuthenticationProvider在重写方法retrieveUser()里调用了loadUserByUsername()方法,自定义AuthUserDetailsServiceImpl类实现UserDetailsService接口,重写loadUserByUsername()方法6、在
loadUserByUsername()在方法中,用户和角色将被查询并返回UserDetails对象7、在继承
WebSecurityConfigurerAdapter登录成功、失败处理器设置在类中,处理器定义返回状态码等信息
2.2.自定义写作
UsernamePasswordAuthenticationToken继承了AbstractAuthenticationToken抽象类,AbstractAuthenticationToken实现了抽象类Authentication接口认证流程:
1.前端通过将用户名和密码发送到后端控制器,控制器调用业务层
2、Service层创建
UsernamePasswordAuthenticationToken对象将用户名和密码包装成Authentication对象3、然后调用
AuthenticationManager的authenticate()认证方法,抽象AbstractUserDetailsAuthenticationProvider中重写了authenticate()方法4、
AbstractUserDetailsAuthenticationProvider的authenticate()该方法调用抽象法retrieveUser()方法5、
DaoAuthenticationProvider在重写方法retrieveUser()里调用了loadUserByUsername()方法,自定义AuthUserDetailsServiceImpl类实现UserDetailsService接口,重写loadUserByUsername()方法6、在
loadUserByUsername()在方法中,用户和角色将被查询并返回UserDetails对象
2.3、区别
1、使用
UsernamePasswordAuthenticationFilter自定义写法不需要使用登录成功和失败处理器,自定义写法可以自定义失败处理器(包括认证异常和授权异常)。登陆失败和没有权限)2、使用
UsernamePasswordAuthenticationFilter的写法对于扩展写法没那么友好,比如说添加手机验证码
二、数据库的设计
该示例是上面自定义的前后端分离的写法
这里使用的是Oracle数据库,这里没有权限的表,但是使用角色来判断也差不多
1、用户表
2、用户角色关系表
3、角色表
4、图片表
5、点赞表
三、初始配置
SpringBoot 版本是 2.6.0
1、项目结构
2、导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</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>
<!-- SpringSecurity -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</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>
<!--redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.74</version>
</dependency>
<!--hutool工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.5.6</version>
</dependency>
<!-- mybatis-plus-generator -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!-- lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<!--添加 模板引擎 依赖,MyBatis-Plus 支持 Velocity(默认)-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.2</version>
</dependency>
<!--swagger的依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<!-- JWT的依赖 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Oracle数据库 -->
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
3、代码生成器
代码生成器这里最开始使用的是mysql 8.X版本的,读者需要自己修改一下数据库的名字,如果是mysql 5.X还需要修改一下驱动
后面才改用Oracle数据库,这里的代码就懒得改了
package com.guet.APPshareimage;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.apache.commons.lang3.StringUtils;
import java.util.Scanner;
/** * @Author LZDWTL * @Date 2021-12-15 17:09 * @ClassName CodeGenerator * @Description 代码生成器 */
public class CodeGenerator {
/** * <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.isNotEmpty(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 创建代码生成器对象
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
gc.setOutputDir(scanner("请输入你的项目路径") + "/src/main/java");
//作者
gc.setAuthor("LZDWTL");
//生成之后是否打开资源管理器
gc.setOpen(false);
//重新生成时是否覆盖文件
gc.setFileOverride(false);
//%s 为占位符
//mp生成service层代码,默认接口名称第一个字母是有I
gc.setServiceName("%sService");
//设置主键生成策略 自动增长
gc.setIdType(IdType.AUTO);
//设置Date的类型 只使用 java.util.date 代替
gc.setDateType(DateType.ONLY_DATE);
//开启实体属性 Swagger2 注解
gc.setSwagger2(true);
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/shareimage?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("shareimage");
dsc.setPassword("888888");
//使用mysql数据库
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
//pc.setModuleName(scanner("请输入模块名"));
pc.setParent("com.guet.APPshareimage");
pc.setController("controller");
pc.setService("service");
pc.setServiceImpl("service.impl");
pc.setMapper("mapper");
pc.setEntity("entity");
pc.setXml("mapper");
mpg.setPackageInfo(pc);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
//设置哪些表需要自动生成
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
//实体类名称驼峰命名
strategy.setNaming(NamingStrategy.underline_to_camel);
//列名名称驼峰命名
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
//使用简化getter和setter
strategy.setEntityLombokModel(true);
//设置controller的api风格 使用RestController
strategy.setRestControllerStyle(true);
//驼峰转连字符
strategy.setControllerMappingHyphenStyle(true);
//忽略表中生成实体类的前缀
//strategy.setTablePrefix("t_");
mpg.setStrategy(strategy);
mpg.execute();
}
}
运行代码生成器,复制路径输入,然后依次输入数据库中表的名字
D:\WorkSpace\JavaWorkSpce\ideal\APP-shareimage\APP-shareimage
t_user,t_picture,t_like,t_user_role,t_role
4、配置application.yml
根据自己的数据库和redis进行配置
server:
port: 8080
spring:
# 数据库配置
datasource:
driver-class-name: oracle.jdbc.driver.OracleDriver
url: jdbc:oracle:thin:@120.77.80.135:1521:orcl
username: XXXXXX
password: XXXXXX
# 连接池
hikari:
# 连接池名
pool-name: DateHikariCP
# 最小空闲连接数
minimum-idle: 5
# 空闲连接最大存活时间,默认600000(10分钟)
idle-timeout: 180000
# 最大连接数,默认10
maximum-pool-size: 10
# 从连接池返回的连接自动提交
auto-commit: true
# 连接最大存活时间,1800000(30分钟)
max-lifetime: 1800000
# 连接超时时间,默认30000(30秒)
connection-timeout: 30000
# 测试连接是否可用的查询语句
#connection-test-query: SELECT 1 #这个是mysql的测试语句
connection-test-query: SELECT * from dual #这个是oracle的测试语句
#redis配置
redis:
#服务器地址
host: 120.77.80.135
#端口
port: 6379
#redis密码
password: XXXXXX
#数据库,默认是0
database: 0
#超时时间
timeout: 1209600000ms
lettuce:
pool:
#最大链接数,默认8
max-active: 8
#最大连接阻塞等待时间,默认-1
max-wait: 10000ms
#最大空闲连接,默认8
max-idle: 200
#最小空闲连接,默认0
min-idle: 5
mybatis-plus:
mapper-locations: classpath:/mapper/*Mapper.xml
type-aliases-package: com.guet.APPshareimage.entity
logging:
level:
com.guet.shareimage.mapper: debug
jwt:
# JWT存储的请求头
tokenHeader: Authorization
# JWT 加解密使用的密钥
secret: lzdwtl
# JWT的超期限时间(1000*60*60*24*14)14天,即两周
expiration: 1209600000
# JWT 负载中拿到开头
tokenHead: Bearer
role:
roleid: 1
5、其他配置、工具类
5.1、SpringSecurity配置类
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyOncePerRequestFilter myOncePerRequestFilter;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//1、关闭csrf,关闭Session
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
//2、设置不需要认证的URL
http
.authorizeRequests()
//允许未登录的用户进行访问
.antMatchers("/doLogin").anonymous()
//其余url都要认证才能访问
.anyRequest().authenticated();
}
}
5.2、JSON格式返回配置类
public abstract class JSONAuthentication { /** * 输出JSON * * @param request * @param response * @param obj * @throws IOException * @throws ServletException */ protected void WriteJSON(HttpServletRequest request, HttpServletResponse response, Object obj) throws IOException, ServletException { //这里很重要,否则页面获取不到正常的JSON数据集 response.setContentType("application/json;charset=UTF-8"); //跨域设置 response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Method", "POST,GET"); //输出JSON PrintWriter out = response.getWriter(); out.write(JSON