1、概览 本文通过一个示例来带你了解如何处理 Spring Security Resource Server 产生的 Spring Security 异常。
2、Spring Security Spring Security 是 Spring 的一个子项目。它试图将 Spring 项目中的所有用户访问控制功能进行整合。访问控制允许限制特定用户或角色在应用中可以执行的选项。
在本例中,我们重点关注 Exception Handler 的配置。Spring Security 提供了三种不同的接口来实现这一目的并控制产生的事件:
Authentication Success(认证成功)Handler Authentication Failure(认证失败)Handler Access Denied(拒绝访问)Handler 3、Security Configuration 首先,配置类必须创建一个 SecurityFilterChain Bean。它将负责管理应用的所有安全配置。因此,我们必须在这里引入 Handler。
定义所需的配置:
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf() .disable() .httpBasic() .disable() .authorizeRequests() .antMatchers("/login") .permitAll() .antMatchers("/customError") .permitAll() .antMatchers("/access-denied") .permitAll() .antMatchers("/secured") .hasRole("ADMIN") .anyRequest() .authenticated() .and() .formLogin() .failureHandler(authenticationFailureHandler()) .successHandler(authenticationSuccessHandler()) .and() .exceptionHandling() .accessDeniedHandler(accessDeniedHandler()) .and() .logout(); return http.
1、概览 本文将带你了解如何在 Spring Boot 中处理 GraphQL 异常,以及 GraphQL 规范中关于错误响应的规定。
2、根据 GraphQL 规范进行响应 根据 GraphQL 规范,每个接收到的请求都必须返回一个格式良好的响应。这个格式良好的响应是一个 Map,包含了来自相应成功或失败的请求操作的数据或错误。此外,响应可能包含部分成功的结果数据和字段错误。
响应 Map 的关键 key 是 errors、data 和 extensions。
响应中的 errors 描述了请求操作过程中出现的任何异常、错误。如果没有错误发生,则响应中不得出现 errors 字段。接下来,我们将了解规范中描述的不同类型的错误。
data 字段描述了成功执行所请求操作的结果。如果操作是 Query,该字段就是 Query Root Operation 类型的对象。如果操作是 Mutation,则该字段是 Mutation Root Operation 类型的对象。
如果请求的操作在执行前就因信息缺失、验证错误或语法错误而失败,那么响应中就必须没有 data 字段。如果操作在执行过程中失败,且结果不成功,则 data 必须为 null。
响应 map 可能包含一个名为 extensions 的附加字段,它是一个 map 对象。该字段便于实现者在响应中提供他们认为合适的其他自定义内容。因此,对其内容格式没有额外的限制。
如果响应中没有 data 字段,则必须有 errors 字段,并且必须至少包含一个 error。此外,它还应说明失败的原因。
下面是一个 GraphQL 错误示例:
mutation { addVehicle(vin: "NDXT155NDFTV59834", year: 2021, make: "Toyota", model: "Camry", trim: "XLE", location: {zipcode: "75024", city: "Dallas", state: "TX"}) { vin year make model trim } } 当违反唯一性约束时,错误响应如下所示:
1、概览 本文将带你了解如何在 Spring 中获取到所有带自定义注解的 Bean。
不同的 Spring 版本,实现方式也不同。
2、使用 Spring Boot 2.2 或更高版本 从 Spring Boot 2.2 起,可以使用 getBeansWithAnnotation 方法。
创建一个示例。首先,定义自定义注解。注意,要使用 @Retention(RetentionPolicy.RUNTIME) 元注解,以确保程序在运行时可以访问注解:
@Retention(RetentionPolicy.RUNTIME) public @interface MyCustomAnnotation { } 现在,用注解定义第一个 Bean(还要添加 @Component 注解):
@Component @MyCustomAnnotation public class MyComponent { } 然后,定义另一个带有注解的 Bean。不过,这次通过 @Configuration 中的 @Bean 方法来创建:
public class MyService { } @Configuration public class MyConfigurationBean { @Bean @MyCustomAnnotation MyService myService() { return new MyService(); } } 现在,写一个测试,测试 getBeansWithAnnotation 方法能否获取到上述两个 Bean:
1、简介 RestClient 是 Spring 6.1 M2 中引入的同步 HTTP 客户端,它取代了 RestTemplate。同步 HTTP 客户端以阻塞方式发送和接收 HTTP 请求和响应,这意味着它会等待每个请求完成后才继续下一个请求。
本文将带你了解 RestClient 的功能以及它与 RestTemplate 的比较。
2、RestClient 和 RestTemplate RestTemplate,顾名思义,是基于模板设计模式构建的。它是一种行为设计模式,用于在方法中定义算法的框架,允许子类为某些步骤提供特定的实现。虽然这是一种强大的模式,但它会导致需要进行方法覆写,这可能是不方便的。
为了改进这一点,RestClient 采用了 Fluent API。Fluent API 也是一种设计模式,它允许以一种使代码更易读和表达的方式进行方法链式调用,通常无需使用中间变量。
从创建一个基本的 RestClient 开始:
RestClient restClient = RestClient.create(); 3、使用各种 HTTP 方法发起请求 与 RestTemplate 或其他 REST 客户端类似,RestClient 可以使用不同的 HTTP 方法发起请求。
创建一个用于操作的 Article 类:
public class Article { Integer id; String title; // 构造函数和 getter/setter } 3.1、使用 GET 请求获取资源 GET 请求用于检索 Web 服务器上指定资源的数据,而不对其进行修改。通常,这是一个只读的操作!
获取服务器响应的字符串(String):
对于文件的下载功能来说,我归纳为 2 头 1 流。极其简单。
Content-Type 头,告诉客户端文件类型。 Content-Disposition 头,告诉客户端对于这个文件的处理方式(在浏览器中显示,还是下载)。 Output 流,写入文件内容到客户端。 文件名称乱码的问题 Content-Disposition 头有三个属性值,可以指定文件的名称。
name filename filename* 至于区别,我也说不大清楚。反正是有个规范,我感觉很乱。具体可以参考如下链接:
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Disposition
这就是容易出问题的地方了,由于文件名称设置方法不正确。或者是浏览器不兼容问题,如果 文件名称包含中文,可能会导致 下载文件名称乱码:
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + "中文の文件.txt"); 如上,直接用 + 拼接中文字符串,在谷歌浏览器中下载就是乱码。
常见的解决方案 以下我是在网上看到的一些解决方案。
对文件名称进行URI编码
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + URLEncoder.encode("中文の文件.txt", StandardCharsets.UTF_8)); 修改字符编码
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + new String("中文の文件.txt".getBytes("GBK"),"ISO-8859-1")); 经过测试,上面2种办法都OK。
比较优雅的方式 Spring 提供了一个工具类: org.springframework.http.ContentDisposition,专门用于处理文件下载中的 Content-Disposition Header,它会自动处理好中文文件名称的编码问题。
ContentDisposition 使用实在是太简单了,这里就不多说了,给一个完整的使用示例就能看明白。
package io.springboot.demo.web.controller; import java.io.IOException; import java.nio.charset.StandardCharsets; import javax.servlet.http.HttpServletResponse; import org.springframework.http.ContentDisposition; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.GetMapping; import org.
1、简介 在容器化大行其道的今天,越来越多人选择把 Spring Boot 应用部署到 Docker 中。
本文将带你了解在 Docker 容器中启动 Spring Boot 应用时,如何设置其 Profile。
2、基本的 Dockerfile 一般来说,要容器化 Spring Boot 应用,只需提供一个 Dockerfile。
Spring Boot 应用的最小 Dockerfile,如下:
FROM openjdk:11 COPY target/*.jar app.jar ENTRYPOINT ["java", "-jar", "/app.jar"] 然后,可以通过 docker build 来构建 Docker 镜像:
docker build --tag=docker-with-spring-profile:latest . 接着,我们就可以通过 docker-with-spring-profile 镜像运行应用了:
docker run docker-with-spring-profile:latest 你可以注意到,Spring Boot 应用以 “default” Profile 启动:
2022-04-22 22:34:25.268 INFO 1 --- [main] c.b.docker.spring.DemoApplication: Starting DemoApplication using Java 11.0.14.1 on ea8851bea75f with PID 1 (/app.
简介 本文将带你了解,当无法依赖 CascadeType 机制来将状态转换从父实体传播到子实体时,如何使用 Spring Data JPA 级联删除单向关联,
Domain Model 假设我们的系统中有以下实体:
Post 实体是该实体层次结构的根(root),子实体只使用单向 @ManyToOne 或 @OneToOne 关联,用于映射一个一对多或一对一表关系的底层外键。
Post root 实体如下:
@Entity @Table(name = "post") public class Post { @Id private Long id; private String title; // getter/setter 方法省略 } 请注意,没有双向的 @OneToMany 或 @OneToOne 关联,可以允许我们从父级 Post 级联 DELETE 操作到 PostComment、PostDetails 或 PostTag 子实体。
PostComment 实体使用单向 @ManyToOne 关联映射 post_id 外键列:
@Entity @Table(name = "post_comment") public class PostComment { @Id @GeneratedValue private Long id; @ManyToOne(fetch = FetchType.
1、概览 Spring Security 有 2 个核心的组件:HttpSecurity 和 WebSecurity。
2、Spring Security Spring Security 是 Spring Framework 的扩展,是一个功能强大、高度可定制的安全框架。它为基于 Jakarta® EE 的企业软件应用提供全面的安全服务。它旨在处理身份验证、授权、防止常见安全漏洞等问题。
Spring Security 与两个重要组件 HttpSecurity 和 WebSecurity 紧密集成。
HttpSecurity 主要负责配置 HTTP 请求的安全,而 WebSecurity 则侧重于配置基于 Web 的安全。
Spring Security 的核心依赖于 AuthenticationManager、UserDetailsService 和 UserDetails 等基本组件。这些组件构成了 Spring Security 的基础。通过它们,我们可以实现身份认证、管理用户详情并有效定义访问控制。
3、HttpSecurity HttpSecurity 在配置 HTTP 请求的授权方面起到核心作用。它允许我们基于各种条件定义访问规则,包括 URL、HTTP 方法和用户角色,以确保对应用程序的不同部分进行安全访问。
链式方法调用是 HttpSecurity 的一个强大特性,它使我们能够流畅地表达安全配置。通过方法链,我们可以定义特定端点的安全行为,处理身份认证机制,并无缝设置自定义访问规则。这种方式让安全配置更加灵活和易于管理。
HttpSecurity 的一个关键特性是基于角色和权限进行访问配置。我们可以指定访问特定URL 所需的角色或权限。因此,我们可以对应用程序的安全性进行细粒度控制。
HttpSecurity 还可以实现平滑处理登录和注销功能。因此,我们可以配置自定义的登录页面、认证成功和失败的处理方式。它还允许我们实现自定义的注销行为,提升用户体验和安全性。
在使用 HttpSecurity 配置授权时,重要的是要解决安全问题。这些包括防止未经授权的访问、确保安全的身份认证机制、防止会话固定攻击,并防范常见的Web漏洞。
使用 HttpSecurity 来配置授权。
@Configuration @EnableWebSecurity public class HttpSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.
1、概览 本文将带你了解 Spring Boot 3.1 中引入的 ConnectionDetails 接口,用于编程式定义连接属性(可从外部服务中加载)。Spring Boot 提供了开箱即用的抽象,用于与远程服务集成,如关系型数据库、NoSQL 数据库、消息队列服务等
传统上,连接信息都是配置在 application.properties 文件中的。因此,如果需要从 AWS Secret Manager、Hashicorp Vault 等外部服务加载连接配置的话就比较麻烦。
为了解决这个问题,Spring Boot 引入了 ConnectionDetails。这个接口是空的,即标记接口。Spring 提供了该接口的子接口,如 JdbcConnectionDetails、CassandraConnectionDetails、KafkaConnectionDetails 等。它们可以在 Spring 配置类中实现和指定为 Bean。之后,Spring 将依赖这些配置 Bean 来动态检索连接属性,而不是静态的 application.properties 文件。
2、场景 把敏感的配置信息(如数据库)定义在 application.properties 文件中存在一定的安全隐患。最好的方式是使用专业的私密信息存储服务。
HashiCorp Vault 是一款企业级私密信息管理工具,用于安全地存储和访问敏感数据,例如API密钥、数据库凭据、密码等。支持多种认证方法和加密机制,并提供 API 和命令行界面,以便与应用程序和工具集成。还支持动态秘密生成、密钥轮换和审计日志等功能,使数据的管理和安全性更加便捷和可靠。
Spring Boot 从 Vault 中读取敏感信息的流程如下:
Spring Boot 通过 Secret Key 调用 Vault 服务来获取连接配置信息。然后,使用该配置和远程服务创建连接。
4、ConnectionDetails 通过 ConnectionDetails 接口,Spring Boot 应用可以自行发现连接细节(Connection Detail),而无需任何手动干预。注意,ConnectionDetails 优先于 application.properties 文件。不过,仍有一些非连接属性(如 JDBC 连接池大小)可以通过 application.properties 文件进行配置。
在前文 《在 Spring Boot 中通过 RequestBodyAdvice 统一解码请求体》 中,我们介绍了如何通过 RequestBodyAdvice 组件统一地解码客户端编码后的请求体。
有请求,就有响应,本文将会带你了解如何使用 ResponseBodyAdvice 来统一对响应体进行编码。
这种场景比较常见,常用于 APP 客户端和服务器之间的通信。为了避免中间人通过抓包直接获取到服务器的响应数据,所以会对数据进行加密(AES、3DES、RSA 等等)。
客户端获取到加密数据后,使用本地存储的密钥进行解密从而得到原始数据。由于密钥没有经过网络传输,意味着加密数据不会轻易被人破解。当然,密钥由于存储在客户端,需要通过混淆等手段保证它不会被轻易的破解、获取(这种安全话题超出了本文的范畴)!
ResponseBodyAdvice ResponseBodyAdvice spring mvc 提供的一个增强接口,用于在返回对象被 HttpMessageConverter 执行序列化之前对其进行一些自定义的操作。
public interface ResponseBodyAdvice <T> { boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType); @Nullable T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response); } 这是一个带泛型 <T> 的接口,T 表示返回的对象类型,这决定了它会对哪些返回的对象生效。另外,它比 RequestBodyAdvice 简单,它只有 2 个方法。
supports:用于确定该实现类是否支持对响应体进行处理,通过 returnType 参数可以获取到 Controller 方法的返回类型等信息。 beforeBodyWrite:该方法在响应体写入之前被调用,在这个方法中可以通过参数获取到最终要响应给客户端的对象,我们可以对这个对象进行一些操作,最后返回修改后的对象。 接下来我们通过一个示例来了解如何使用 ResponseBodyAdvice。