MHBLOG当中SpringBootSecurity+JWT运用结合
SpringbootSecurity的工作流程

当用户在 Spring Boot Security 系统中提交登录的账号和密码时,系统会经历一系列流程来验证用户的身份并建立安全的会话。下面是 Spring Boot Security 工作流程的详细讲解,分解了从用户提交登录信息到完成身份认证的整个过程。
Spring Boot Security 工作流程(登录流程):
-
用户提交登录请求
- 用户通过前端(通常是登录页面)提交用户名和密码,这些信息通过 POST 请求发送到服务端(例如,
/login路径)。
- 用户通过前端(通常是登录页面)提交用户名和密码,这些信息通过 POST 请求发送到服务端(例如,
-
过滤器链处理请求
- Spring Security 的过滤器链(
FilterChain)会拦截所有请求,其中的核心过滤器之一是UsernamePasswordAuthenticationFilter,它专门处理用户提交的认证请求(如用户名和密码)。
- Spring Security 的过滤器链(
-
UsernamePasswordAuthenticationFilter获取登录信息UsernamePasswordAuthenticationFilter负责从请求中提取用户名和密码,通常从HttpServletRequest的参数中提取这些信息。它会将用户输入的凭证封装为UsernamePasswordAuthenticationToken对象。
-
委托给 AuthenticationManager 进行认证
UsernamePasswordAuthenticationFilter会将封装好的UsernamePasswordAuthenticationToken对象提交给AuthenticationManager,由它负责进一步的认证处理。- Spring Security 默认使用
ProviderManager作为AuthenticationManager的实现,它会将认证委托给多个AuthenticationProvider。
-
AuthenticationProvider验证凭证- 在典型的应用程序中,Spring Security 使用
DaoAuthenticationProvider作为默认的AuthenticationProvider。 DaoAuthenticationProvider使用UserDetailsService来加载用户信息,它会根据传入的用户名从数据库或其他存储中加载用户详细信息(通常包括加密的密码)。
- 在典型的应用程序中,Spring Security 使用
-
UserDetailsService加载用户信息UserDetailsService的实现类(例如自定义实现或 Spring Security 默认的InMemoryUserDetailsManager)会查询用户的详细信息,并返回一个UserDetails对象。- 该对象通常包含:用户名、加密后的密码、账户是否启用、是否未过期等信息。
-
密码验证
- 一旦
UserDetails被成功加载,DaoAuthenticationProvider会通过PasswordEncoder比较用户输入的明文密码和数据库中存储的加密密码是否匹配。 - Spring Security 默认使用
BCryptPasswordEncoder,它会对输入的明文密码进行加密处理,并与数据库中的哈希密码进行比对。
- 一旦
-
认证成功或失败
- 如果密码匹配且账户未锁定、未过期,
DaoAuthenticationProvider会将认证成功的用户信息封装为Authenticated UsernamePasswordAuthenticationToken,并返回给AuthenticationManager。 - 如果密码不匹配或其他认证信息无效,则抛出异常,如
BadCredentialsException,认证失败。
- 如果密码匹配且账户未锁定、未过期,
-
安全上下文保存用户信息
- 如果认证成功,
AuthenticationManager返回的Authentication对象会被存储到SecurityContext中。 SecurityContextHolder是一个持有用户安全上下文的容器,保存了经过认证的用户信息(Authentication对象),该信息在当前会话中是全局可用的。- 默认情况下,Spring Security 会通过
HttpSessionSecurityContextRepository将安全上下文保存到用户的HttpSession中。
- 如果认证成功,
-
创建会话并返回响应
- 如果认证成功,Spring Security 会根据配置创建用户的会话(
Session)。系统会通过RememberMe机制,决定是否保持用户的登录状态。 - 如果认证成功,系统会重定向或返回一个成功的响应(例如返回主页或 JWT Token,取决于系统的设计)。
- 如果认证成功,Spring Security 会根据配置创建用户的会话(
-
认证失败处理
- 如果认证失败,Spring Security 会调用失败处理器(例如
SimpleUrlAuthenticationFailureHandler),返回认证失败的错误信息或重定向到登录页面。
- 如果认证失败,Spring Security 会调用失败处理器(例如
总结流程图:
用户提交用户名和密码
↓
`UsernamePasswordAuthenticationFilter` 拦截请求并提取凭证
↓
凭证交给 `AuthenticationManager` 进行认证
↓
`AuthenticationManager` 调用 `AuthenticationProvider` 进行认证
↓
`AuthenticationProvider` 使用 `UserDetailsService` 加载用户信息
↓
`AuthenticationProvider` 比较输入密码和数据库密码(通过 `PasswordEncoder`)
↓
[认证成功] [认证失败]
↓ ↓
将认证后的信息存入 `SecurityContext` 调用失败处理器
↓ ↓
创建会话并返回响应 返回错误信息
重要组件的作用:
UsernamePasswordAuthenticationFilter:拦截登录请求,并提取用户名和密码。AuthenticationManager:负责管理整个认证流程,通常委托给AuthenticationProvider。AuthenticationProvider:实际执行认证的核心组件,调用UserDetailsService加载用户数据并进行密码验证。UserDetailsService:加载用户信息的接口,通常通过实现这个接口来查询数据库中的用户。PasswordEncoder:负责密码的加密和比对,通常使用BCryptPasswordEncoder。SecurityContextHolder:保存当前认证成功的用户信息,系统通过它判断用户是否已登录。HttpSession:保存用户的安全上下文信息,默认在会话中存储。
用户登录后的授权流程:
- 一旦用户登录成功并在
SecurityContextHolder中保存了认证信息,Spring Security 会在每次请求时通过SecurityContext验证用户是否已登录,以及用户是否有权访问特定资源。
在MHBlog开发的过程当中,运用了SpringbootSecurity+JWT的组合来验证登录
那么什么是JWT呢?
这里不过多描述,JWT不是很难,所以直接放视频不懂直接看JWT使用
使用详解
由上面我们可以知道,在Security的验证是封装成UsernamePasswordAuthenticationToken对象来传输给其他Filter进行验证的,所以我们的SecurityImpl里要创建一个对象来将Username和Password来构建一个UsernamePasswordAuthenticationToken对象
@Override
public ResponseResult login(User user) {
//创建验证对象Token
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
//判断是否认证通过
if (Objects.isNull(authenticate)) {
throw new RuntimeException("用户名或密码错误");
}
//获取UserId生成Token
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String id = loginUser.getUser().getId().toString();
String jwt = JwtUtil.createJWT(id);
//把用户信息存入redis,然后把Token和UserInfo封装 返回
redisCache.setCacheObject("bloglogin:" + id, loginUser);
UserInfoVo userInfoVo = BeanCopyUtils.copyBean(loginUser.getUser(), UserInfoVo.class);
BlogUserLoginVo vo = new BlogUserLoginVo(jwt, userInfoVo);
return ResponseResult.okResult();
}
之后将UsernamePasswordAuthenticationToken类提交给AuthenticationManager来进行认证,因为Springboot里默认是没有AuthenticationManager这个Bean的,所以我们得在SecurityConfig类里自己定义一个Bean来添加进IOC容器里
@Configuration
public class SecurityConfig {
//此配置方法适用于5.7版本之后
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
之后在SecurityImpl类的方法里注入AuthenticationManager类的Bean
将UsernamePasswordAuthenticationToken传给AuthenticationManager的authenticate()方法
ProviderManager会调用DaoAuthenticationProvider使用UserDetailsService接口的loadUserByUsername方法来根据Username获取内存中的用户,这时后我们就要创建一个类来实现UserDetailsService接口的loadUserByUsername方法
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
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 (Objects.isNull(user)) {
throw new RuntimeException("用户不存在");
}
// TODO 查询权限封装
// 查到用户作为方法返回值返回
return new LoginUser(user);
}
}
让我们来查看这个接口的源码

从源码我们可以知道,这个方法要返回的是一个UserDetails类,但是我们ORM数据库查询出来的实体Entity并不是这个类,这个UserDetails我们点进去查看一下

可以发现这是一个接口类
所以我们创建一个LoginUser类来实现这个UserDetails接口

在这个自定义的LoginUser类当中封装一个User类的属性,这样子Details就能获取User的UserName和Password了
让我们回到BlogLoginServiceImpl

getPrincipal()这又是什么方法?

getPrincipal() 是 Authentication 接口中的一个方法,它用于获取当前经过认证的用户的主要身份信息(通常是用户名或用户对象)。在 Spring Security 中,当用户成功通过身份验证后,Authentication 对象会被存储在 SecurityContext 中,而 getPrincipal() 方法可以返回该用户的身份信息。
详细解释:
-
Authentication接口:Authentication是 Spring Security 的核心接口之一,表示经过身份验证或正在进行身份验证的主体(通常是用户)。- 它包含关于身份验证过程中的各种信息,比如用户身份、权限、凭证等。
-
getPrincipal()方法:getPrincipal()返回与身份验证相关的主身份信息,即用户的主要标识。这个标识通常是经过认证的用户的用户名或UserDetails对象。- 如果使用的是基于用户名和密码的身份验证(如
UsernamePasswordAuthenticationToken),getPrincipal()通常返回的是实现了UserDetails接口的对象,或直接是用户名。
示例:getPrincipal() 返回值的类型
-
基于用户名和密码的认证:
- 在基于用户名和密码的认证(如
UsernamePasswordAuthenticationToken)中,getPrincipal()返回的是UserDetails对象,通常是用户的详细信息。
代码示例:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Object principal = authentication.getPrincipal(); if (principal instanceof UserDetails) { String username = ((UserDetails) principal).getUsername(); System.out.println("Authenticated user: " + username); } else { String username = principal.toString(); System.out.println("Authenticated user: " + username); }- 如果用户通过身份验证,
principal通常是一个实现了UserDetails接口的对象,比如自定义的CustomUserDetails或 Spring Security 提供的org.springframework.security.core.userdetails.User对象。 - 如果
principal是字符串类型,通常它是用户的用户名。
- 在基于用户名和密码的认证(如
-
匿名用户或未认证的用户:
- 如果用户尚未认证或是匿名用户,
getPrincipal()可能返回一个字符串"anonymousUser",或者在某些情况下返回null。
- 如果用户尚未认证或是匿名用户,
常见场景:
-
获取用户详细信息:
在经过身份验证后,通常我们会通过getPrincipal()获取用户的详细信息(如用户名、权限等),以便在业务逻辑中使用。示例:
@GetMapping("/user-info") public ResponseEntity<?> getUserInfo() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); UserDetails userDetails = (UserDetails) authentication.getPrincipal(); return ResponseEntity.ok(userDetails.getUsername()); } -
自定义
UserDetails:
如果你实现了一个自定义的UserDetails类,比如CustomUserDetails,那么getPrincipal()方法会返回你的自定义用户对象,你可以通过它获取与用户相关的自定义信息。
总结:
getPrincipal()方法用于返回经过身份验证的用户的主要标识信息,通常是一个UserDetails对象或用户名。- 在大多数情况下,它返回的是一个实现了
UserDetails接口的对象(如自定义的用户类),可以通过它获取用户名、密码、权限等用户信息。 - 对于匿名用户或未认证的用户,
getPrincipal()可能返回"anonymousUser"或null。
通过 getPrincipal(),你可以轻松获取当前认证用户的详细信息并用于后续的业务逻辑。
也就是说这里返回的是一个LoginUser类,这个类当中有我们主要的用户实体User
从下面的代码我们可以看得出来

之后就是将user的ID作为Subject传入JWT里再存入Redis缓存(k,v)当中
最后再将用户信息VO类和JwtToken封装进前端API所规定的Vo类当中返回
注:
在DaoAuthenticationProvider当中会选择PasswordEncoder,所以我们在Security里配置一下PasswordEncoder
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
这个编码是单向不可逆的,对比是否一致就是对比哈希值,密码:

工作流程例子:
详细的工作流程:
-
前端用户输入的账号密码封装成一个对象
- 前端用户提交登录表单,包含用户名和密码。该信息通过 POST 请求发送到后端,后端会将其封装为一个对象(例如
LoginRequest),然后传递给UsernamePasswordAuthenticationToken进行处理。
例如:
public class LoginRequest { private String username; private String password; // getters and setters } - 前端用户提交登录表单,包含用户名和密码。该信息通过 POST 请求发送到后端,后端会将其封装为一个对象(例如
-
封装成
UsernamePasswordAuthenticationToken对象- 后端接收到
LoginRequest对象后,将用户名和密码封装成UsernamePasswordAuthenticationToken,这是 Spring Security 内置的一个认证对象,用于存放用户凭证(用户名和密码)。
代码示例:
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()); - 后端接收到
-
调用
authenticationManager进行认证- 接下来,将这个
UsernamePasswordAuthenticationToken对象传递给authenticationManager的authenticate()方法。 authenticationManager是 Spring Security 中用于处理认证请求的核心组件。默认情况下,Spring Boot 不会自动配置一个authenticationManager,所以你需要在配置类中手动定义它。
代码示例:
Authentication authentication = authenticationManager.authenticate(authenticationToken); - 接下来,将这个
-
默认没有
authenticationManager,需要手动配置- Spring Boot 默认没有自动配置
AuthenticationManager,因此你需要在你的配置类(通常是SecurityConfig)中手动配置一个AuthenticationManagerBean。
配置
AuthenticationManagerBean:@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } - Spring Boot 默认没有自动配置
-
AuthenticationManager调用DaoAuthenticationProvider进行认证- 当调用
authenticationManager.authenticate()时,实际执行认证的是DaoAuthenticationProvider。 DaoAuthenticationProvider会调用UserDetailsService来加载用户的详细信息,进行认证(包括密码验证)。
- 当调用
-
UserDetailsService从数据库中根据用户名查询用户信息UserDetailsService是 Spring Security 中的一个接口,专门用于从数据源(例如数据库)中加载用户信息。- 你需要自己实现
UserDetailsService接口,并重写loadUserByUsername(String username)方法。该方法通过传入的username查询数据库中对应的用户信息。
实现
UserDetailsService:@Service public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 根据用户名查询用户 User user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("User not found"); } // 将用户信息封装到 UserDetails 并返回 return new CustomUserDetails(user); } } -
将查询到的用户封装成
UserDetails对象返回- 如果用户存在,
loadUserByUsername方法会将用户信息封装到实现了UserDetails接口的类中(如CustomUserDetails),然后返回该对象。 CustomUserDetails类是一个自定义的类,通常用来封装用户的关键信息,例如用户名、密码、权限等。
示例:
public class CustomUserDetails implements UserDetails { private User user; public CustomUserDetails(User user) { this.user = user; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { // 返回用户的权限 return Arrays.asList(new SimpleGrantedAuthority(user.getRole())); } @Override public String getPassword() { // 返回加密后的密码 return user.getPassword(); } @Override public String getUsername() { return user.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return user.isEnabled(); } } - 如果用户存在,
-
验证密码并返回
Authentication对象DaoAuthenticationProvider使用PasswordEncoder来验证用户输入的密码是否与数据库中存储的加密密码一致。通常使用BCryptPasswordEncoder来进行密码的加密和匹配。- 如果验证成功,
DaoAuthenticationProvider会返回一个认证成功的Authentication对象,并将其存储在 Spring Security 的上下文中。
-
认证成功后存入
SecurityContext并放行请求- 认证成功后,Spring Security 会将
Authentication对象存入SecurityContextHolder中,用户的信息就可以在整个会话中使用。 - 之后,用户可以访问受保护的资源,Spring Security 会通过
SecurityContext验证用户的身份和权限。
- 认证成功后,Spring Security 会将
总结:
- 用户登录时输入用户名和密码。
- 封装到
UsernamePasswordAuthenticationToken中。 - 调用
authenticationManager.authenticate()进行认证。 AuthenticationManager调用DaoAuthenticationProvider。DaoAuthenticationProvider使用UserDetailsService加载用户信息。UserDetailsService从数据库中查询用户信息。- 将用户信息封装到
UserDetails对象中返回。 DaoAuthenticationProvider验证密码。- 认证成功后,保存到
SecurityContext中。
你需要自定义 UserDetailsService 来查询数据库中的用户信息,并配置 AuthenticationManager 以使用 DaoAuthenticationProvider 进行认证。

浙公网安备 33010602011771号