最基础的Spring Security
首先登陆网站start.spring.io,添加web,devtool,security依赖
编写完controller代码后进入网址会发现需要填表单,用户默认是user,密码在后端的控制台.springsecurity会有很多过滤器,如图中的f1,f2等request经过filter进入front controller(这个是在controller之前的控制器),且注意这里的后端都是在Servlet Container(即Tomcat)中运行的,关于Servlet:
Servlet是Java EE(Java Platform, Enterprise Edition)规范的一部分,它是一种运行在服务器端的Java程序,用于处理客户端(通常是网页浏览器)的请求,并生成动态的网页内容。Servlet可以响应多种类型的请求,包括GET、POST等HTTP方法。
Servlet的主要特点包括:
1. **请求处理**:Servlet能够接收和处理来自客户端的请求,这些请求可以包含各种类型的数据,如表单数据、文件上传等。
2. **响应生成**:Servlet可以根据请求的内容生成响应,这些响应可以是HTML页面、JSON数据、XML文档等。
3. **会话管理**:Servlet可以管理用户会话,跟踪用户的交互状态。
4. **线程安全**:Servlet是多线程的,可以同时处理多个请求,因此Servlet需要设计为线程安全的。
5. **可配置**:Servlet可以通过web.xml文件或注解进行配置,指定其URL映射、初始化参数等。
6. **可扩展**:Servlet可以与其他Java EE组件(如EJB、JSP)集成,构建复杂的企业级应用。
Servlet的生命周期包括以下几个阶段:
- **加载和实例化**:当Servlet容器(如Tomcat)启动或第一次接收到请求时,它会加载Servlet类并创建其实例。
- **初始化**:Servlet容器会调用`init()`方法来初始化Servlet实例,这个方法只会在Servlet生命周期中被调用一次。
- **请求处理**:对于每个请求,Servlet容器会创建一个新的`HttpServletRequest`对象和`HttpServletResponse`对象,并调用Servlet的`service()`方法来处理请求。`service()`方法会根据请求的类型(GET、POST等)来决定调用`doGet()`或`doPost()`等方法。
- **销毁**:当Servlet容器决定卸载Servlet时(例如,应用被停止或重新部署),它会调用`destroy()`方法来释放Servlet占用的资源。
Servlet是构建Java Web应用的基础组件之一,它提供了一种灵活的方式来处理HTTP请求和生成响应。随着技术的发展,虽然出现了如Spring MVC等更高级的Web框架,但Servlet仍然是Java Web开发中不可或缺的一部分。
并且过滤器也可以过滤response(response也是另一种形式上的request)
继续上面的,如果你在一个浏览器上输入了user和对应的密码,怎么刷新都不会弹出登录表单了,因为这都是一个会话(session),点F12,点开network,上面的cookie中包含session_id,如果在网址后面加上logout,则会退出,这时候再看session_id,应该不一样了。下面的两张图是说我可以获取这个session_id,图1的HttpServletRequest request可以获取从浏览器发送来的请求通过下图中的代码,可以修改我的用户名和密码
如果这时候使用postman,必须在Authorization中的Basic Auth中输入正确的用户名和密码才能返回数据
CSRF
为什么需要CSRF?首先,在上一个例子中,我的session_id会在我的客户端一直保留,直到退出登录,如果我点进去了一个恶意网站时,这个网站会获取我的session_id,然后这个网站就能冒充我去登录那些本来登不上去的网站了,如下图当使用get方法时,不需要csrf token,但是当使用post,delete,put等时,就需要传递csrf token了,如果不传,就如下图,这是一个加一个student的post方法,虽然传过去了username和password,但是由于没有csrf token,所以返回401
下图中的Value是直接从浏览器中的logout界面中的源代码获得的,但奇怪的是,这个还是返回401
所以我使用下面的方法获得csrf令牌,由于返回的是Object类型,所以需要类型转换为CsrfToken
之后在postman获取csrf_token
再放入添加student的header里面
成功了!
但是如果为了确保安全,可以禁用它
进阶Spring Security--自己配置
在Spring框架中,`@Configuration`和`@EnableWebSecurity`是两个常用的注解,它们在Spring Security配置中扮演着重要的角色。下面是对这两个注解的解释:
1. `@Configuration`:
- 这个注解用于标记一个类,表明它是一个配置类。在Spring框架中,配置类允许你以编程方式定义Bean和它们的依赖关系,而不是使用XML配置文件。
- 当一个类被标记为`@Configuration`时,Spring容器会扫描这个类中的方法,并为每个被`@Bean`注解标记的方法创建一个Bean实例,这些Bean实例会被添加到Spring应用程序上下文中。
- `@Configuration`类通常用于定义应用程序的基础设施,比如数据源、消息代理、事务管理器等。
2. `@EnableWebSecurity`:(实际上就是声明我自己要实现springsecurity了)
- 这个注解用于启用Spring Security的Web安全支持。当你在一个配置类上使用`@EnableWebSecurity`注解时,Spring Security会自动配置一些基本的安全特性,比如HTTP安全头、CSRF保护等。
- `@EnableWebSecurity`注解告诉Spring Security启动Web安全支持,并开始处理HTTP请求的安全检查。
- 通常,你会在`@Configuration`注解的类中使用`@EnableWebSecurity`,这样你就可以在同一个类中定义安全配置,比如认证管理器、授权规则等。
在你提供的代码片段中,`SecurityConfig`类被标记为`@Configuration`和`@EnableWebSecurity`,这意味着这个类是一个配置类,用于定义应用程序的安全配置。在这个类中,你可以添加更多的方法来配置Spring Security的各种安全特性,比如用户认证、角色授权、密码加密等。
如果我自己需要配置springsecurity,首先建一个config文件夹,然后在里面加上一个SecurityConfig,如图,如果先把@EnableWebSecurity和@Bean去掉,则进入网址后还是有登录表单,但是如果加上了这两个@,则就不会出现登录表单了,这是因为我们自己实现了springsecurity,但是还没有实现具体的过滤,所以任何人都可访问
但是当我配置了这个就会出现下面的情况,即所有请求都需要验证,且禁止跨域请求
但是如果加上formlogin,如下图,就又会出现表单登录,然后输入properties设置的username
但是此时如果用postman发送请求时,如图,就会返回一个登录表单界面的html如果加上一行代码
postman就能顺利返回响应了,如下图
下面是关于两个api的区别:
在Spring Security中,`http.formLogin()`和`http.httpBasic()`是两种不同的认证方式,它们的行为和返回的响应会有所不同。让我们来分析一下这两种配置的区别:
### 图1的代码:使用`http.formLogin(Customizer.withDefaults())`
```java
http.formLogin(Customizer.withDefaults());
```
- `http.formLogin()`:配置表单登录。这意味着Spring Security会提供一个登录表单,用户需要填写用户名和密码进行认证。
- `Customizer.withDefaults()`:使用默认的表单登录配置。
当你使用Postman发送请求时,如果没有提供认证信息,Spring Security会拦截请求并返回一个302状态码,重定向到登录页面。如果你在Postman中设置了自动跟随重定向,你可能会看到登录表单。但是,如果你查看响应的状态码,它应该是302(重定向),而不是200。如果状态码是200,可能是因为Postman没有正确显示重定向后的响应。
### 图2的代码:使用`http.httpBasic(Customizer.withDefaults())`
```java
http.httpBasic(Customizer.withDefaults());
```
- `http.httpBasic()`:配置HTTP基本认证。这种方式要求客户端在请求头中提供`Authorization`字段,其中包含Base64编码的用户名和密码。
- `Customizer.withDefaults()`:使用默认的基本认证配置。
当你使用Postman发送请求时,如果你在请求头中正确设置了`Authorization`字段,Spring Security会验证提供的凭据。如果凭据有效,请求将被允许通过,你将看到相应的内容。如果凭据无效,你将收到一个401状态码(未授权)。
### 总结
- **表单登录**(图1):通常会导致重定向到登录页面,状态码为302。
- **基本认证**(图2):如果凭据正确,请求通过,状态码为200;如果凭据错误,状态码为401。
如果你在使用图1的代码时收到了状态码200,可能是因为Postman没有正确处理重定向,或者你已经在会话中认证过,所以不需要再次登录。如果你在使用图2的代码时收到了相应的内容,说明你的认证凭据是有效的。
为了确保测试的准确性,你可以在Postman中禁用自动跟随重定向,并检查响应头中的`Location`字段来确认是否有重定向发生。同时,确保在测试基本认证时正确设置了`Authorization`字段。
下面加了一行代码,用于不维护每个会话,所以每次在浏览器上在登陆表单上输入正确的username,password后都不会进入,因为是刷新后又是新会话,而新会话又要重新登录,但是如果在postman上面,就能返回响应了,但如果非要在浏览器上像登录进去,就要把代码中的formlogin那行代码删去,如下下图
下面是到目前为止的一些操作
这段代码是Spring Security配置的一部分,用于定义安全过滤器链(Security Filter Chain)。安全过滤器链是Spring Security用来处理HTTP请求的安全检查的一系列过滤器。下面是对代码的逐行解释:
```java
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
```
- `@Bean`:这个注解标记了一个方法,告诉Spring框架这个方法返回的对象应该被注册为Spring应用程序上下文中的一个Bean。
- `public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception`:这是一个方法声明,返回类型是`SecurityFilterChain`,它代表一个安全过滤器链。`HttpSecurity`是一个用于配置HTTP安全设置的DSL(领域特定语言)。方法可能抛出`Exception`。
```java
http.csrf(customizer -> customizer.disable());
```
- `http.csrf(customizer -> customizer.disable());`:这行代码配置了CSRF(跨站请求伪造)保护。`csrf()`方法返回一个`CsrfConfigurer`对象,允许你配置CSRF保护的设置。通过传递一个lambda表达式给`customizer`,调用`disable()`方法来禁用CSRF保护。
```java
http.authorizeHttpRequests(request -> request.anyRequest().authenticated());
```
- `http.authorizeHttpRequests(request -> request.anyRequest().authenticated());`:这行代码配置了HTTP请求的授权规则。`authorizeHttpRequests()`方法接受一个lambda表达式,用于定义哪些请求需要被授权。`anyRequest()`表示所有请求,`authenticated()`表示所有请求都必须由经过认证的用户发起。
```java
http.formLogin(Customizer.withDefaults());
```
- `http.formLogin(Customizer.withDefaults());`:这行代码配置了表单登录。`formLogin()`方法返回一个`FormLoginConfigurer`对象,允许你配置表单登录的设置。`Customizer.withDefaults()`使用默认的表单登录配置。
```java
http.httpBasic(Customizer.withDefaults());
```
- `http.httpBasic(Customizer.withDefaults());`:这行代码配置了HTTP基本认证。`httpBasic()`方法返回一个`HttpBasicConfigurer`对象,允许你配置基本认证的设置。`Customizer.withDefaults()`使用默认的基本认证配置。
```java
http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
```
- `http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));`:这行代码配置了会话管理。`sessionManagement()`方法返回一个`SessionManagementConfigurer`对象,允许你配置会话管理的设置。这里设置会话创建策略为`STATELESS`,意味着应用程序不会创建、管理和维护会话。
```java
return http.build();
}
```
- `return http.build();`:这行代码构建并返回配置好的`SecurityFilterChain`实例。`build()`方法将所有配置应用到`SecurityFilterChain`上,并返回最终的配置对象。
总的来说,这段代码定义了一个安全过滤器链,其中禁用了CSRF保护,并要求所有请求都必须由经过认证的用户发起。同时,它配置了表单登录和基本认证,并将会话管理设置为无状态。这种配置通常用于开发或测试环境,因为它允许所有请求通过,但要求用户必须经过认证。在生产环境中,你可能需要更精细的控制,比如指定哪些路径需要认证,哪些路径可以公开访问等。
如果不用lambda表达式,就第一个跨域请求都要写很长,如图
上述代码可以简化成下面的代码
之后如果再用postman请求/student,只需要传入username,password即可请求到相关信息,不用传csrf_token,因为我在第一行已经禁用了这个
考虑从数据库获取username和password而不是在我的application.properties中获取username和password
(即不是硬编码了)
我们先在securityconfig类中添加一个@bean,代表返回的用户基本信息,如图之后用postman发送get请求,仍然使用原来的username和password就会失败,返回401,因为我们自己定义了用户详细的信息,下面是UserDetailsService的详细解释
这段代码是Spring Security配置的一部分,用于定义一个`UserDetailsService`的Bean。`UserDetailsService`是一个Spring Security接口,用于根据用户名加载用户特定的数据。下面是对代码的逐行解释:
```java
@Bean
public UserDetailsService userDetailsService(){
return new InMemoryUserDetailsManager();
}
```
- `@Bean`:这个注解标记了一个方法,告诉Spring框架这个方法返回的对象应该被注册为Spring应用程序上下文中的一个Bean。
- `public UserDetailsService userDetailsService(){`:这是一个方法声明,返回类型是`UserDetailsService`。这个方法没有参数。
- `return new InMemoryUserDetailsManager();`:这行代码创建并返回一个新的`InMemoryUserDetailsManager`实例。`InMemoryUserDetailsManager`是一个简单的实现,用于在内存中管理用户信息,通常用于测试或开发环境。
- `}`:方法结束。
在这个配置中,`InMemoryUserDetailsManager`被用作`UserDetailsService`的实现。这意味着Spring Security将使用这个管理器来查找和验证用户。`InMemoryUserDetailsManager`通常用于开发和测试,因为它不需要连接到数据库或其他外部用户存储。
当你在Spring Security中配置表单登录时,如果没有指定`UserDetailsService`,Spring Security会尝试自动配置一个默认的实现。通过显式定义这个Bean,你可以控制用户信息的来源,这对于定制用户认证过程非常重要。在生产环境中,你通常会使用连接到数据库的实现,如`JdbcUserDetailsManager`,以便从数据库中检索用户信息。
之后如果改成了下面的代码,在postman请求时,加入的Authorization中的basic auth信息就需要是下面的信息了,注意:在这里的User引入的是java包中的User,而不是我们自己实现的User,包括我们之后如果要用到自己实现的User时,也要检查有没有导入成java自带的User了
如下图,我便可以获得这个响应了
但实际上这个还是硬编码,我们如何将这个用户信息和数据库联系起来呢?
首先下面的图是认证的过程
此时我不想用默认的Authentication Provider,想用我自己写的Authentication Provider,并且这个Provider能够连接数据库,如下图之后我们就重写了Provider,如下图,并且@Autowired了一个UserDetailsService,但是这个UserDetailsService需要我们自己单独创建一个类,并且需要将这个UserDetailService和数据库连接起来(注意这里面的securityFilterChain变了,其实实际上还是跟上面的一样,只不过这个是最终的版本,所以这个securityFilterChain跟原来不一样了,securityFilterChain的具体代码看前面)
之后我们就创建了MyUserDetailsService,这个类实现了UserDetailsService接口,刚好对应与上面的@Autowired userdetailsservice,首先需要重写里面的抽象,如图
首先实现model中的user类和repo层的代码(在repo层使用JPA),如图1,之后需要连接数据库并创建表,这里使用的是pgAdmin4,创建表的一些参数,这些参数和model层的class的一些属性要一一对应,如图
图1中:
在Java中,第6行和第8行的代码使用了JPA(Java Persistence API)的注解,这些注解用于定义实体类的特性,以便与数据库进行交互。下面是对这两行代码的解释:
```java
@Entity
```
- `@Entity`:这是一个JPA注解,用于标记一个类为实体(Entity)。实体是数据库表的映射。当你将`@Entity`注解添加到一个类上时,JPA提供者(如Hibernate)会将这个类映射到数据库中的一个表。这个注解通常位于类定义的第一行。
```java
@Id
```
- `@Id`:这是另一个JPA注解,用于标记类中的一个属性作为实体的主键(Primary Key)。主键是数据库表中用于唯一标识每条记录的字段。在`Users`类中,`id`属性被标记为主键,这意味着数据库表中会有一列作为主键,通常是自增的整数或UUID。
总结:
- 第6行的`@Entity`注解告诉JPA,`Users`类是一个实体,应该映射到数据库中的一个表。
- 第8行的`@Id`注解告诉JPA,`Users`类中的`id`属性是这个实体的主键,用于在数据库表中唯一标识每个用户记录。
之后配置数据库连接,如下图
现在我们需要去实现MyUserDetailsService中的load方法了,如图,这里对于上图有一个疑问:就是为什么UserDetails可以作为类型出现在上图
解答:
在Spring Security中,`UserDetails`是一个接口,它定义了用户信息的模型,包括用户名、密码、权限等。这个接口是用于认证和授权的核心部分,因为它提供了用户信息的标准化表示。
在第19行代码中:
```java
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
```
- `UserDetails`:这里`UserDetails`是作为返回类型使用的。这意味着`loadUserByUsername`方法应该返回一个实现了`UserDetails`接口的对象。这个方法是`UserDetailsService`接口的一部分,它要求实现者提供一个根据用户名加载用户详细信息的方法。
- `loadUserByUsername`:这是`UserDetailsService`接口中定义的方法,它接受一个用户名作为参数,并返回一个`UserDetails`对象。如果找不到用户,方法应该抛出`UsernameNotFoundException`异常。
在Spring Security的上下文中,`UserDetails`接口通常由一个实现了`UserDetailsService`接口的类来实现,这个类负责从数据源(如数据库)中检索用户信息,并将其封装成一个`UserDetails`对象返回。这个对象随后被用于Spring Security的认证过程。
所以,尽管`UserDetails`是一个接口,但在方法签名中,它被用作返回类型,表示方法应该返回一个实现了`UserDetails`接口的具体对象。这是Java接口作为类型使用的典型例子,接口可以作为方法的返回类型、参数类型或局部变量的类型。
所以返回的应该是实现了`UserDetails`接口的具体对象,所以我们需要写一个对象来实现UserDetails接口,所以我们在model层中实现了UserPrincipal,下面的图1是一开始的样子,图2是完善后的样子
之后我们就实现了从数据库中获取用户的详细信息(username password),所以我们用数据库中的相关信息登录就成功了,如下图
接下来实现密码的加密
进行到现在,有一个问题,就是数据库存储的密码都是未加密的密码,如图
从plain(普通文本)可以到hash,但是从hash不能推出plain;
比如我们获得了一些plain,然后通过一下算法比如md5,sha,sh256之后就得到了hash;
生成了hash可以在hash即plain->hash->hash
比如可以用bcrypt,如下图,每一轮都会生成新的哈希值
我到现在还没有实现注册功能,所以需要实现这个功能,并且将用户信息储存到数据库中,且密码需要用Bcrypt变成哈希值,先写一个controller和service之后重新启动一下
成功了
如何验证我加密后的密码?
但是又抛出了一个新的问题:虽然我的密码在存储的时候加密了,但是如果我要验证我的身份时,当我输入用户名输入avni,密码输入a@123时,却失败了,原因是因为我的密码现在是加密后的,那么如何验证呢?
这时候再看我的SecurityConfig中的代码,如下图,发现我的setPasswordEncoder中的参数是No0p...
所以需要改为下图
之后再用get请求,并且使用我一开始的用户信息(username:avni,password:a@123)就可以返回相关信息了
这样就完美解决了这个问题
JWT详解
jwt基本概念:
JWT(JSON Web Token)是一种常用的Web令牌格式。它是一种紧凑且自包含的方式,用于在各方之间以JSON对象的形式安全地传输信息。每个JWT都是一个经过数字签名的字符串,因此你可以验证发送者的身份并确保数据在传输过程中没有被篡改。
JWT通常用于以下场景:
1. **身份验证**:在用户登录后,服务器会生成一个JWT并发送给客户端。客户端在随后的请求中将JWT作为认证信息发送回服务器,服务器验证JWT的有效性后允许用户访问受保护的资源。
2. **信息交换**:JWT可以包含声明(claims),这些声明是关于实体(通常是用户)和其他数据的声明。由于JWT是数字签名的,接收方可以确定发送方的身份以及信息在传输过程中未被篡改。
3. **无状态和可扩展性**:由于JWT是自包含的,服务器不需要存储会话状态,这使得JWT非常适合分布式系统和微服务架构。
JWT的结构通常由三部分组成,用点(.)分隔:头部(Header)、载荷(Payload)和签名(Signature)。头部通常包含令牌的类型(即JWT)和所使用的签名算法(如HMAC SHA256或RSA)。载荷包含声明,声明是关于用户和其他元数据的键值对。最后,签名用于验证消息在传输过程中未被篡改。
JWT的一个关键特性是它们可以是自包含的,这意味着所有必要的信息都包含在令牌本身中,不需要查询数据库或其他存储来验证令牌。这使得JWT非常适合用于分布式系统和需要高性能的场景。然而,这也意味着JWT不适合存储敏感信息,因为它们可以被解码和读取。
首先为什么需要jwt,前面我们一直说的都是注册,登录功能,包括密码的加密,但是服务器怎么知道我们注册了还是登录,或者说它怎么知道我们目前的登录状态呢?一般来说通过会话,即浏览器维持一个session,session有session_id,session_id又储存在浏览器cookie中,每次浏览器请求服务器端都会发送一个session_id,然后因为服务器端也有这个session_id,所以可以知道这次会话之前有没有注册或登录。
大概的步骤就是我login之后,服务器给我一个jwt,保存在我的客户端,服务器端不存,然后每次我进网站时都会将这个jwt传给服务器,然后服务器验证这个jwt,并且返回一些响应给客户端
之后开始引入pom.xml文件,引入jjwtapi和jjwtimpl,jjwtjackson
这三个依赖都是与JSON Web Token (JWT) 相关的Java库,它们由`io.jsonwebtoken`组织提供。每个依赖都有其特定的用途:
1. **`jwt-api`**:
- **groupId**: `io.jsonwebtoken`
- **artifactId**: `jwt-api`
- **version**: `0.12.5`
- **作用**: 这个依赖提供了JWT的核心API,用于创建和验证JWT。它定义了JWT的模型和基本操作,但不包含具体的实现。这个库通常用于那些需要自定义JWT处理逻辑的场景。
2. **`jwt-impl`**:
- **groupId**: `io.jsonwebtoken`
- **artifactId**: `jwt-impl`
- **version**: `0.12.5`
- **scope**: `runtime`
- **作用**: 这个依赖包含了`jwt-api`的默认实现。它提供了处理JWT所需的具体算法和逻辑,如HMAC、RSA等。`runtime`作用域意味着这个库只在运行时需要,编译时不需要。
3. **`jwt-jackson`**:
- **groupId**: `io.jsonwebtoken`
- **artifactId**: `jwt-jackson`
- **version**: `0.12.5`
- **scope**: `runtime`
- **作用**: 这个依赖提供了使用Jackson库来序列化和反序列化JWT载荷的实现。Jackson是一个流行的Java库,用于处理JSON数据。这个依赖允许你将JWT的载荷部分作为JSON对象来处理,方便地存取和操作JWT中的声明。
总结来说,`jwt-api`定义了JWT的基本结构和操作,`jwt-impl`提供了这些操作的具体实现,而`jwt-jackson`则提供了使用Jackson来处理JWT载荷的实现。在实际应用中,你通常需要至少`jwt-api`和`jwt-impl`来处理JWT,而`jwt-jackson`则是可选的,取决于你是否需要使用Jackson来处理JSON数据。
之后首先添加login的controller并返回success即可,然后设置SecurityConfig中
并且需要实现SecurityConfig中的AuthenticationManager这个@Bean
如图
二者的关系:
在Spring Security中,`AuthenticationProvider`和`AuthenticationManager`是两个非常重要的组件,它们在认证过程中扮演着不同的角色。
### AuthenticationProvider
`AuthenticationProvider`是一个接口,它定义了认证逻辑的实现。每个`AuthenticationProvider`可以处理不同类型的认证逻辑,例如基于用户名和密码的认证、基于LDAP的认证等。在Spring Security中,你可以通过实现`AuthenticationProvider`接口来自定义认证逻辑。
在代码中,`authenticationProvider()`方法创建了一个`DaoAuthenticationProvider`实例,这是Spring Security提供的一个默认实现,用于处理基于数据库的用户认证。它使用`UserDetailsService`来获取用户信息,并使用`PasswordEncoder`来验证密码。
### AuthenticationManager
`AuthenticationManager`是负责接收认证请求并将其委派给一个或多个`AuthenticationProvider`进行处理的组件。它是一个高级接口,用于协调多个`AuthenticationProvider`的工作。当一个认证请求到来时,`AuthenticationManager`会遍历所有注册的`AuthenticationProvider`,直到找到一个能够处理该请求的`AuthenticationProvider`。
在代码中,`authenticationManager(AuthenticationConfiguration config)`方法返回了一个`AuthenticationManager`实例。这个实例通常是通过`AuthenticationConfiguration`配置类来构建的,它负责将`AuthenticationProvider`注册到`AuthenticationManager`中。
### 关系
`AuthenticationManager`和`AuthenticationProvider`之间的关系是委托关系。`AuthenticationManager`负责接收认证请求,并将这些请求委托给一个或多个`AuthenticationProvider`来处理。每个`AuthenticationProvider`负责处理特定类型的认证逻辑。
在Spring Security的配置中,通常会有一个或多个`AuthenticationProvider`被添加到`AuthenticationManager`中。当进行认证时,`AuthenticationManager`会根据认证请求的类型,将请求转发给相应的`AuthenticationProvider`进行处理。
总结来说,`AuthenticationProvider`负责具体的认证逻辑实现,而`AuthenticationManager`负责管理和协调这些认证逻辑的执行。
之后用postman发送login请求,如图
之后开始实现真正的登录服务,首先controller调用service层中的verify方法,然后我们需要实现verify方法,如下图
具体实现下面是对verify方法的具体解释:
这个`verify`方法是一个用于验证用户登录信息的方法。它接收一个`Users`类型的参数`user`,这个参数包含了用户名和密码。方法的目的是检查提供的用户名和密码是否正确,如果正确,则返回"Success";如果不正确,则返回"fail"。
下面是`verify`方法的详细解释:
1. `Authentication authentication = authManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()));`:这行代码创建了一个`UsernamePasswordAuthenticationToken`对象,它包含了用户名和密码。然后,它调用`AuthenticationManager`的`authenticate`方法来验证这个令牌。`authenticate`方法是`AuthenticationManager`接口中定义的方法,用于验证传入的认证信息。
2. `if(authentication.isAuthenticated())`:这行代码检查认证是否成功。如果认证成功,`isAuthenticated`方法会返回`true`。
3. `return "Success";`:如果认证成功,方法会返回"Success"。
4. `return "fail";`:如果认证失败,方法会返回"fail"。
所以,`verify`方法通过调用`AuthenticationManager`的`authenticate`方法来验证用户名和密码,然后根据验证结果返回"Success"或"fail"。
至于`AuthenticationManager`,它是一个Spring Security中用于处理认证的核心组件。它接收一个`Authentication`对象,然后将其传递给一个或多个`AuthenticationProvider`进行验证。如果所有的`AuthenticationProvider`都不能验证通过,那么认证就会失败。在这个方法中,`AuthenticationManager`被用来验证用户名和密码,如果验证通过,就认为用户已经通过了认证。
下面是我的一个疑问及解答:
疑问:你说authenticate方法是AuthenticationManager接口中定义的方法,用于验证传入的认证信息。那么它是怎么知道具体是按什么去验证的
回答:
`AuthenticationManager` 接口在 Spring Security 中是一个核心接口,它定义了 `authenticate` 方法,该方法接受一个 `Authentication` 对象作为参数,并返回一个 `Authentication` 对象,如果认证成功,返回的对象会标记为已认证(`isAuthenticated()` 返回 `true`),否则为未认证。
`AuthenticationManager` 本身并不包含具体的认证逻辑,它只是一个协调者,负责将认证请求委托给一个或多个 `AuthenticationProvider`。这些 `AuthenticationProvider` 是具体的认证逻辑实现者,它们会根据各自的认证策略来验证 `Authentication` 对象。
当 `AuthenticationManager` 的 `authenticate` 方法被调用时,它会按照在 Spring Security 配置中定义的顺序,将 `Authentication` 对象传递给每一个 `AuthenticationProvider`。每个 `AuthenticationProvider` 都会尝试验证传入的 `Authentication` 对象。如果某个 `AuthenticationProvider` 成功验证了认证信息,它会返回一个已认证的 `Authentication` 对象,并且认证过程会停止,不再继续调用后续的 `AuthenticationProvider`。如果所有的 `AuthenticationProvider` 都无法验证认证信息,那么 `AuthenticationManager` 会抛出一个 `AuthenticationException`,表示认证失败。
在 Spring Security 的配置中,通常会配置多个 `AuthenticationProvider`,以支持不同类型的认证机制,例如:
- `DaoAuthenticationProvider`:用于基于数据库的用户名和密码认证。
- `LdapAuthenticationProvider`:用于基于 LDAP 的认证。
- `OAuth2AuthenticationProvider`:用于基于 OAuth 2.0 的认证。
每个 `AuthenticationProvider` 都会实现自己的认证逻辑,例如查询数据库、调用 LDAP 服务、处理 OAuth 令牌等。当 `AuthenticationManager` 接收到一个认证请求时,它会根据配置的 `AuthenticationProvider` 来决定使用哪种认证机制。
总结来说,`AuthenticationManager` 通过委托给配置好的 `AuthenticationProvider` 来知道如何去验证传入的认证信息。这些 `AuthenticationProvider` 负责具体的认证逻辑实现,而 `AuthenticationManager` 则负责协调这些认证逻辑的执行。
之后用postman发送login请求,返回成功
如何实现返回一个jwt令牌
login返回的值我们不希望是Success,而是希望返回一个jwt令牌给客户端
注意一点:一开始的login的验证是跟authentication相关的,包括SecurityConfig中的AuthenticationManager,AuthenticationProvider;但是如果login验证成功了之后便与jwt息息相关了,因为login返回一个jwt令牌给客户端,之后客户端每次在登录前只要将jwt发送给服务器端,再验证就可以直接登录了(有关jwt的相关知识可以看我写另一篇文章)
之后的完整代码就是我本地的SpringSecEx,自己理解或查ai,查资料可以解决
之后提几个问题
问题1:解释一下图1中的jwtFilter是怎么与图2产生联系的,图2中的addFilterBefore是什么意思,和后面的Authentication Provider和Authentication Manager又有什么联系
答案1:
图1中的`JWTFilter`是一个自定义的过滤器,它继承自`OncePerRequestFilter`,用于处理进入Spring Security的安全上下文中的每个HTTP请求。这个过滤器的主要作用是验证JWT令牌的有效性,并将用户信息设置到安全上下文中。
图2中的`SecurityConfig`类是一个Spring Security配置类,它使用`@EnableWebSecurity`注解来启用Spring Security的Web安全支持。在这个配置类中,`jwtFilter`被添加到Spring Security的过滤器链中,以在处理请求之前执行JWT验证。
### `addFilterBefore`的作用
在图2中,`http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);`这行代码的作用是将自定义的`jwtFilter`添加到Spring Security的过滤器链中,并且指定它在`UsernamePasswordAuthenticationFilter`之前执行。这意味着在Spring Security处理用户名和密码认证之前,`jwtFilter`会先执行,检查请求中是否包含有效的JWT令牌。
### `JWTFilter`与`Authentication Provider`和`Authentication Manager`的联系
在Spring Security中,`AuthenticationProvider`是负责提供认证逻辑的组件,而`AuthenticationManager`是负责管理一个或多个`AuthenticationProvider`的组件。当一个认证请求到来时,`AuthenticationManager`会将请求传递给配置的`AuthenticationProvider`进行处理。
在图2的配置中,`authenticationProvider()`方法创建了一个`DaoAuthenticationProvider`实例,这是Spring Security提供的一个默认实现,用于处理基于数据库的用户认证。这个`AuthenticationProvider`会使用`UserDetailsService`来获取用户信息,并使用`PasswordEncoder`来验证密码。
当`JWTFilter`验证JWT令牌成功后,它会创建一个`UsernamePasswordAuthenticationToken`对象,这个对象包含了从JWT令牌中提取的用户信息。然后,`JWTFilter`会将这个`AuthenticationToken`设置到`SecurityContextHolder`中,这样Spring Security就会认为用户已经通过了认证。
如果`JWTFilter`没有验证通过JWT令牌,或者请求中没有包含JWT令牌,那么Spring Security会继续执行后续的过滤器,包括`UsernamePasswordAuthenticationFilter`,后者会处理传统的用户名和密码认证。
总结来说,`JWTFilter`与`Authentication Provider`和`Authentication Manager`的联系在于,`JWTFilter`在认证过程中起到了前置验证的作用,如果JWT验证成功,它可以直接设置认证信息,否则,认证请求会传递给后续的`AuthenticationProvider`进行处理。这种设计使得Spring Security可以灵活地处理不同类型的认证请求。
问题2:所以登录执行流程应该是jwtfilter在最前面执行,如果没有jwt,那么就转到verify方法,然后verify方法再调用AuthenticationManage,AuthenticationManage再调用AuthenticationProvider,之后再通过具体的userDetailsService查看验证是否成功,我说的流程是否正确
答案2:
您的描述基本正确,但有一些细节需要澄清。下面是登录执行流程的详细步骤:
1. **JWTFilter执行**:当一个请求到达Spring Security的过滤器链时,`JWTFilter`首先执行。它检查请求中是否包含JWT令牌。
2. **验证JWT**:如果请求中包含JWT令牌,`JWTFilter`会验证令牌的有效性,包括检查签名、过期时间等。
3. **提取用户信息**:如果JWT令牌有效,`JWTFilter`会从令牌中提取用户信息,并创建一个`Authentication`对象。
4. **设置认证信息**:`JWTFilter`将创建的`Authentication`对象设置到`SecurityContextHolder`中,这样Spring Security就知道用户已经通过认证。
5. **无JWT或验证失败**:如果请求中没有JWT令牌或JWT验证失败,`JWTFilter`不会设置认证信息,请求将继续向下传递到其他过滤器。
6. **传统认证方法**:如果`JWTFilter`没有设置认证信息,Spring Security会继续执行后续的过滤器,如`UsernamePasswordAuthenticationFilter`。这个过滤器处理传统的用户名和密码认证。
7. **调用AuthenticationManager**:在用户名和密码认证过程中,`UsernamePasswordAuthenticationFilter`会创建一个`Authentication`对象,并调用`AuthenticationManager`的`authenticate`方法。
8. **调用AuthenticationProvider**:`AuthenticationManager`接收到认证请求后,会将其传递给配置的`AuthenticationProvider`。
9. **用户详细信息服务**:`AuthenticationProvider`(如`DaoAuthenticationProvider`)会使用`UserDetailsService`来获取用户信息,并验证提供的密码是否正确。
10. **认证成功或失败**:如果用户详细信息服务验证成功,`AuthenticationProvider`会创建一个已认证的`Authentication`对象并返回给`AuthenticationManager`。如果验证失败,会抛出`AuthenticationException`。
您的流程描述中,`verify`方法并不是Spring Security认证流程的一部分。在您提供的代码中,`verify`方法可能是`UserService`类中的一个方法,用于验证用户凭据。这个方法可能会与`AuthenticationManager`交互,但它不是Spring Security过滤器链的一部分。在Spring Security中,认证过程是由过滤器链和`AuthenticationManager`来处理的。
问题3:所以一共有几个filter,你说的UsernamePasswordAuthenticationFilter我没看见,我只在下面图中的34行发现了UsernamePasswordAuthentication,那么是否还有这个过滤器?
答案3:
在Spring Security中,`UsernamePasswordAuthenticationFilter` 是默认的过滤器之一,用于处理基于表单的用户名和密码认证。即使你在配置类中没有显式地看到它,它也是Spring Security默认过滤器链的一部分,除非被明确地禁用或替换。
在图2中的`SecurityConfig`类,`http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);` 这行代码的作用是将自定义的 `jwtFilter` 添加到Spring Security的过滤器链中,并且指定它在 `UsernamePasswordAuthenticationFilter` 之前执行。这意味着 `jwtFilter` 会在 `UsernamePasswordAuthenticationFilter` 之前对请求进行处理。
如果你没有在配置中看到 `UsernamePasswordAuthenticationFilter` 被添加,这可能是因为它已经被包含在Spring Security的默认配置中,或者你的配置类继承了另一个配置类,其中已经包含了这个过滤器。
在图1中的 `JWTFilter` 是一个自定义过滤器,它在Spring Security的过滤器链中执行特定的任务,比如验证JWT令牌。如果JWT验证成功,它会将用户信息设置到安全上下文中,否则请求会继续传递给下一个过滤器,这可能包括 `UsernamePasswordAuthenticationFilter`。
总结来说,即使在配置类中没有显式地看到 `UsernamePasswordAuthenticationFilter`,它也可能是Spring Security默认过滤器链的一部分。你的自定义 `jwtFilter` 被添加到这个链中,并且在 `UsernamePasswordAuthenticationFilter` 之前执行。如果 `jwtFilter` 没有处理认证,那么 `UsernamePasswordAuthenticationFilter` 会处理传统的用户名和密码认证。