什么是SpringSecurity
Spring Security是一个Java框架,用于保护应用程序的安全性。它提供了一套全面的安全解决方案,包括身份验证、授权、防止攻击等功能。Spring Security基于过滤器链的概念,可以轻松地集成到任何基于Spring的应用程序中。它支持多种身份验证选项和授权策略,开发人员可以根据需要选择适合的方式。此外,Spring Security还提供了一些附加功能,如集成第三方身份验证提供商和单点登录,以及会话管理和密码编码等。
Spring Security是一个强大且易于使用的框架,可以帮助开发人员提高应用程序的安全性和可靠性。而我们最常用的两个功能就是认证和鉴权,因此作为入门文章本文也只介绍这两个功能的使用。
❝
Spring Security可以用于Servlet应用和Reactive应用,本文主要介绍基于Servlet应用的场景
❞
如需更详细的使用方式请参考官方文档:
https://spring.io/projects/spring-security
架构
上图是Spring Security官方提供的架构图。我们先看图的左边部分,就是一个典型
Servlet
Filter
(过滤器)处理流程,我们依次讲解流程涉及的组件。
FilterChain
FilterChain
:过滤器链,是
Servlet
容器在接收到客户端发送的请求时创建的,一个
FilterChain
可以包含多个
Filter
和一个
Servlet
,
Servlet
容器根据请求URI的路径来处理
HttpServletRequest
。
在Spring MVC中,
Servlet
就是
DispatcherServlet
实例。一个
Servlet
最多只能处理一个
HttpServletRequest
和
HttpServletResponse
。然而,可以使用多个
Filter
来完成如下工作。
防止下游的
Filter
或
Servlet
被调用。在这种阻断请求的情况下,
Filter
通常会使用
HttpServletResponse
对客户端写入响应内容。
修改下游的
Filter
和
Servlet
所使用的
HttpServletRequest
或
HttpServletResponse
。
DelegatingFilterProxy
DelegatingFilterProxy
:Spring Security 对
Servlet
的支持是基于
Servlet
Filter
的,而
DelegatingFilterProxy
就是Spring Security的
Filter
实现。
DelegatingFilterProxy
允许在
Servlet
容器的生命周期和 Spring 的
ApplicationContext
之间建立桥梁。
Servlet
容器允许通过使用自己的标准来注册
Filter
实例,但
Servlet
容器不知道 Spring 定义的 Bean。因此大多数情况下我们通过标准的
Servlet
容器机制来注册
DelegatingFilterProxy
,但将所有工作委托给实现
Filter
的Spring Bean。
❝ Spring Security会自动向
Servlet
容器机制注册
DelegatingFilterProxy
,无需我们手动去注册
❞
FilterChainProxy
FilterChainProxy
:是 Spring Security 提供的一个特殊的
Filter
,允许通过
SecurityFilterChain
委托给许多
Filter
实例。由于
FilterChainProxy
是一个Spring Bean,因此它被包含在
DelegatingFilterProxy
中。
SecurityFilterChain
SecurityFilterChain
:是
FilterChainProxy
用来确定当前请求应该调用哪些Spring Security
Filter
实例的过滤器链。
SecurityFilterChain
中的
Security
Filter
一般都是Spring Bean,但这些
Security
Filter
是用
FilterChainProxy
进行注册,而不是通过
DelegatingFilterProxy
注册。与直接向Servlet容器或
DelegatingFilterProxy
注册相比,
FilterChainProxy
有很多优势。
首先,由于
FilterChainProxy
是 Spring Security 使用的核心,它可以处理一些必须要做的事情。 例如:
清除
SecurityContext
以避免内存泄漏。
应用Spring Security的
HttpFirewall
来保护应用程序免受某些类型的攻击。
其次,它在确定何时应该调用
SecurityFilterChain
方面提供了更大的灵活性。在Servlet容器中,
Filter
实例仅基于URL被调用。 然而,
FilterChainProxy
可以通过使用
RequestMatcher
接口,根据
HttpServletRequest
中的任何内容确定调用。
图的右边部分是存在多个
SecurityFilterChain
,
FilterChainProxy
的匹配策略则是匹配第一个满足的
SecurityFilterChain
。
比如,请求的URL是
/api/messages/
,它首先与
/api/**
的
SecurityFilterChain 0
模式匹配,所以只有
SecurityFilterChain0
被调用;虽然它也与
SecurityFilterChain n
匹配。
如果请求的URL是
/messages/
,它与
/api/**
的
SecurityFilterChain 0
模式不匹配,所以
FilterChainProxy
继续尝试每个
SecurityFilterChain
。如果没有其他
SecurityFilterChain
实例相匹配,则调用
SecurityFilterChain n
。
SecurityFilter
SecurityFilter
:是指通过
SecurityFilterChain
插入
FilterChainProxy
中的
Filter
。
这些
Filter
可以用于许多不同的目的,如 认证、 授权、 漏洞保护等。
Filter
是按照特定的顺序执行的,以保证它们在正确的时间被调用。
例如,执行认证的
Filter
应该在执行授权的
Filter
之前被调用。如果想要知道 Spring Security 的
Filter
的顺序,可以查看
FilterOrderRegistration
源码。
❝ 如果想查看你应用中注册了哪些
SecurityFilter
的话可以将org.springframework.security的日志级别调到info,这样在你应用启动的时候就会在控制台打印出当前应用注册的所有
SecurityFilter
。效果如下:
❞
2023 -06 -14 T08:55 :22.321 -03 :00 INFO 76975 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [ org.springframework.security.web.session.DisableEncodeUrlFilter@404 db674, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@50f 097b5, org.springframework.security.web.context.SecurityContextHolderFilter@6f c6deb7, org.springframework.security.web.header.HeaderWriterFilter@6f 76c2cc, org.springframework.security.web.csrf.CsrfFilter@c 29fe36, org.springframework.security.web.authentication.logout.LogoutFilter@ef 60710, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@7 c2dfa2, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@4397 a639, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@7 add838c, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@5 cc9d3d0, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7 da39774, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@32 b0876c, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@3662 bdff, org.springframework.security.web.access.ExceptionTranslationFilter@77681 ce4, org.springframework.security.web.access.intercept.AuthorizationFilter@169268 a7]
至此,Spring Security官方架构图中涉及的组件就基本介绍完了,大家先对整体架构和执行流程有一个了解,只有先了解了整体架构,才方便接下来我们去理解Spring Security是如何去实现认证和授权的。
常用Spring Security开启的
SecurityFilter
CsrfFilter:防止Csrf攻击的
SecurityFilter
AuthorizationFilter:授权
SecurityFilter
ExceptionTranslationFilter:处理认证和授权异常的
SecurityFilter
异常处理
Spring Security中有一个
ExceptionTranslationFilter
,
ExceptionTranslationFilter
作为 Security Filter 之一被插入到 FilterChainProxy 中。
ExceptionTranslationFilter
可以处理AuthenticationException或AccessDeniedException,其逻辑大概是这样:
try { filterChain.doFilter(request, response); } catch (AccessDeniedException | AuthenticationException ex) { if (!authenticated || ex instanceof AuthenticationException) { startAuthentication(); } else { accessDenied(); } }
❝ 这段代码的逻辑大致就是,拦截AccessDeniedException 或 AuthenticationException,如果不是这两个异常则不处理。
❞
ExceptionTranslationFilter
流程如下:
因此如果我们想自己处理AuthenticationException或者AccessDeniedException,分别实现
AuthenticationEntryPoint
或者
AccessDeniedHandler
即可
@Component public class AccessDeniedHandlerImpl implements AccessDeniedHandler { @Override public void handle (HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { log.error("AccessDeniedException 请求URI:{}" , request.getRequestURI(), accessDeniedException); response.setCharacterEncoding("UTF-8" ); HashMap<String, String> result = new HashMap(); result.put("code" , "401" ); result.put("message" , "权限不足" ); response.getWriter().write(JsonUtil.toString(result)); } }
@Component public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint { @Override public void commence (HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { log.error("AccessDeniedException 请求URI:{}" , request.getRequestURI(), authException); response.setCharacterEncoding("UTF-8" ); HashMap<String, String> result = new HashMap(); result.put("code" , "500" ); result.put("message" , "用户名或密码错误" ); response.getWriter().write(JsonUtil.toString(result)); } }
@Configuration public class SecurityConfig { @Bean public SecurityFilterChain apiFilterChain (HttpSecurity httpSecurity, AuthenticationEntryPoint authenticationEntryPoint, AccessDeniedHandler accessDeniedHandler) throws Exception { httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler); httpSecurity.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint); return httpSecurity.build(); } }
上面是Spring Security对于认证和鉴权异常的处理机制,但是如果我们自定义了一个Filter。如果这个Filter抛出异常,Spring的全局异常处理机制是无法处理的(原因自行搜索)。所以我们还需要自己做一个Filter异常的处理流程。
首先,我们自定义一个Filter,要在FilterChain中的位置比较靠前,没有其他逻辑就是拦截后面filter抛出的异常,然后转发到指定Controller去处理,然后再用全局异常去处理Filter抛出的异常。
@Component public class ExceptionFilter extends OncePerRequestFilter { @Override protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { filterChain.doFilter(request, response); }catch (Exception e){ request.setAttribute("filterException" , e); request.getRequestDispatcher("/exception" ).forward(request, response); } } }
@RestController public class ExceptionController { @RequestMapping ("/exception" ) public void handleException (HttpServletRequest request) throws Exception { Object attribute = request.getAttribute("filterException" ); if (ObjectUtil.isNotEmpty(attribute) && attribute instanceof Exception e){ throw e; } } }
@Configuration public class SecurityConfig { @Bean public SecurityFilterChain apiFilterChain (HttpSecurity httpSecurity, ExceptionFilter exceptionFilter) throws Exception { httpSecurity.addFilterBefore(exceptionFilter, ExceptionTranslationFilter.class ) ; return httpSecurity.build(); } }
认证
上面我们已经把Spring Security整体流程讲完了,接下来我们就看一下具体认证的流程是怎样的。Spring Security有提供一套基于标准页面的流程,但是不适用于基于前后端分离的开发模式。地址给大家贴这,有需要的可自行去看看:
认证 :: Spring Security Reference (springdoc.cn)
接下来介绍基于前后端分离的流程:
先配置AuthenticationManager(认证管理器),其中最常用的AuthenticationManager实现是ProviderManager
@Configuration public class SecurityConfig { @Bean public AuthenticationManager authenticationManager (AuthenticationProvider authenticationProvider) { return new ProviderManager(authenticationProvider); } }
配置AuthenticationProvider,这里我们的认证方案是使用数据库存储的密码和登录请求的密码进行匹配验证,因此我们选择
DaoAuthenticationProvider
。
DaoAuthenticationProvider
是一个
AuthenticationProvider
的实现,它使用
UserDetailsService
和
PasswordEncoder
来验证一个用户名和密码。
@Configuration public class SecurityConfig { @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder(); } @Bean public AuthenticationProvider authenticationProvider (UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) { DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); daoAuthenticationProvider.setUserDetailsService(userDetailsService); daoAuthenticationProvider.setPasswordEncoder(passwordEncoder); return daoAuthenticationProvider; } }
配置
UserDetails
和
UserDetailsService
import lombok.EqualsAndHashCode;import lombok.Getter;import lombok.Setter;import lombok.ToString;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;import java.util.List;@Setter @EqualsAndHashCode @ToString public class SecurityUserDetail implements UserDetails { @Getter private Long userId; private String userName; private String password; private List<GrantedAuthority> authorities; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return this .authorities; } @Override public String getPassword () { return this .password; } @Override public String getUsername () { return this .userName; } @Override public boolean isAccountNonExpired () { return true ; } @Override public boolean isAccountNonLocked () { return true ; } @Override public boolean isCredentialsNonExpired () { return true ; } @Override public boolean isEnabled () { return true ; } }
import lombok.RequiredArgsConstructor;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.stereotype.Service;@Service @RequiredArgsConstructor public class AuthUserDetailsService implements UserDetailsService { private final UserService userService ; @Override public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException { User user= userService .getValidUser(username); Assert.notEmpty(user, "用户名或密码错误" ); SecurityUserDetail userDetail = new SecurityUserDetail(); userDetail.setUserId(user.getId()); userDetail.setUserName(user.getUserName()); userDetail.setPassword(user.getPassword()); return userDetail; } }
LoginController提供登录接口,伪代码如下:
@Slf 4j@RestController @RequiredArgsConstructor public class LoginController { private final AuthenticationManager authenticationManager; public Response<LoginVO> login (LoginAO loginAo) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginAo.getKey(), loginAo.getPassword()); Authentication authenticate = authenticationManager.authenticate(authenticationToken); Assert.notEmpty(authenticate, "用户名或密码错误" ); if (authenticate.getPrincipal() instanceof SecurityUserDetail userDetail){ User user = User.builder().id(userDetail.getUserId()).userName(userDetail.getUsername()).build(); String token = JwtUtil.generateToken(user); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user, null , null ); SecurityContextHolder.getContext().setAuthentication(authentication); return Response.success(LoginVO.builder().token(token).build()); }else { log.error("登录异常,从上下文获取用户信息失败,authenticate:{}" , JsonUtil.toString(authenticate)); return Response.fail(null ); } } }
授权
本文这里分享两种主流的Spring Security授权方式,一种是基于注解的方式,一种是基于配置的方式。
基于注解的授权校验
基于注解的方式校验授权,是通过Spring aop实现的,其流程如下:
首先,我们要开启注解鉴权的功能
@Configuration @EnableMethodSecurity public class SecurityConfig { }
然后在需要鉴权的方法上添加权限注解
@RestController public class UserController { private final UserService userService; @PreAuthorize ("hasAuthority('sys:user:page')" ) public Response<IPage<UserVO>> page(QueryUserPageParam param) { return Response.success(userService.page(param)); } }
Spring Security的常用权限注解有:
@PreAuthorize:前置校验权限,在方法执行之前校验权限,支持Spel表达式
@PostAuthorize:后置权限校验,在方法执行结束以后进行校验,可以对返回结果进行校验,支持Spel表达式
具体每个权限注解的使用方式可以自行去官网学习,这里就不具体介绍了。
下面就以最常用的@PreAuthorize注解为例,介绍一下Spring Security基于注解鉴权的流程与原理:
AuthorizationManagerBeforeMethodInterceptor
(授权管理前置方法拦截器),会将权限注解与
AuthorizationManager
(授权管理器)进行关联及初始化
AuthorizationManagerBeforeMethodInterceptor
拦截器拦截到请求后,会根据权限注解
@PostAuthorize
调用匹配的
PreAuthorizeAuthorizationManager#check
方法,并从SecurityContextHolder上下文中获取
Authentication
对象,将
Supplier<Authentication>
和
MethodInvocation
作为参数传递给
PreAuthorizeAuthorizationManager#check
方法。
AuthorizationManager
授权管理器使用
MethodSecurityExpressionHandler
解析
@PostAuthorize
注解的 SpEL 表达式,并从包含
Supplier<Authentication>
和
MethodInvocation
的
MethodSecurityExpressionRoot
构建相应的
EvaluationContext
。
然后从
Supplier
读取
Authentication
,并检查其权限集合中是否有
sys:user:page
。
如果校验通过,将继续调用业务方法。如果校验不通过,会发布一个
AuthorizationDeniedEvent
,并抛出一个
AccessDeniedException
,
ExceptionTranslationFilter
会捕获并处理。
基于配置的授权校验
基于配置的授权校验是通过
AuthorizationFilter
实现的,首先我们需要配置授权校验规则:
@Configuration public class SecurityConfig { public static final String[] PERMIT_URL = new String[]{ "/doc.html" , "/favicon.ico" , "/swagger-resources" , "/v3/**" , "/webjars/**" , "/actuator/**" , "/login" , "/register" , }; @Bean public SecurityFilterChain apiFilterChain (HttpSecurity httpSecurity, TokenFilter tokenFilter, AuthenticationEntryPoint authenticationEntryPoint, ExceptionFilter exceptionFilter, AccessDeniedHandler accessDeniedHandler) throws Exception { httpSecurity.addFilterBefore(tokenFilter, AuthorizationFilter.class ) ; httpSecurity.addFilterBefore(exceptionFilter, ExceptionTranslationFilter.class ) ; httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler); httpSecurity.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint); httpSecurity.authorizeHttpRequests() .antMatchers(PERMIT_URL).permitAll() .antMatchers("/sys/user/page" ).hasAuthority("sys:user:page" ) .anyRequest().authenticated(); httpSecurity.csrf().disable(); httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); httpSecurity.headers().cacheControl().disable(); httpSecurity.cors(); return httpSecurity.build(); } public WebSecurityCustomizer webSecurityCustomizer () { return customizer -> customizer.ignoring().antMatchers(PERMIT_URL); } }
这里咱们还是以前置校验是否拥有某个权限为例:
httpSecurity.authorizeHttpRequests() .antMatchers("/sys/user/page" ).hasAuthority("sys:user:page" ) .anyRequest().authenticated();
这里初始化设置了
AuthorityAuthorizationManager
作为
AuthorizationManager
的实现,不同的权限校验功能可能对应的
AuthorizationManager
实现会不一样,比如
anyRequest().authenticated()
对应的
AuthorizationManager
实现则是
AuthenticatedAuthorizationManager
当
AuthorizationFilter
执行的时候,会根据配置的授权规则找到对应的
AuthorizationManager
实现,然后执行
check
方法,并从SecurityContextHolder上下文中获取
Authentication
对象,将
Authentication
和
request
作为参数传递给
AuthorizationManager#check
方法。 这里根据上面的规则
hasAuthority()
对应的
AuthorizationManager
实现就是
AuthorityAuthorizationManager
。
然后
AuthorityAuthorizationManager
会根据SecurityContextHolder的
Authentication
中获取所有权限和配置需要的权限进行对比,如果用户上下文SecurityContextHolder中存储的权限集合包含配置需要的权限则返回true通过,反之则返回false。
注意事项
需要特别说明的是,Spring Security存储角色和权限都是使用的
GrantedAuthority
对象,因此Spring Security规定角色需要加上统一前缀方便与权限区分开
❝ 这个统一前缀默认为
ROLE_
,无论是基于配置还是基于注解的授权校验都是同样的规则。
❞
当然你也可以自定义这个前缀,只需要将自定义的
GrantedAuthorityDefaults
对象注册进Spring容器即可。
@Configuration public class SecurityConfig { @Bean static GrantedAuthorityDefaults grantedAuthorityDefaults () { return new GrantedAuthorityDefaults("ROLE_" ); } }
细心的朋友可能已经发现了,无论是基于注解还是基于配置的授权校验,都是从用户上下文SecurityContextHolder中获取当前用户拥有的角色和权限,然后再和需要的权限去比较是否拥有权限。所以我们需要在授权校验之前需要往用户上下文SecurityContextHolder中设置当前用户所拥有的权限。这里就需要用到自定义Filter了。
自定义Filter
如果Spring Security中的
SecurityFilter
不能满足你的业务需求,需要自定义
SecurityFilter
。比如我们需要自定义一个Filter用于解析请求的Token,然后从Token中获取用户信息和权限。自定义
SecurityFilter
有两种方式:
自定义
SecurityFilter
实现jakarta.servlet.Filter,在doFilter方法中实现自己的业务逻辑,参考案例:
public class TokenFilter implements Filter { @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; String token= request.getHeader("Authorization" ); boolean hasAccess = checkToken(token); if (hasAccess) { filterChain.doFilter(request, response); return ; } throw new AuthenticationException("权限不足" ); } }
然后将该
SecurityFilter
注册进
SecurityFilterChain
@Configuration public class SecurityConfig { @Bean SecurityFilterChain filterChain (HttpSecurity http) throws Exception { http.addFilterBefore(new TokenFilter(), AuthorizationFilter.class ) ; return http.build(); } }
❝ 注意,如果想把jakarta.servlet.Filter的实现注册为Spring Bean,这可能会导致 filter 被调用两次,一次由容器调用,一次由 Spring Security 调用,而且顺序不同。可以通过声明
FilterRegistrationBean
Bean 并将其
enabled
属性设置为
false
来告诉 Spring Boot不要向容器注册它。配置如下:
❞
@Bean public FilterRegistrationBean<TokenFilter> tenantFilterRegistration (TokenFilter filter) { FilterRegistrationBean<TokenFilter> registration = new FilterRegistrationBean<>(filter); registration.setEnabled(false ); return registration; }
自定义
SecurityFilter
继承OncePerRequestFilter,这样能保证每个请求只会调用一次的filter(
「
推荐方式
」
),然后将该
SecurityFilter
注册进
SecurityFilterChain
@Component public class TokenFilter extends OncePerRequestFilter { @Override protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String requestURI = request.getRequestURI(); if (!AntPathUtil.match(requestURI, SecurityConfig.PERMIT_URL)){ String token = request.getHeader("Authorization" ); if (StrUtil.isBlank(token)){ throw new AuthenticationCredentialsNotFoundException("请先登录" ); } User user = parse(token); List<GrantedAuthority> authorities = listUserAllPermissions(user.getId()); PreAuthenticatedAuthenticationToken authenticationToken = new PreAuthenticatedAuthenticationToken(user, null , authorities); SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } filterChain.doFilter(request, response); } }
public class AntPathUtil { private AntPathUtil () {} public static final AntPathMatcher MATCHER = createMatcher(); public static boolean match (String path, String... pattern) { if (ArrayUtil.isEmpty(pattern)){ return true ; } if (StrUtil.isBlank(path)){ return false ; } return Arrays.stream(pattern).filter(p -> MATCHER.match(p, path)).findAny().isPresent(); } private static AntPathMatcher createMatcher () { AntPathMatcher antPathMatcher = new AntPathMatcher(); antPathMatcher.setCaseSensitive(false ); return antPathMatcher; } }
然后将该
SecurityFilter
注册进
SecurityFilterChain
@Configuration public class SecurityConfig { @Bean SecurityFilterChain filterChain (HttpSecurity http, TokenFilter tokenFilter) throws Exception { http.addFilterBefore(tokenFilter, AuthorizationFilter.class ) ; return http.build(); } }
总结
最后总结Spring Security的认证和授权的流程如下:
梳理一下上面的流程:
首先,用户携带用户名密码通过LoginController进行登录(认证流程),如果登录成功则返回token(推荐使用JWT作为token)
后续其他请求,携带通过登录获取得到的token,然后先被TokenFilter解析token获取用户信息,并将用户信息写入SecurityContextHolder
AuthorizationManager
通过从SecurityContextHolder获取到当前用户的authentication(权限集合),然后与需要的权限进行对比,从而校验当前用户是否有权限使用当前业务功能
本文使用
markdown.com.cn
排版