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:基于数据库的身份验证提供者。它从数据库中检索用户信息并与提供的凭据进行比较。
用户详情服务:

UserDetailsS​​ervice接口:用于从数据存储中加载用户详细信息。Spring Security使用UserDetailsService来获取用户的详细信息,例如用户名、密码和权限。
密码编码器:

PasswordEncoder接口:用于加密用户的密码。Spring Security推荐存储加密后的密码,以提高安全性。
BCryptPasswordEncoder:常用的密码加密实现之一。
授权(授权):

GrantedAuthority接口:表示授予用户的权限。权限可以是角色或其他自定义标识。
GrantedAuthorityImpl: GrantedAuthority接口的简单实现。

AccessDecisionManager接口:决定用户是否有权限特定执行操作。

Voter接口:用于投票决定是否授予用户权限。

安全上下文持有者
用于在应用程序的不同部分之间的交付和存储

SecurityContext
安全上下文:
包含关于当前身份验证和授权的信息。可以通过SecurityContextHolder访问。

FilterChainProxy
用于定义安全过滤器链,处理HTTP请求的安全性。

WebSecurityConfigurerAdapter
用于配置Spring Security的filter。开发者可以通过继承WebSecurityConfigurerAdapter来配置不同的安全规则。
@Secured、@PreAuthorize、@RolesAllowed等注解:

登录认证基于过滤器链

img

  1. 贯穿于整个过滤器链始终有一个上下文对象SecurityContext和一个Authentication对象(登录认证的主体)
  2. 一旦某一个该主体通过其中某一个过滤器的认证,Authentication对象信息被填充,比如:isAuthenticated=true表示该主体通过验证。
  3. 如果该主体通过了所有的过滤器,仍然没有被认证,在整个过滤器链的最后方有一个FilterSecurityInterceptor过滤器(虽然叫Interceptor,但它是名副其实的过滤器,不是拦截器)。判断Authentication对象的认证状态,如果没有通过认证则抛出异常,通过认证则访问后端API。
  4. 之后进入响应阶段,FilterSecurityInterceptor抛出的异常被ExceptionTranslationFilter对异常进行相应的处理。比如:用户名密码登录异常,会被引导到登录页重新登陆。
  5. 如果是登陆成功且没有任何异常,在请求响应中最后一个过滤器SecurityContextPersistenceFilter中将SecurityContext放入session。下次再进行请求的时候,直接从SecurityContextPersistenceFilter的session中取出认证信息。从而避免多次重复认证。

过滤器登录验证细节

img

img

随后使用AuthenticationManager 接口对登录认证主体进行authenticate认证。

public interface AuthenticationManager {
Authentication authenticate(Authentication authentication) throwsAuthenticationException;
}
ProviderManager继承于AuthenticationManager是登录验证的核心类。

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
……
private List providers;
……
ProviderManager保管了多个AuthenticationProvider,每一种登录认证方式都可以尝试对登录认证主体进行认证。只要有一种方式被认证成功,Authentication对象就成为被认可的主体。

public interface AuthenticationProvider {
Authentication authenticate(Authentication var1) throws AuthenticationException;
boolean supports(Class<?> var1);
}
RememberMeAuthenticationProvider定义了“记住我”功能的登录验证逻辑
DaoAuthenticationProvider加载数据库用户信息,进行用户密码的登录验证

数据库加载用户信息 DaoAuthenticationProvider

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

img

完成登录认证之后,将认证完成的Authtication对象(authenticate: true, 有授权列表authority list, 和username信息)放入SecurityContext上下文里面。后续的请求就直接从SecurityContextFilter中获得认证主体,从而访问资源。

img

posted @ 2023-11-21 10:52  庭有奇树  阅读(32)  评论(0编辑  收藏  举报