spring security
Spring Security是一个基于Spring框架的强大且可高度定制的身份验证和访问控制框架。它提供了全面的安全服务,涵盖了从身份验证到授权的各个方面。
SpEL 表达式鉴权
在 Spring Security 中,@PreAuthorize注解用于在方法执行前进行权限验证。该注解允许您指定一个 SpEL(Spring Expression Language)表达式来定义访问控制规则。在您的情况下,这行代码的含义是@PreAuthorize("@ss.hasPermi('system:notice:add')"):
@PreAuthorize注解表明在调用该方法之前会执行权限验证。
@ss.hasPermi('system:notice:add')是一个 SpEL 表达式,它调用了PermissionService中的hasPermi方法,作为传递了一个权限字符串'system:notice:add'参数。
现在,为什么可以这样鉴权呢?这涉及到 Spring Security 和 Spring Expression Language 的结合运用:
@PreAuthorize注解:它是Spring Security提供的一种声明式的权限验证方式。它允许你在方法执行之前进行权限检查,确保具备相应权限的用户可以访问该方法。
SpEL 表达式:@PreAuthorize注解支持使用 SpEL 表达式,这使得您可以在注解中调用其他 Bean 的方法来进行权限检查。在这个例子中,是类的一个实例,方法接收一个权限字符串作为参数@ss进行PermissionService权限hasPermi验证。
PermissionService 类:在你的代码中,PermissionService类可能包含了与权限相关的逻辑。方法hasPermi可能会检查当前用户是否具备确定的权限字符串所代表的权限。
因此,这种方法可以用于鉴权,因为在@PreAuthorize注解中使用了SpEL表达式,它调用了提高PermissionService了中的方法来进行权限检查。这种方式将权限检查的逻辑与业务逻辑分离,了代码的模块化和可维护性。
@Service("ss") public class PermissionService { /** * 验证权限 * * @param permission ????? * @return ????????? */ public boolean hasPermi(String permission) { if (StringUtils.isEmpty(permission)) { return false; } LoginUser loginUser = SecurityUtils.getLoginUser(); if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) { return false; } PermissionContextHolder.setContext(permission); return hasPermissions(loginUser.getPermissions(), permission); }
/** * 使用鉴权 */ @PreAuthorize("@ss.hasPermi('system:notice:add')") @Log(title = "????", businessType = BusinessType.INSERT) @PostMapping public AjaxResult add(@Validated @RequestBody SysNotice notice) { notice.setCreateBy(getUsername()); return toAjax(noticeService.insertNotice(notice)); }
过滤器鉴权
/** * token 鉴权 * * @author ruoyi */ @Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private TokenService tokenService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { LoginUser loginUser = tokenService.getLoginUser(request); if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) { tokenService.verifyToken(loginUser); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } chain.doFilter(request, response); } }
几个重要的组件
以下是Spring Security的一些核心组件:
身份验证(身份验证):
Authentication接口:用户表示的身份。Spring Security中的Authentication对象包含已验证用户的信息。
AuthenticationManager接口:用于执行身份验证。它接收Authentication对象作为参数,并返回一个已填充完全的Authentication对象。
提供者(认证提供者):
AuthenticationProvider接口:实现了身份验证过程。AuthenticationManager使用一个或多个AuthenticationProvider来尝试验证身份。
DaoAuthenticationProvider:基于数据库的身份验证提供者。它从数据库中检索用户信息并与提供的凭据进行比较。
用户详情服务:
UserDetailsService接口:用于从数据存储中加载用户详细信息。Spring Security使用UserDetailsService来获取用户的详细信息,例如用户名、密码和权限。
密码编码器:
PasswordEncoder接口:用于加密用户的密码。Spring Security推荐存储加密后的密码,以提高安全性。
BCryptPasswordEncoder:常用的密码加密实现之一。
授权(授权):
GrantedAuthority接口:表示授予用户的权限。权限可以是角色或其他自定义标识。
GrantedAuthorityImpl: GrantedAuthority接口的简单实现。
AccessDecisionManager接口:决定用户是否有权限特定执行操作。
Voter接口:用于投票决定是否授予用户权限。
安全上下文持有者:
用于在应用程序的不同部分之间的交付和存储
SecurityContext。
安全上下文:
包含关于当前身份验证和授权的信息。可以通过SecurityContextHolder访问。
FilterChainProxy:
用于定义安全过滤器链,处理HTTP请求的安全性。
WebSecurityConfigurerAdapter:
用于配置Spring Security的filter。开发者可以通过继承WebSecurityConfigurerAdapter来配置不同的安全规则。
@Secured、@PreAuthorize、@RolesAllowed等注解:
登录认证基于过滤器链
- 贯穿于整个过滤器链始终有一个上下文对象SecurityContext和一个Authentication对象(登录认证的主体)
- 一旦某一个该主体通过其中某一个过滤器的认证,Authentication对象信息被填充,比如:isAuthenticated=true表示该主体通过验证。
- 如果该主体通过了所有的过滤器,仍然没有被认证,在整个过滤器链的最后方有一个FilterSecurityInterceptor过滤器(虽然叫Interceptor,但它是名副其实的过滤器,不是拦截器)。判断Authentication对象的认证状态,如果没有通过认证则抛出异常,通过认证则访问后端API。
- 之后进入响应阶段,FilterSecurityInterceptor抛出的异常被ExceptionTranslationFilter对异常进行相应的处理。比如:用户名密码登录异常,会被引导到登录页重新登陆。
- 如果是登陆成功且没有任何异常,在请求响应中最后一个过滤器SecurityContextPersistenceFilter中将SecurityContext放入session。下次再进行请求的时候,直接从SecurityContextPersistenceFilter的session中取出认证信息。从而避免多次重复认证。
过滤器登录验证细节
随后使用AuthenticationManager 接口对登录认证主体进行authenticate认证。
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication) throwsAuthenticationException;
}
ProviderManager继承于AuthenticationManager是登录验证的核心类。
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
……
private List
……
ProviderManager保管了多个AuthenticationProvider,每一种登录认证方式都可以尝试对登录认证主体进行认证。只要有一种方式被认证成功,Authentication对象就成为被认可的主体。
public interface AuthenticationProvider {
Authentication authenticate(Authentication var1) throws AuthenticationException;
boolean supports(Class<?> var1);
}
RememberMeAuthenticationProvider定义了“记住我”功能的登录验证逻辑
DaoAuthenticationProvider加载数据库用户信息,进行用户密码的登录验证
数据库加载用户信息 DaoAuthenticationProvider
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
完成登录认证之后,将认证完成的Authtication对象(authenticate: true, 有授权列表authority list, 和username信息)放入SecurityContext上下文里面。后续的请求就直接从SecurityContextFilter中获得认证主体,从而访问资源。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具