一、知识准备
(一)Servlet规范
根据Servlet规范:
一个客户端请求Request在Servlet容器中需要经过Filter Chain中一些列Filter处理后才会获取到Web资源,
而且响应Response也需要再次经过Filter Chain中的Filter处理后才能返回给客户端。Spring基于该Servlet规范,在Filter中接入安全机制来保证Web资源的安全。
(二) Spring Security
Spring Security 是一个强大的安全框架,用于保护 Java 应用程序免受各种安全威胁。它提供了全面的安全服务,包括认证、授权、攻击防护等。
认证:Spring Security 处理用户身份的验证。它可以配置各种身份验证机制,如表单登录、LDAP、OAuth 2.0 等。通过集成 JWT,Spring Security 可以利用 JWT 作为认证令牌来确认用户身份。
授权:Spring Security 提供了灵活的授权机制,通过配置角色和权限,控制用户对不同资源的访问。它可以根据 JWT 中的声明(如角色)来进行授权决策。
安全配置:Spring Security 提供了用于配置安全策略的工具,确保应用程序的安全需求得以实现。例如,配置哪些端点需要认证,哪些不需要,防止跨站请求伪造(CSRF)等。
保护机制:Spring Security 提供了防御性保护机制,如防止常见的安全漏洞(如跨站脚本攻击、跨站请求伪造、点击劫持等)。
Spring Security主要就做这2件事:
1:身份认证(谁在发起请求),
2:身份授权(是否有权限访问资源)。
但是需要明确一点:
FilterSecurityInterceptor主要做的是基于访问规则的身份授权。而身份认证是身份授权的前提,因此FilterSecurityInterceptor会在认证信息不存在时进行一次身份认证。正常认证流程是在其他优先级更高的过滤器完成的身份认证,当然二者的认证流程一致:
- 通过AuthenticationManager获取当前请求的身份认证信息
- 通过AccessDecisionManager决断特定访问规则的web资源能否被访问
(三) JWT (JSON Web Token)
JWT 是一种用于在网络应用环境中安全传输信息的标准。它是一种自包含的令牌格式,能够在网络通信中传递用户身份和相关权限。
无状态的认证:JWT 是自包含的,它存储了所有必要的用户信息和声明。使用 JWT,服务器端不需要维护会话状态,从而提高了扩展性和简化了跨多个服务器的负载均衡。
安全性:JWT 可以被签署和加密。签署确保了数据的完整性和来源的真实性,加密则保护了数据的隐私。通过对 JWT 进行签名,服务器能够验证令牌未被篡改。
灵活的声明:JWT 可以包含自定义的声明(Claims),这些声明提供了有关用户权限、角色等信息。这使得 JWT 能够灵活地满足不同应用场景的需求。
跨域认证:由于 JWT 是一种独立于服务器存储的令牌,它可以在不同的前端和后端系统之间传递,支持跨域和跨平台的认证需求。
通过JWT进行用户认证与传统会话管理的方式相比,具有一些显著的不同。传统的会话管理通常依赖服务器端存储用户的会话状态,而JWT(JSON Web Token)是一种无状态的认证机制,令用户在每次请求时都携带认证信息。下面是详细的讲解:
1. 传统会话管理 vs. JWT
传统会话管理:
- 会话存储:当用户登录时,服务器生成一个会话(Session),并在服务器端存储该会话的状态信息。
- 会话ID:服务器生成一个唯一的会话ID,并将其发送给客户端,通常存储在浏览器的cookie中。
- 状态管理:每次请求时,客户端将会话ID发送到服务器,服务器使用会话ID查找并恢复会话状态,进行身份验证和授权。
- 状态存储问题:会话信息存储在服务器端,可能会增加服务器的负担,尤其是在大规模用户访问时。
JWT:
- 无状态:JWT包含了所有的认证信息,服务器不需要存储会话状态,认证信息直接嵌入在JWT中。
- Token生成:当用户登录成功时,服务器生成一个JWT,并将其发送给客户端。JWT通常包含用户的身份信息和一些元数据(如过期时间)。
- Token验证:每次请求时,客户端将JWT发送到服务器(通常在HTTP头部的
Authorization
字段中)。服务器解密和验证JWT,检查其有效性(例如签名和过期时间)以进行身份认证。- 无状态优势:由于JWT是无状态的,服务器不需要维护会话信息,减少了存储负担,并使得应用更具可扩展性。
2. JWT的工作流程
1. 用户登录
- 用户请求:用户向服务器发送包含用户名和密码的登录请求。
- 服务器验证:服务器验证用户的凭据。如果凭据正确,则生成JWT。
2. 生成JWT
- 构造JWT:JWT通常包含三个部分:
- 头部(Header):指定JWT的类型(通常是
JWT
)和签名算法(如HS256
)。- 负载(Payload):包含声明(Claims),如用户ID、角色和其他元数据。
- 签名(Signature):对头部和负载进行加密,以防止篡改。
- 发送Token:服务器将生成的JWT返回给客户端,客户端通常将其存储在本地存储(LocalStorage)或cookie中。
3. 客户端请求
- 附带Token:每次客户端发送请求时,将JWT附加在HTTP头部(通常是
Authorization
字段中)或者作为请求参数。- 服务器验证:服务器接收到请求后,提取JWT并进行验证:
- 解密签名:使用服务器的密钥解密JWT的签名部分,验证Token的完整性和真实性。
- 检查过期:验证Token的过期时间,确保Token仍然有效。
- 提取用户信息:从JWT中提取用户信息,并进行身份验证和授权。
4. 服务器响应
- 验证成功:如果JWT有效,服务器根据用户的权限进行相应的操作,并返回数据给客户端。
- 验证失败:如果JWT无效(如签名错误或过期),服务器会返回认证失败的响应,客户端需要重新登录。
(四)整合的作用
当 Spring Security 和 JWT 结合使用时,它们共同解决了以下问题:
安全认证和授权:Spring Security 负责处理认证和授权的配置和策略,JWT 提供了一个安全、无状态的令牌来传递用户信息和权限。
无状态会话管理:通过 JWT,无需在服务器端存储会话状态,从而简化了扩展和负载均衡。
增强的安全性:Spring Security 提供了强大的安全机制,JWT 通过签名和加密增强了数据的安全性。
灵活性和扩展性:结合 JWT 的灵活声明和 Spring Security 的配置能力,能够满足复杂的认证和授权需求。
Spring Security 提供了一个全面的安全框架,而 JWT 提供了一种轻量级、无状态的认证机制。结合这两者可以实现高效且安全的认证和授权解决方案。
二、Spring Security 身份认证流程
在Spring Security中进行身份认证的流程如:
1、过滤器链(FilterChain)(内部组件,不需配置):SpringSecurity的过滤器链负责处理HTTP请求并进行身份认证。过滤器链是由一系列过滤器组成的链条,其中包括UsernamePasswordAuthenticationFilter等过滤器。过滤器链根据请求的URL和配置的规则来确定是否对请求进行身份认证处理。
2、UsernamePasswordAuthenticationFilter(内部组件,不需配置):这个过滤器负责处理基于用户名和密码的身份认证请求。它从请求中获取用户名和密码,并将其封装成UsernamePasswordAuthenticationToken对象,然后将该对象传递给下一个组件。
3、AuthenticationManager(默认不需配置,也可以自定义):AuthenticationManager是SpringSecurity中的一个核心接口,负责管理身份认证。当UsernamePasswordAuthenticationFilter传递UsernamePasswordAuthenticationToken给AuthenticationManager时,它会委托给一个或多个AuthenticationProvider来完成具体的身份认证过程。
4、AuthenticationProvider(默认不需配置,也可以自定义):AuthenticationProvider是一个接口,它定义了对特定类型的身份认证进行处理的方法。常见的实现类是DaoAuthenticationProvider,它使用用户名和密码进行身份认证。AuthenticationProvider依赖于UserDetailsService来获取用户的详细信息,并使用PasswordEncoder来进行密码的比对。
5、UserDetailsService(默认不需配置,也可以自定义):UserDetailsService是一个接口,用于加载用户的详细信息。它根据用户名从数据库或其他存储中加载用户信息,包括密码和权限等。你可以自定义实现该接口,或使用Spring Security提供的默认实现。
6、PasswordEncoder(默认不需配置,也可以自定义):PasswordEncoder是一个接口,用于对密码进行编码和比对。在身份认证过程中,AuthenticationProvider使用PasswordEncoder来比对用户提交的密码和数据库中存储的密码是否匹配。常见的实现类是BCryptPasswordEncoder,它使用bcrypt算法进行密码的加密和比对。
7、Authentication(内部组件,不需配置):当身份认证成功时,AuthenticationProvider会创建一个已认证的Authentication对象,其中包含了用户的身份信息和权限信息。该对象将被封装到SecurityContextHolder中的安全上下文中,供后续的权限控制和访问控制使用。
8、登录成功或失败处理器(默认不需配置,也可以自定义):根据认证结果,UsernamePasswordAuthenticationFilter会根据配置的登录成功和失败处理器,进行相应的处理。例如,可以进行重定向到指定的页面或返回认证失败的错误信息。
三、Spring Security 编码流程
Spring Boot项目中使用Spring Security的实现用户认证配置和实现(授权控制和其他安全需求后续再实现):
1、添加Spring Security依赖:在项目的
pom.xml
文件中添加以下依赖项:
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2、创建Spring Security配置类:SecurityConfig 来继承
WebSecurityConfigurerAdapter
来自定义安全策略,
- 注入 UserDetailsService,用UserDetailsServiceImpl实现UserDetailsService接口,用于加载用户认证信息;
- 自定义
PasswordEncoder
组件实现密码策略:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 注入 UserDetailsService,用于加载用户认证信息
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() // 开始配置请求授权
.antMatchers("/admin/**").hasRole("ADMIN") // 访问 /admin/** 路径需要 ADMIN 角色
.antMatchers("/user/**").hasAnyRole("ADMIN", "USER") // 访问 /user/** 路径需要 ADMIN 或 USER 角色
.anyRequest().authenticated() // 其他所有请求需要认证
.and()
.formLogin() // 配置表单登录
.loginPage("/login") // 设置自定义登录页面路径
.permitAll() // 允许所有用户访问登录页面
.and()
.logout() // 配置注销功能
.permitAll(); // 允许所有用户访问注销功能
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService) // 配置用户信息服务
.passwordEncoder(passwordEncoder()); // 配置密码编码器
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // 定义 BCryptPasswordEncoder 作为密码编码器
}
}
3、认证机制,可以进一步定制
UserDetailsService
,自定义UserDetailsServiceImpl实现UserDetailsService(组件自带)接口:
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 根据用户名查询用户信息
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserName, username);
User user = userMapper.selectOne(queryWrapper);
// 判断是否查到用户,如果没查到抛出异常
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
// 返回用户信息
return new LoginUser(user);
}
}
用户登录实体类 LoginUser
/**
* 自定义用户实现类,提供给Spring Security使用
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails {
private User user; // 封装的用户实体
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 返回用户的权限,示例中暂时返回null
return null;
}
@Override
public String getPassword() {
// 返回用户的密码
return user.getPassword();
}
@Override
public String getUsername() {
// 返回用户的用户名
return user.getUserName();
}
@Override
public boolean isAccountNonExpired() {
// 账户是否过期,示例中始终返回true
return true;
}
@Override
public boolean isAccountNonLocked() {
// 账户是否被锁定,示例中始终返回true
return true;
}
@Override
public boolean isCredentialsNonExpired() {
// 凭证是否过期,示例中始终返回true
return true;
}
@Override
public boolean isEnabled() {
// 账户是否启用,示例中始终返回true
return true;
}
}
4、创建登录页面控制器 LoginController
@Controller
public class WebController {
@GetMapping("/")
public String home() {
return "home";
}
@GetMapping("/login")
public String login() {
return "login";
}
}
5、创建视图模板 (仅做演示,不是项目中代码)
src/main/resources/templates
目录下创建两个视图模板:home.html
和login.html
home.html:
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
</head>
<body>
<h1>Welcome to the Home Page!</h1>
<a href="/logout">Logout</a>
</body>
</html>
login.html:
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<form action="/login" method="post">
<div>
<label for="username">Username:</label>
<input type="text" id="username" name="username" />
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password" />
</div>
<div>
<button type="submit">Login</button>
</div>
</form>
</body>
</html>
四、Spring Security整合JWT
Spring Security+JWT认证流程:
- 用户登录: 提交用户名和密码 -> Spring Security进行认证。
- JWT生成: 认证成功后生成JWT并返回给用户。
- 请求中的JWT验证: 用户在后续请求中提供JWT ->
JwtAuthenticationFilter
验证JWT。- 用户身份设置: 如果JWT有效,设置用户的身份到Spring Security上下文。
- 权限控制: 根据配置的权限规则检查用户是否有权访问资源。
具体的编码过程
添加依赖:首先,在
pom.xml
文件中添加 Spring Boot、Spring Security 和 JWT 相关的依赖。这些依赖提供了你所需的库和工具,用于构建安全性和处理 JWT。配置 JWT:创建一个JwtUtil工具 类,这个类负责生成和解析 JWT。它使用一个秘密密钥来签署令牌,并设置令牌的有效期。你可以根据需求调整这些配置。
配置 Spring Security:在
SecurityConfig
类中配置 Spring Security,以便它知道如何处理 JWT。需要禁用 CSRF 保护(如果应用程序不需要它),并配置哪些端点需要认证,哪些不需要。同时,添加一个 JWT 过滤器来处理传入的请求并验证 JWT。创建 JWT 过滤器:
JwtAuthenticationFilter
拦截器用于从请求中提取 JWT 并验证其有效性。如果 JWT 合法,它将设置 Spring Security 的认证上下文,使得应用程序可以基于 JWT 的内容进行授权。添加认证控制器:创建一个控制器
AuthController
,用于处理用户的登录请求。这个控制器将验证用户凭证,并在凭证合法时生成并返回 JWT。配置属性文件:在
application.yml
文件中配置 JWT 的秘密密钥和有效期,这些配置将用于生成和验证 JWT。
1、添加相关依赖
在
pom.xml
文件中添加以下依赖:
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JSON Web Token (JWT) -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2、创建JWT工具类:
创建一个工具类JwtUtil,用于生成和解析JWT令牌。
/**
* JWT工具类
*/
public class JwtUtil {
//有效期为
public static final Long JWT_TTL = 24*60 * 60 *1000L;// 60 * 60 *1000 一个小时
//设置秘钥明文
public static final String JWT_KEY = "sangeng";
public static String getUUID(){
String token = UUID.randomUUID().toString().replaceAll("-", "");
return token;
}
/**
* 生成jtw
* @param subject token中要存放的数据(json格式)
* @return
*/
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
return builder.compact();
}
/**
* 生成jtw
* @param subject token中要存放的数据(json格式)
* @param ttlMillis token超时时间
* @return
*/
public static String createJWT(String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if(ttlMillis==null){
ttlMillis=JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("sg") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
.setExpiration(expDate);
}
/**
* 创建token
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
return builder.compact();
}
public static void main(String[] args) throws Exception {
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";
Claims claims = parseJWT(token);
System.out.println(claims);
}
/**
* 生成加密后的秘钥 secretKey
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 解析
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}
3、配置Spring Security:
(1)禁用 CSRF:
http.csrf().disable();
关闭了跨站请求伪造保护,通常在使用 token 认证时这样做。(2)无状态会话:
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
配置 Spring Security 不使用 session 来管理用户状态。
(3)访问控制:允许匿名访问特定的 URL,比如登录接口 (
/SSOlogin/login
)。其他所有请求都需要认证才能访问。
(4)异常处理:配置了自定义的
AuthenticationEntryPoint
和AccessDeniedHandler
用于处理认证和授权异常。
(5)JWT 过滤器:将自定义的
JwtAuthenticationTokenFilter
添加到 Spring Security过滤器链中,用于处理 JWT 认证。
(6)跨域支持:
http.cors();
允许跨域请求。(7)密码编码器:使用
BCryptPasswordEncoder
对密码进行加密和验证。
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 注入 JwtAuthenticationFilter,用于处理 JWT 认证
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
// 注入自定义的 AuthenticationEntryPoint,用于处理未认证的请求
@Autowired
AuthenticationEntryPoint authenticationEntryPoint;
// 注入自定义的 AccessDeniedHandler,用于处理认证后的拒绝访问情况
@Autowired
AccessDeniedHandler accessDeniedHandler;
// 创建一个认证管理器
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
// 返回 Spring Security 默认的 AuthenticationManager 实例
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 关闭 CSRF(跨站请求伪造)保护,通常在使用 Token 认证时关闭
.csrf().disable()
// 配置 Session 管理
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 设定 Session 为无状态,不会创建和使用 Session
.and()
.authorizeRequests()
// 配置允许匿名访问的 URL
.antMatchers("/SSOlogin/login").anonymous()
.antMatchers("/SSOlogin/callback").anonymous()
.antMatchers("/login").anonymous()
// TODO 这里需要对注销接口进行权限控制,通常不允许匿名访问
// 配置其余所有请求需要认证才能访问
.anyRequest().authenticated();
// 配置异常处理器
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint) // 未认证请求的处理器
.accessDeniedHandler(accessDeniedHandler); // 已认证但访问被拒绝的处理器
// 关闭默认的注销功能
http.logout().disable();
// 将自定义的 JWT 认证过滤器添加到 Spring Security 过滤器链中
// 确保 JWT 过滤器在 UsernamePasswordAuthenticationFilter 之前执行
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
// 允许跨域请求
http.cors();
}
// 对密码进行编码和验证,使用 BCrypt 算法
@Bean
public PasswordEncoder passwordEncoder() {
// 返回一个 BCryptPasswordEncoder 实例
return new BCryptPasswordEncoder();
}
}
说明:目前的这种实现方法
优点:
- 简单直接:配置相对简单,适用于基础的JWT认证。
- JWT专注:主要关注JWT过滤器的设置,对JWT进行解析和验证。
适用场景:
- 项目只需要基本的JWT认证,不涉及复杂的用户角色和权限控制。
- 需要快速搭建一个支持JWT认证的简单应用。
另外一种实现方法 ,后面用到授权的时候再进行使用。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("ADMIN", "USER")
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public JwtUtil jwtUtil() {
return new JwtUtil();
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
}
优点:
- 更全面的用户管理:结合了
UserDetailsService
和PasswordEncoder
,支持更复杂的用户认证和密码管理。- 细粒度的权限控制:允许配置不同的访问权限(如
/admin/**
和/user/**
路径),适用于需要角色和权限管理的应用。适用场景:
- 项目需要复杂的用户权限管理,涉及不同角色的用户访问控制。
- 需要自定义用户认证流程或密码加密机制。
- 需要使用Spring Security的其他功能,如
UserDetailsService
来从数据库加载用户信息。
4、创建JWT认证过滤器:
认证的实现主要通过JwtAuthenticationFilter过滤器来完成。这个过滤器通常会在请求到达控制器之前,从请求头中提取 JWT(JSON Web Token),然后验证它的有效性。具体步骤如下:
- JWT 过滤器:
JwtAuthenticationFilter
负责解析并验证 JWT。它检查请求中的 JWT 是否存在并有效。- JWT 解析:解析 JWT 中的用户信息,比如用户名和角色。
- 设置上下文:将解析出的用户信息设置到 Spring Security 的上下文中,使得后续的安全检查能够识别当前用户的身份。
- 链式调用:将
JwtAuthenticationFilter
插入到 Spring Security 的过滤器链中,确保它在身份认证过程中正确处理请求。
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private RedisCache redisCache;
@Autowired
private ObjectMapper objectMapper; // 推荐使用 ObjectMapper 进行 JSON 处理
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 获取请求头中的 token
String token = request.getHeader("token");
if (!StringUtils.hasText(token)) {
// 说明该接口不需要登录,直接放行
filterChain.doFilter(request, response);
return;
}
// 解析获取 userid
Claims claims;
try {
claims = JwtUtil.parseJWT(token);
} catch (Exception e) {
// 打印异常日志并返回错误信息给客户端
e.printStackTrace();
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.TOKEN_INVALID, "TOKEN令牌无效");
WebUtils.renderString(response, objectMapper.writeValueAsString(result));
return;
}
String userId = claims.getSubject();
// 从 Redis 缓存中获取 JSON 字符串并反序列化为 User 对象
String userJsonFromCache = redisCache.getCacheObject("userInfo:" + userId);
User user = objectMapper.readValue(userJsonFromCache, User.class);
// 如果获取不到
if (Objects.isNull(user)) {
// 说明登录过期,提示重新登录
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
WebUtils.renderString(response, objectMapper.writeValueAsString(result));
return;
}
// 存入 SecurityContextHolder
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
}
}
5、创建登录接口:
创建一个登录接口AuthController,用于验证用户身份并生成JWT令牌。(仅作为示例,后面会进行整合Oauth2.0)
@RestController
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/login")
public ResponseEntity<?> createToken(@RequestBody AuthRequest authRequest) {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword()));
final String token = jwtUtil.generateToken(authRequest.getUsername());
return ResponseEntity.ok(new AuthResponse(token));
}
}