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中获得认证主体,从而访问资源。