Spring-boot
hello World
添加依赖
添加maven依赖
<!-- 添加父工程,父工程主要是做版本控制和依赖管理-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
<!-- web项目添加依赖,只需要一个web其他会自动导入 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
部署
maven中添加插件,然后打成jar包,然后使用java -jar 运行项目即可
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
配置
修改依赖版本
如果需要手动修改可以在pom.xml添加对应依赖的版本号
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
程序入口
//注解表示是springboot的主类、入口
@SpringBootApplication
public class MySpringBootApplication {
public static void main(String[] args) {
//得到IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MySpringBootApplication.class,args);
//获得springContext添加的所有组件的名称
String[] beanDefinitionNames = run.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
springboot自动配置流程
-
自动配置好Tomcat
- 引入依赖
- 配置Tomcat
-
自动配置好springmvc常用功能如字符编码
- SpringBoot帮我们配置好了所有web开发的常见场景
-
自动扫描包
-
主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
-
无需以前的包扫描配置
-
想要改变扫描路径
- @SpringBootApplication(scanBasePackages="com.lun")
- @ComponentScan 指定扫描路径
-
-
各种配置拥有默认值
- 默认配置最终都是映射到某个类上,如:
MultipartProperties - 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
- 默认配置最终都是映射到某个类上,如:
总结
SpringBoot默认会在底层配好所有的组件,但是如果用户自己配置了以用户的优先。
-
SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
-
每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。(xxxxProperties里面读取,xxxProperties和配置文件进行了绑定)
-
生效的配置类就会给容器中装配很多组件
-
只要容器中有这些组件,相当于这些功能就有了
-
定制化配置
- 用户直接自己@Bean替换底层的组件
- 用户去看这个组件是获取的配置文件什么值就去修改。
xxxxxAutoConfiguration ---> 组件 ---> xxxxProperties里面拿值 ----> application.properties
查看自动配置报告
查看自动配置了哪些
application.properties配置文件中debug=true开启自动配置报告。
- Negative(不生效)
- Positive(生效)
注解,spring配置类型注解
@Configuration
使用Configuration配置的类默认是单例的
proxyBeanMethods修改代理bean的方式
- Full模式:proxyBeanMethods=true则是单例模式
- Lite模式:proxyBeanMethods=false则每次返回的都是一个新对象
配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
配置 类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式(默认)
/**
* 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2、配置类本身也是组件
* 3、proxyBeanMethods:代理bean的方法
* Full(proxyBeanMethods = true)(保证每个@Bean方法被调用多少次返回的组件都是单实例的)(默认)
* Lite(proxyBeanMethods = false)(每个@Bean方法被调用多少次返回的组件都是新创建的)
*/
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {
/**
* Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
* @return
*/
@Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
public User user01(){
User zhangsan = new User("zhangsan", 18);
//user组件依赖了Pet组件
zhangsan.setPet(tomcatPet());
return zhangsan;
}
@Bean("tom")
public Pet tomcatPet(){
return new Pet("tomcat");
}
}
@ComponentScan
添加扫描包的范围
@ComponentScan("edu.mkit.test")
@import
给容器中自动创建出这个类型的组件、默认组件的名字就是全类名
@Import({User.class, DBHelper.class})//直接添加这个类型的组件 @Configuration //告诉SpringBoot这是一个配置类 == 配置文件 public class MyConfig { }
@Conditional条件装配
条件装配:满足Conditional指定的条件,则进行组件注入
- ConditionalOnProperty:控制配置类是否生效,可以将配置与代码进行分离,实现了更好的控制配置
- ConditionalOnResource
- ConditionalOnBean
- ConditionalOnClass
- ConditionalOnMissingBean
- ConditionalOnMissingClass
//ConditionalOnBean在容器中存在tom的时候user才会注入,如果tom不存在那么user也不存在
@ConditionalOnBean(name="tom")
@Bean
public User user(){
User zhangsan = new User("zhangsan", 18);
zhangsan.setPet(tomcatPet());
return zhangsan;
}
@Bean("tom")
public Pet tomcatPet(){
return new Pet("tomcat");
}
@importResource
导入Spring配置文件,即可使用原来的xml
@ImportResource("classpath:beans.xml") public class MyConfig { ... }
@ConfigurationProperties
读取到properties文件中的内容,并且把它封装到JavaBean中
car.brand=BMW
car.price=100000
@Component
@ConfigurationProperties(prefix = "car")
//prefix是前缀-> new Car().price = car.price
@MatrixVariable矩阵变量
web请求
拦截器
-
自定义拦截器
自定义拦截器,即拦截器的实现类,一般有两种自定义方式:
定义一个类,实现
org.springframework.web.servlet.HandlerInterceptor接口。定义一个类,继承已实现了HandlerInterceptor接口的类,例如
org.springframework.web.servlet.handler.HandlerInterceptorAdapter抽象类。//实现HandlerInterceptor 接口 //preHandle:在业务处理器处理请求之前被调用。预处理,可以进行编码、安全控制、权限校验等处理; boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; //postHandle:在业务处理器处理请求执行完成后,生成视图之前执行。后处理(调用了Service并返回ModelAndView,但未进行页面渲染),有机会修改ModelAndView void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception; //在DispatcherServlet完全处理完请求后被调用,可用于清理资源等。返回处理(已经渲染了页面); void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception; -
把拦截器添加到WebMvcConfiguration中
实现addInterceptors方法,在方法中添加
/** * 添加拦截器 * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { // addPathPatterns(/**) 拦截所有请求,excludePathPatterns("")放行请求 registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns(""); }
Converter转换器
使用场景:
前端请求url为:http://127.0.0.1:8080/address?address=四川/成都/青羊区如何使用对象进行接收
使用转换器
// 实体类 @Data @ToString public class Address { private String province; private String city; private String area; } // controller @Controller public class AddressController { @ResponseBody @GetMapping("/address") public Address address(Address address){ return address; } } //转换器 @Configuration public class Config implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new Converter<String,Address>(){ @Override public Address convert(String source) { String[] split = source.split("/"); Address address = new Address(); address.setProvince(split[0]); address.setCity(split[1]); address.setArea(split[2]); return address; } }); }
数据响应与内容协商
内容协商
springboot根据请求头Accept:text/html,application/xhtml+xml,application/xml
来确定返回给客户端的内容是什么类型
请求处理
请求映射
REST风格
springboot默认关闭了rest风格,开启需要添加配置,RequestMapping里的参数是区分大小写的,所以写的时候最好是全小写
spring:
mvc:
hiddenmethod:
filter:
enabled: true #开启页面表单的Rest功能
Rest原理(表单提交要使用REST的时候)
表单提交会带上
_method=PUT请求过来被
HiddenHttpMethodFilter拦截
请求是否正常,并且是POST
- 获取到
_method的值。- 兼容以下请求;PUT.DELETE.PATCH
- 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
- 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
修改默认的_methd
@Configuration(proxyBeanMethods = false)//Lite(多例)模式
public class WebConfig{
//自定义filter
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
//创建自己的HiddenHttpMethodFilter添加到IOC容器中
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");//把_methd修改为_m
return methodFilter;
}
}
请求映射原理
SpringMVC所有请求的入口
org.springframework.web.servlet.DispatcherServlet->doDispatch()
所有的请求映射都在HandlerMapping中:
-
SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
-
SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
-
请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
- 如果有就找到这个请求对应的handler
- 如果没有就是下一个 HandlerMapping
-
我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping
请求处理注解-常用参数注解
注解:
-
@PathVariable路径变量获取请求路径占位符的值
@RequestMapping("test/{id}}") public String test5(@PathVariable("id") Long id ,){ // id为请求占位符的id return ""; } -
@RequestHeader获取请求头 -
@RequestParam获取请求参数(指问号后的参数,url?a=1&b=2)[GET]@GetMapping("/greeting") public String greeting(@RequestParam(name="name", required=false, defaultValue="World") String name, Model model) { //name参数和value参数基本等价,都是获取前端传入的数据 //使用 @RequestParam 注解将 HTTP 请求中名为 name 的参数映射到 String 类型的 name 参数上,required=false 表示该参数不是必需的,默认值为 "World". //如果没有设置默认值,且required为true那么如果请求中没有包含name这个参数会抛出异常 model.addAttribute("name", name); return "greeting"; } -
@CookieValue获取Cookie值 -
@RequestAttribute获取request域属性//在形参中直接使用requestAttribute可以省去在方法体内使用request.getRequestAttribute获取这一步 public String index(@requestAttribute("msg") String msg){ } -
@RequestBody获取请求体[POST] -
@MatrixVariable矩阵变量,springboot默认关闭一般,矩阵变量会应用到这样的场景。在页面开发中,如果Cookie被禁用了,该如何获得Session中的数据?
如果使用Cookie,通过获取Cookie中的JSessionID,找到Session对象,从而获取该Session中的数据。而Cookie被禁用了,就无法获取JSessionID,也就不能用这种方法获取Session的数据。这时,我们可以使用
url重写的方式解决这一问题,比如这样的url:/abc;jsessionid=xxxxx,也就是将Cookie的值使用矩阵变量的方式传递 -
@ModelAttribute
数据校验
校验注解
| 注解 | 验证的数据类型 | 说明 |
|---|---|---|
| Null | 所有类型 | 验证元素值必须为 null |
| NotNull | 所有类型 | 验证元素值必须不为 null |
| NotBlank | CharSequence | 验证元素值不能为 null,并且至少包含一个非空白字符。 |
| NotEmpty | CharSequence、Collection、Map、Array | 验证元素值不能为 null,且不能为空 |
| Size(min = min, max = max) | 同 NotEmpty | 验证元素的 size 必须在 min 和 max 之间(包含边界),认为 null 是有效的 |
| AssertFalse | boolean、Boolean | 验证元素值必须为 false,认为 null 是有效的 |
| AssertTrue | 同 AssertFalse | 验证元素值必须为 true,认为 null 是有效的 |
| DecimalMax(value=, inclusive=) | BigDecimal、BigInteger、CharSequence,byte、 short、int、long 及其包装类型,由于舍入错误,不支持double和float | 验证元素值必须小于等于指定的 value 值,认为 null 是有效的 |
| DecimalMin | 同 DecimalMax | 验证元素值必须大于等于指定的 value 值,认为 null 是有效的 |
| Max | 同 DecimalMax,不支持CharSequence | 验证元素值必须小于等于指定的 value 值,认为 null 是有效的 |
| Min | 同 DecimalMax,不支持CharSequence | 验证元素值必须大于等于指定的 value 值,认为 null 是有效的 |
| Digits(integer =, fraction =) | 同 DecimalMax | 验证元素整数位数的上限 integer 与小数位数的上限 fraction,认为 null 是有效的 |
| Positive | BigDecimal、BigInteger,byte、short、int、long、float、double 及其包装类型 | 验证元素必须为正数,认为 null 是有效的 |
| PositiveOrZero | 同Positive | 验证元素必须为正数或 0,认为 null 是有效的 |
| Negative | 同Positive | 验证元素必须为负数,认为 null 是有效的 |
| NegativeOrZero | 同Positive | 验证元素必须为负数或 0,认为 null 是有效的 |
| Future | Date、Calendar、Instant、LocalDate、LocalDateTime、LocalTime、MonthDay、OffsetDateTime、OffsetTime、Year、YearMonth、ZonedDateTime、HijrahDate、JapaneseDate、MinguoDate、ThaiBuddhistDate | 验证元素值必须是一个将来的时间,认为 null 是有效的 |
| FutureOrPresent | 同 Future | 验证元素值必须是当前时间或一个将来的时间,认为 null 是有效的 |
| Past | 同 Future | 验证元素值必须是一个过去的时间,认为 null 是有效的 |
| PastOrPresent | 同 Future | 验证元素值必须是当前时间或一个过去的时间,认为 null 是有效的 |
| Email(regexp = 正则表达式,flag = 标志的模式) | CharSequence | 验证注解的元素值是Email,可以通过 regexp 和 flag 指定自定义的 email 格式,认为 null 是有效的 |
| Pattern | 同 Email | 验证元素值符合正则表达式,认为 null 是有效的 |
案例
!!注意!!
如果只在model类里写了注解,但是在请求上没有开启功能,注解还是不会生效,必须在Control类上加上@Valid注解才有用
public class BrandEntity implements Serializable {
@TableId
@Null(message = "新增时必须为空")
private Long brandId;
@NotBlank(message = "品牌名不能为空")
private String name;
@URL(message = "logo必须是url地址")
@NotEmpty(message = "不能为空")
private String logo;
}
@RequestMapping("/info/{brandId}")
public R info(@Valid @PathVariable("brandId") Long brandId){
BrandEntity brand = brandService.getById(brandId);
return R.ok().put("brand", brand);
}
分组校验
在不同的请求下,需要不同的注解来校验数据,
如自增id字段,在新增接口中,则不需要校验该数据,但是在更新数据时,则该字段不能为空。
所以需要分组校验。
分组时,必须在Control接口指定当前的分组,且还需要提前准备好分组接口(接口什么都不写, 只需要一个空接口)。
如果在Control指定了分组,而校验没有指定分组的话,则该校验会失效
public class BrandEntity implements Serializable {
@TableId
@Null(message = "新增时必须为空",groups = {AddGroup.class})
@NotNull(message = "修改时必须有该字段",groups = {UpdateGroup.class})
private Long brandId;
@NotBlank(message = "品牌名不能为空")
// 如果在Control指定了分组,如果没有写,则不会识别到该校验
private String name;
@URL(message = "logo必须是url地址")
@NotEmpty(message = "不能为空")
private String logo;
}
@RequestMapping("/save")
//@RequiresPermissions("product:brand:save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
// 接口内容可以什么都不写,只是单纯使用名字分组的
public interface AddGroup {
}
自定义校验
- 自定义一个校验注解
- 实现自定义注解的自定义校验器
- 添加配置文件,校验失败提示信息
一个校验注解,可以由多个校验器实现
package com.yibai.common.valid;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
// 自定义注解
@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
String message() default "{com.yibai.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals() default { };
}
package com.yibai.common.valid;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
// 自定义校验器
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private Set<Integer> set = new HashSet<>();
/**
* 初始化方法
* @param constraintAnnotation
*/
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for (int val : vals) {
set.add(val);
}
}
/**
* 判断是否效验成功
* @param value 需要效验的值
* @param context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
//判断是否有包含的值
boolean contains = set.contains(value);
return contains;
}
}
public class BrandEntity implements Serializable {
/**
* 显示状态[0-不显示;1-显示]
*/
@ListValue(vals = {0,1},message = "显示状态必须是0或1")
private Integer showStatus;
}
# 文件名 ValidationMessages.properties
com.yibai.common.valid.ListValue.message=必须是指定的值
简化开发
lombok
开发过程中用注解的方式,简化了 JavaBean 的编写,避免了冗余和样板式代码而出现的插件,让编写的类更加简洁。
添加依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
@NoArgsConstructor //无参构造器
@AllArgsConstructor //全参构造器,如果需要其他单独的构造器需要自己重新写
@Data //get&set方法
@ToString //tostring
@EqualsAndHashCode //equals和hashcode
@Slf4j //添加后可以使用(log.info("test...");)直接输出日志信息,而不用sout,
伪热更新(自动重启项目)dev-tools
在IDEA中,项目或者页面修改以后:Ctrl+F9。
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
yaml
YAML 是 "YAML Ain't Markup Language"(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言)。
yaml代码提示插件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- 下面插件作用是工程打包时,不将spring-boot-configuration-processor打进包内,让其只在编码的时候有用 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
基本语法
- key: value;kv之间有空格
- 大小写敏感
- 使用缩进表示层级关系
- 缩进不允许使用tab,只允许空格
- 缩进的空格数不重要,只要相同层级的元素左对齐即可
- '#'表示注释
- 字符串无需加引号,如果要加,单引号''、双引号""表示字符串内容会被 转义、不转义
- 大写字母前面可以使用-加小写字母代替如:M-》-m
数据类型
字面量:单个的、不可再分的值。date、boolean、string、number、null
对象
键值对的集合。map、hash、set、object ,冒号后面需要一个空格在跟上具体的数值
#行内写法:
k: {k1:v1,k2:v2,k3:v3}
#或
k:
k1: v1
k2: v2
k3: v3
数组
一组按次序排列的值。array、list、queue
#行内写法:
k: [v1,v2,v3]
#或者
k:
- v1
- v2
- v3
示例
java
public class Person {
private String userName;
private Boolean boss;
private Date birth;
private Integer age;
private Pet pet;
private String[] interests;
private List<String> animal;
private Map<String, Object> score;
private Set<Double> salarys;
private Map<String, List<Pet>> allPets;
}
public class Pet {
private String name;
private Double weight;
}
yaml
person:
userName: zhangsan
boss: false
birth: 2019/12/12 20:12:33
age: 18
pet:
name: tomcat
weight: 23.4
interests: [篮球,游泳]
animal:
- jerry
- mario
score:
english:
first: 30
second: 40
third: 50
math: [131,140,148]
chinese: {first: 128,second: 136}
salarys: [3999,4999.98,5999.99]
allPets:
sick:
- {name: tom}
- {name: jerry,weight: 47}
health: [{name: mario,weight: 47}]
静态资源
静态资源目录
只要静态资源放在类路径下: called
/static(or/publicor/resourcesor/META-INF/resources访问 : 当前项目根路径/ + 静态资源名
原理: 静态映射/**。
请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面。
也可以改变默认的静态资源路径,
/static,/public,/resources,/META-INF/resources失效
# 修改默认静态资源路径
#2.5.6新版本这个弃用了,有一个新的方式,web:resources:static-locations:[classpath:/newresource/]
resources:
static-locations: [classpath:/newresource/]
添加静态资源访问前缀
spring:
mvc:
static-path-pattern: /res/**
静态资源访问方式:当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找
webjar
可用jar方式添加css,js等资源文件,
例如,添加jquery
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.5.1</version>
</dependency>
访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径。
自定义web配置
默认配置
springboot的默认web配置在WebMVCAutoConfiguration类中
Security
Spring Security是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制。
关于安全方面的两个主要区域是“认证”和“授权”(或者访问控制),一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分,这两点也是 Spring Security 重要核心功能。
1、用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录。
2、用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情。
过滤器
security本质就是一个过滤器链,通过一个个过滤器来进行安全验证,Security 采取过滤链实现认证与授权,只有当前过滤器通过,才能进入下一个过滤器
SpringSecurity 采用的是责任链的设计模式,它有一条很长的过滤器链。现在对这条过滤器链的 15 个过滤器进行说明:
(1) WebAsyncManagerIntegrationFilter:将 Security 上下文与 Spring Web 中用于处理异步请求映射的 WebAsyncManager 进行集成。
(2) SecurityContextPersistenceFilter:在每次请求处理之前将该请求相关的安全上下文信息加载到 SecurityContextHolder 中,然后在该次请求处理完成之后,将SecurityContextHolder 中关于这次请求的信息存储到一个“仓储”中,然后将SecurityContextHolder 中的信息清除,例如Session 中维护一个用户的安全信息就是这个过滤器处理的。
(3) HeaderWriterFilter:用于将头信息加入响应中。
(4) CsrfFilter:用于处理跨站请求伪造。
(5)LogoutFilter:用于处理退出登录。
(6)UsernamePasswordAuthenticationFilter:用于处理基于表单的登录请求,从表单中获取用户名和密码。默认情况下处理来自 /login 的请求。从表单中获取用户名和密码时,默认使用的表单 name 值为 username 和 password,这两个值可以通过设置这个过滤器的 usernameParameter 和 passwordParameter 两个参数的值进行修改。
(7)DefaultLoginPageGeneratingFilter:如果没有配置登录页面,那系统初始化时就会配置这个过滤器,并且用于在需要进行登录时生成一个登录表单页面。
(8)BasicAuthenticationFilter:检测和处理 http basic 认证。
(9)RequestCacheAwareFilter:用来处理请求的缓存。
(10)SecurityContextHolderAwareRequestFilter:主要是包装请求对象 request。
(11)AnonymousAuthenticationFilter:检测 SecurityContextHolder 中是否存在Authentication 对象,如果不存在为其提供一个匿名 Authentication。
(12)SessionManagementFilter:管理 session 的过滤器
(13)ExceptionTranslationFilter:处理 AccessDeniedException 和AuthenticationException 异常。
(14)FilterSecurityInterceptor:可以看做过滤器链的出口。
(15)RememberMeAuthenticationFilter:当用户没有登录而直接访问资源时, 从 cookie里找出用户的信息, 如果 Spring Security 能够识别出用户提供的 remember me cookie,用户将不必填写用户名和密码, 而是直接登录进入系统,该过滤器默认不开启。
重要过滤器
UsernamePasswordAuthenticationFilter 过滤器:该过滤器会拦截前端提交的 POST 方式的登录表单请求,并进行身份认证。
ExceptionTranslationFilter 过滤器:该过滤器不需要我们配置,对于前端提交的请求会直接放行,捕获后续抛出的异常并进行处理(例如:权限访问限制)。
FilterSecurityInterceptor 过滤器:该过滤器是过滤器链的最后一个过滤器,根据资源权限配置来判断当前请求是否有权限访问对应的资源。如果访问受限会抛出相关异常,并 由 ExceptionTranslationFilter 过滤器进行捕获和处理。
控制器
AuthenticationFailureHandler //认证失败处理器
AuthenticationSuccessHandler //认证成功处理器
LogoutSuccessHandler //退出成功处理器
MkAccessDenyHandler//访问拒绝处理器
SecurityConfig
authorizeRequests //URL访问控制授权
antMatchers //使用ant表达式匹配URL(里面可以写多个请求)
regexMatchers()//使用正则表达式匹配请求
anyRequest //全部请求
denyAll() //禁止所有请求,或上一个方法匹配的请求
permitAll //上一个方法匹配到的请求,或直接通过所有请求
authenticated //匹配的请求需要登录才能访问
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//拦截请求
http.
authorizeRequests()
//允许login底下的请求访问
.antMatchers("/login/**").permitAll()
//禁止所有请求访问
.anyRequest().denyAll();
}
}
security 注解
security默认是不开启注解功能的,如果想要使用,需要在springboot的启动类上添加
@EnableGlobalMethodSecurity注解,开启security的注解使用@enableglobalmethodsecurity()
- prepostenabled:是否启用 spring security pre/post annotations。默认值为 false。
- securedenabled:是否启用使用 @secured 注释进行授权。默认值为 false。
- jsr250enabled:是否启用 jsr-***注解进行授权。默认值为 false。
- proxytargetclass:是否要使用基于子类的(cglib)代理或基于接口的代理。默认为false,意味着使用基于接口的代理
// 先决条件授权,要求在进入方法之前执行授权检查。,可以将登录用户的 roles / permissions 参数 传到方法中 @PreAuthorize() // 后置授权,允许在退出方法时执行授权检查 @PostAuthorize() // 要求用户具有指定的角色才能访问方法。需要注意匹配的字符串需要增加前缀 ROLE_。 @Secured // 权限验证通过后,留下指定用户名的数据。对返回数据做过滤 @PostFilter // 对传递参数值做过滤。 @PreFilter
spring cache缓存
Spring 定义了CacheManager和Cache接口来提供缓存的抽象,可以通过切换的CacheManager的实现类,来使用不同的缓存技术。CacheManager的实现有:ConcurrentMapCacheManager、EhCacheCacheManager、RedisCacheManager等。
只有public方法才可以被缓存
- @Cacheable :触发将数据保存到缓存的操作;
- @CacheEvict : 触发将数据从缓存删除的操作;
- @CachePut :不影响方法执行更新缓存;
- @Cacheing:组合以上多个操作;
- @CacheConfig:在类级别共享缓存的相同配置;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
spring:
cache:
type: redis # 使用Redis作为缓存
redis:
time-to-live: 3600000 # 缓存过期时间为3600000毫秒(1小时)
use-key-prefix: true # 使用key前缀
cache-null-values: true # 缓存null值
配置一个自定义security登录过滤器
该过滤器不包含userdetails,使用到的类有。
过滤器的链
filter用于接收网络请求把数据封装到token中,调用manager进行认证
manager管理多个provider,选择合适的provider进行认证
provider负责认证,返回认证后token
token存储认证信息handler负责结果
handler负责token认证的结果处理
// 负责拦截请求并把获取的参数包装成AuthenticationToken交给token指定的provider处理 org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; // 负责封装数据,分别有两个构造器,一个构造器是校验前使用,也就是上面过滤器第一次构造的token,第二个构造器由provider提供,校验后使用 org.springframework.security.authentication.AbstractAuthenticationToken; // 登录成功后处理的handler,注意,这个handler要配置到自定义的filter上,http对象添加是不生效的 org.springframework.security.web.authentication.AuthenticationSuccessHandler; // 认证失败后的handler org.springframework.security.web.authentication.AuthenticationFailureHandler; // 权限不足,未验证的handler org.springframework.security.web.access.AccessDeniedHandler; // 配置类 org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; // 单次fliter,可以单独在某个指定过滤器前进行处理, org.springframework.web.filter.OncePerRequestFilter;
WebSecurityConfigurerAdapter
@Override
protected void configure(HttpSecurity http) throws Exception {
// 通过配置http来构造filter,
// 可以进行csrf防护的配置,请求拦截的配置。
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 可以进行权限管理,添加自定义的provider
auth.authenticationProvider(emailAuthenticationProvider);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
// 必须重写,要不然自定义的filter传入的manager会空指针。
return super.authenticationManagerBean();
}
// 注入自定义的provider
@Bean
EmailAuthenticationProvider emailAuthenticationProvider(){
EmailAuthenticationProvider emailAuthenticationProvider = new EmailAuthenticationProvider();
return emailAuthenticationProvider;
}
//注入密码编码器
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
// 如果还有需要的@bean或@autowrite可以自行注入,需要注意的是如果需要进行一些配置的bean不能则直接使用autowrite注入,需要通过bean构造后注入。
AbstractAuthenticationProcessingFilter //自定义的filte
//拦截自定义的请求,构造未验证的token,交给provider
public class EmailAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
// 是否只能post请求
private boolean postOnly = true;
private AuthenticationManager authenticationManager;
//执行父方法的构造器,指定拦截的请求和方法
public EmailAuthenticationFilter() {
//super也是必须要执行的
super(new AntPathRequestMatcher("/login/email", "POST"));
}
//拦截到指定请求,进行封装token
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
//是否post请求
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
//获取请求信息
String email = request.getParameter("userEmail");
String password = request.getParameter("password");
if (StringUtils.isEmpty(email) || StringUtils.isEmpty(password) ) {
throw new UserException(ResultCodeEnum.ARGUMENT_VALID_ERROR);
}
/**
* 使用请求参数传递的邮箱和验证码,封装为一个未认证 EmailVerificationCodeAuthenticationToken 身份认证对象,
* 然后将该对象交给 AuthenticationManager 进行认证
*/
EmailAuthenticationToken emailAuthenticationToken = new EmailAuthenticationToken(email,password);
//返回构造的未验证token
return this.getAuthenticationManager().authenticate(emailAuthenticationToken);
}
}
AuthenticationProvider
//接收指定token进行认证处理
public class EmailAuthenticationProvider implements AuthenticationProvider, ProviderStrategy {
@Resource
private LoginService loginService;
@Resource
private BCryptPasswordEncoder passwordEncoder;
@Override
@Deprecated
public Authentication authenticate(HttpServletRequest request) {
//这个方法好像没用,filter拦截已经创建了token,
//通过请求创建一个未通过验证token
String email = request.getParameter("email");
String password = request.getParameter("password");
return new EmailAuthenticationToken(email, password);
}
/**
* 验证传入的authentication 认证通过返回认证对象,失败返回null
*
* @param authentication the authentication request object.
* @return 校验authentication,如果正确则返回校验后的token,错误则抛出异常。
* @throws AuthenticationException
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
EmailAuthenticationToken emailToken = (EmailAuthenticationToken) authentication;
// 检查对象是否为空
if (emailToken == null || StringUtils.isEmpty(emailToken.getPrincipal().toString()) || StringUtils.isEmpty(emailToken.getCredentials().toString())) {
return null;
}
User user = loginService.getUserByEmail(emailToken.getPrincipal().toString());
// 如果获取的用户不为空那么进行密码校验
if (user!=null){
// 使用密码管理器进行比较,如果为false,或者抛出密码错误异常
if (!passwordEncoder.matches(emailToken.getCredentials().toString(),user.getPassWord())){
throw new UserException(ResultCodeEnum.PASSWORD_ERROR);
}
//TODO 权限目前没有设置所有 为null
//构造一个认证后的token返回给manager,交由给successHandler进行处理
return new EmailAuthenticationToken(user.getUserEmail(),user.getPassWord(),null);
}else{
throw new UserException(ResultCodeEnum.ACCOUNT_EMAIL_ERROR);
}
}
@Override
public boolean supports(Class<?> authentication) {
//Manager传递token给provider,调用本方法判断该provider是否支持该token。不支持则尝试下一个provider
//本类支持的token类:EmailAuthenticationToken
return (EmailAuthenticationToken.class.isAssignableFrom(authentication));
}
}
整合redis
配置文件
spring:
redis:
database: 1
# Redis 服务器地址
host: 127.0.0.1
# Redis 服务器连接端口
port: 6379
#连接池最大连接数《使用负值表示没有限制),默认为8
jedis:
pool:
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制),默认为-1
max-wait: -1
# 连接池中的最大空闲连接,默认为10
max-idle: 10
# 连接池中的最小空闲连接,默认为2
min-idle: 2
# 超时时间
timeout: 6000
使用demo
RedisTemplate类提供了一系列方法,例如:
opsForValue():返回一个用于操作字符串类型的ValueOperations对象。opsForHash():返回一个用于操作哈希表类型的HashOperations对象。opsForList():返回一个用于操作列表类型的ListOperations对象。opsForSet():返回一个用于操作集合类型的SetOperations对象。opsForZSet():返回一个用于操作有序集合类型的ZSetOperations对象。
ValueOperations类是RedisTemplate类下的一个接口,用于操作Redis中的字符串类型数据。它提供了一系列方法,例如:
set(key, value):设置指定键的值。get(key):获取指定键的值。increment(key, delta):将键的值增加给定的delta值。decrement(key, delta):将键的值减去给定的delta值。append(key, value):将给定值追加到键的值后面。size(key):获取键的值的长度。
@SpringBootTest
class Demo1ApplicationTests {
@Resource
private RedisTemplate redisTemplate;
@Test
public void testString() {
//调用set()方法创建缓存
Student student1 = new Student(1,"张三","男","一年级","计算机科学与技术");
// 通过redisTemplate 获取到ValueOperations对象,进行存储
ValueOperations<String,Student> valueOperations = redisTemplate.opsForValue();
valueOperations.set("user:ysxq", student1);
// 调用get()方法获取数据
Student student2 = valueOperations.get("user:ysxq");
System.out.println(student2);
}
}
Redis 5 种数据类型操作
字符串
@GetMapping("/string")
public String stringTest(){
redisTemplate.opsForValue().set("str","Hello World");
String str = (String) redisTemplate.opsForValue().get("str");
return str;
}
列表
@GetMapping("/list")
public List<String> listTest(){
ListOperations<String,String> listOperations = redisTemplate.opsForList();
listOperations.leftPush("list","Hello");
listOperations.leftPush("list","World");
listOperations.leftPush("list","Java");
List<String> list = listOperations.range("list",0,2);
return list;
}
集合
@GetMapping("/set")
public Set<String> setTest(){
SetOperations<String,String> setOperations = redisTemplate.opsForSet();
setOperations.add("set","Hello");
setOperations.add("set","Hello");
setOperations.add("set","World");
setOperations.add("set","World");
setOperations.add("set","Java");
setOperations.add("set","Java");
Set<String> set = setOperations.members("set");
return set;
}
有序集合
@GetMapping("/zset")
public Set<String> zsetTest(){
ZSetOperations<String,String> zSetOperations = redisTemplate.opsForZSet();
zSetOperations.add("zset","Hello",1);
zSetOperations.add("zset","World",2);
zSetOperations.add("zset","Java",3);
Set<String> set = zSetOperations.range("zset",0,2);
return set;
}
哈希
HashMap key value
HashOperations key hashkey value
key 是每一组数据的 ID,hashkey 和 value 是一组完整的 HashMap 数据,通过 key 来区分不同的 HashMap。
HashMap hashMap1 = new HashMap();
hashMap1.put(key1,value1);
HashMap hashMap2 = new HashMap();
hashMap2.put(key2,value2);
HashMap hashMap3 = new HashMap();
hashMap3.put(key3,value3);
HashOperations<String,String,String> hashOperations = redisTemplate.opsForHash();
hashOperations.put(hashMap1,key1,value1);
hashOperations.put(hashMap2,key2,value2);
hashOperations.put(hashMap3,key3,value3);
解决的问题
springboot-test注入对象空指针
先检查test的目录层级,是否在顶层包。
然后检查是否添加注解
如果只有@springBootTest无法解决则添加
@RunWith(SpringJUnit4ClassRunner.class)
security配置多个provider出现找不到处理token的provider
配置多个provider 出现 No AuthenticationProvider found for edu.mkit.mkmusic.system.security.token.EmailAuthenticationToken异常,
原因是在protected void configure(AuthenticationManagerBuilder auth)方法中使用了父方法super.configure(auth);