从零搭建若依(Ruoyi-Vue)管理系统(10)--Spring Security核心内容梳理

文章目录

    • 1. 选型原因
    • 2.Spring Secutity核心内容
      • 2.1 Spring Secutity中的用户信息
      • 2.2 密码加密
      • 2.3 Spring Security的配置
      • 2.4 认证过程
      • 2.5 过滤器和过滤链
      • 2.6 权限相关

本章是Spring Security理论和概念的东西,没有实际的搭建产出,为下节做个铺垫。只是介绍Spring Security一些核心要用到的东西,Spring Security的功能还是很强大的,有兴趣可以系统的学习和了解

历史遗留TODO:

  • 第四章
  1. 登录日志还未实现。(到登录和权限模块完成)
  2. LogAspect从缓存获取当前的用户信息使用模拟的数据(到登录和权限模块完成)

本章将留下TODO:

本章将解决TODO:

1. 选型原因

Spring Security:是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。

具有以下优点:

  1. Spring Security基于Spring开发,项目如果使用Spring作为基础,配合Spring Security做权限更加方便,社区资源十分丰富!
  2. 具有丰富全面的安全相关特性,业务隔离度好。

2.Spring Secutity核心内容

2.1 Spring Secutity中的用户信息

  1. UserDetailsService

image-20210311114229376

该方法很容易理解: 通过用户名来加载用户 。这个方法主要用于从系统数据中查询并加载具体的用户到 Spring Security中。

在开发中我们一般定义一个这个接口的实现类,自定义loadUserByUsername方法,实现从数据源获取用户,加载用户信息。也可以在其中实现一些校验用户逻辑。

例如:

自己Test中的例子:

从零搭建若依(Ruoyi-Vue)管理系统(10)--Spring Security核心内容梳理_第1张图片

若依中的使用:

从零搭建若依(Ruoyi-Vue)管理系统(10)--Spring Security核心内容梳理_第2张图片

  1. UserDetails:

从上面 UserDetailsService 可以知道最终交给Spring Security的是UserDetails 。该接口是提供用户信息的核心接口。该接口实现仅仅存储用户的信息。后续会将该接口提供的用户信息封装到认证对象 Authentication 中去。

UserDetails中默认提供:

  • 用户的权限集, 默认需要添加 ROLE_ 前缀
  • 用户的加密后的密码, 不加密会使用 {noop} 前缀
  • 应用内唯一的用户名
  • 账户是否过期
  • 账户是否锁定
  • 凭证是否过期
  • 用户是否可用

从零搭建若依(Ruoyi-Vue)管理系统(10)--Spring Security核心内容梳理_第3张图片

在我们自己的项目中,我们要定义个用户类实现该接口,在该用户类中我们可以扩展更多的用户信息,比如手机、邮箱等等

  1. UserDetailsServiceAutoConfiguration:

(若依中未使用)

源码:

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({AuthenticationManager.class})
@ConditionalOnBean({ObjectPostProcessor.class})
@ConditionalOnMissingBean(
    value = {AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class},
    type = {"org.springframework.security.oauth2.jwt.JwtDecoder", "org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector"}
)
public class UserDetailsServiceAutoConfiguration {
    private static final String NOOP_PASSWORD_PREFIX = "{noop}";
    private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern.compile("^\\{.+}.*$");
    private static final Log logger = LogFactory.getLog(UserDetailsServiceAutoConfiguration.class);

    public UserDetailsServiceAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
        type = {"org.springframework.security.oauth2.client.registration.ClientRegistrationRepository"}
    )
    @Lazy
    public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties, ObjectProvider<PasswordEncoder> passwordEncoder) {
        User user = properties.getUser();
        List<String> roles = user.getRoles();
        return new InMemoryUserDetailsManager(new UserDetails[]{org.springframework.security.core.userdetails.User.withUsername(user.getName()).password(this.getOrDeducePassword(user, (PasswordEncoder)passwordEncoder.getIfAvailable())).roles(StringUtils.toStringArray(roles)).build()});
    }

    private String getOrDeducePassword(User user, PasswordEncoder encoder) {
        String password = user.getPassword();
        if (user.isPasswordGenerated()) {
            logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));
        }

        return encoder == null && !PASSWORD_ALGORITHM_PATTERN.matcher(password).matches() ? "{noop}" + password : password;
    }
}

我们来简单解读一下该类,从 @Conditional 系列注解我们知道,该类在类路径下存在 AuthenticationManager 或者在Spring 容器中存在Bean ObjectPostProcessor 并且不存在Bean AuthenticationManager , AuthenticationProvider , UserDetailsService 的情况下生效。 千万 不要纠结这些类干嘛用的! 该类只初始化了一个 UserDetailsManager 类型的Bean。 UserDetailsManager 类型负责对安全用户实体抽象 UserDetails 的增删查改操作。同时还继承了 UserDetailsService 接口。

从零搭建若依(Ruoyi-Vue)管理系统(10)--Spring Security核心内容梳理_第4张图片

明白了上面这些让我们把目光再回到 UserDetailsServiceAutoConfiguration 上来。该类初始化了 一个名为 InMemoryUserDetailsManager 的内存用户管理器。该管理器通过配置注入了一个默认的 UserDetails 存在内存中,在项目使用中就是我们上面自己实现的继承UserDetails的User类 ,每次启动 user 都是动态生成的。

我们定义自己的 UserDetailsManager Bean就可以实现我们需要的用户管理逻辑

Spring Secutity提供了JdbcUserDetailsManager,该类继承 UserDetailsManager实现基于JDBC的用户管理逻辑。

(但是如果如此使用,就相当于UserDetails 对应一个数据库的表,但它其实不是一个实体类(enyity),只是一个业务对象(bo)。实际使用中我们还是有个User类对应数据库表,通过User类的Service操作用户数据)

2.2 密码加密

  1. 在Spring Security中有一个密码编码解码器PasswordEncoder接口,并且有多个代表用不同加密算法的实现类
从零搭建若依(Ruoyi-Vue)管理系统(10)--Spring Security核心内容梳理_第5张图片

默认的加密算法是bcrypt,对应BCryptPasswordEncoder

  1. 其中DelegatingPasswordEncoder委托密码编码器

所含内容如图:

从零搭建若依(Ruoyi-Vue)管理系统(10)--Spring Security核心内容梳理_第6张图片

  • idForEncode:通过id来匹配编码器,该id不能是 {} 包括的

  • DelegatingPasswordEncoder::初始化传入,用来提供默认的密码编码器。

  • passwordEncoderForEncode:通过上面 idForEncode 所匹配到的 PasswordEncoder 用来对密码进行编码 。

  • upgradeEncoding密码升级。

  • idToPasswordEncoder:用来维护多个 idForEncode 与具体 PasswordEncoder 的映射关系。 DelegatingPasswordEncoder 初始化时 装载进去,会在初始化时进行一些规则校验。

  • PasswordEncoder defaultPasswordEncoderForMatches = new UnmappedIdPasswordEncoder():默认的密码匹配器,上面的 Map idToPasswordEncoder中都不存在就用它来执行 matches 方法进行匹配验证。这是一个内部类实现。

  • encode编码方法

    @Override
    public String encode(CharSequence rawPassword) {
       return PREFIX + this.idForEncode + SUFFIX + this.passwordEncoderForEncode.encode(rawPassword);
    }
    

    从上面源码可以看出来通过 DelegatingPasswordEncoder 编码后的密码是遵循一定的规则的,遵循 {idForEncode}encodePassword 。也就是前缀 {} 包含了编码的方式再拼接上该方式编码后的密码串。注意:对相同字符串每次加密生成的结果都不同!

    例如:(12345678用bcrypt算法编码后的结果:)

    12345678  --(bcrypt)-->  {bcrypt}$2a$10$XEBcwHqwLYXrbyqN2r9T2..dTmpv23RKi4SxAc4vyyt7ZZh30slAy
    ```
    
    
  • matches:密码匹配方法:

    @Override
    public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
       if (rawPassword == null && prefixEncodedPassword == null) {
          return true;
       }
       String id = extractId(prefixEncodedPassword);
       PasswordEncoder delegate = this.idToPasswordEncoder.get(id);
       if (delegate == null) {
          return this.defaultPasswordEncoderForMatches
             .matches(rawPassword, prefixEncodedPassword);
       }
       String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
       return delegate.matches(rawPassword, encodedPassword);
    }
    

    密码匹配通过传入原始密码和遵循 {idForEncode}encodePassword 规则的密码编码串。通过获取编 码方式id (idForEncode) 来从 DelegatingPasswordEncoder 中的映射集合 idToPasswordEncode 中获取具体的 PasswordEncoder 进行匹配校验。找不到就使用 UnmappedIdPasswordEncoder

  1. PasswordEncoderFactories 密码器静态工厂

制造 PasswordEncoder 。而且还是个静态工厂只提供了初始化 DelegatingPasswordEncoder 的方法。该类的主要方法:

@SuppressWarnings("deprecation")
public static PasswordEncoder createDelegatingPasswordEncoder() {
   String encodingId = "bcrypt";
   Map<String, PasswordEncoder> encoders = new HashMap<>();
   encoders.put(encodingId, new BCryptPasswordEncoder());
   encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
   encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
   encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
   encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
   encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
   encoders.put("scrypt", new SCryptPasswordEncoder());
   encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
   encoders.put("SHA-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
   encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());
   encoders.put("argon2", new Argon2PasswordEncoder());

   return new DelegatingPasswordEncoder(encodingId, encoders);
}

从上面可以非常具体地看出来 DelegatingPasswordEncoder提供的密码编码方式。默认采用了 bcrypt 进行编码。

因此,在 DelegatingPasswordEncoder 中也可以实现新旧密码加密校验方式同时存在,并且实现自动更新老密码的编码方式。

例如:旧密码编码方式是SHA-1,新的方式是bcrypt。这时候验证时,对于未登录的过的密码用新编码方式编然后存入数据库;老用户在数据库中存储的密码是用老编码方式编码的,这时候校验时,可以用老编码方式校验,成功验证后,把老密码再用新编码方式存入数据库。

示例:

从零搭建若依(Ruoyi-Vue)管理系统(10)--Spring Security核心内容梳理_第7张图片

2.3 Spring Security的配置

  1. SpringBootWebSecurityConfiguration:
@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({WebSecurityConfigurerAdapter.class})
@ConditionalOnMissingBean({WebSecurityConfigurerAdapter.class})
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
public class SpringBootWebSecurityConfiguration {
    public SpringBootWebSecurityConfiguration() {
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @Order(2147483642)
    static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {
        DefaultConfigurerAdapter() {
        }
    }
}

这个类是Spring Security 对 Spring Boot Servlet Web 应用的默认配置。核心在于 WebSecurityConfigurerAdapter 适配器。从 @ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class) 我们就能看出 WebSecurityConfigurerAdapter 是安全配置的核心。 默认情况下 DefaultConfigurerAdapter 将以 SecurityProperties.BASIC_AUTH_ORDER (-5 ) 的顺序注入 Spring IoC 容器,这是个空实现。 如果我们需要实现对Spring Security的配置可以通过继承 WebSecurityConfigurerAdapter 来实现。

在若依中:

从零搭建若依(Ruoyi-Vue)管理系统(10)--Spring Security核心内容梳理_第8张图片

  1. 自定义SecurityConfig

上面也提到首先要继承WebSecurityConfigurerAdapter,其次最常用的是实现configure(AuthenticationManagerBuilder auth)configure(WebSecurity web)configure(HttpSecurity http)三个方法实现我们对Spring Security的自定义安全配置。

从零搭建若依(Ruoyi-Vue)管理系统(10)--Spring Security核心内容梳理_第9张图片

  • void configure(AuthenticationManagerBuilder auth) 用来配置认证管理器 AuthenticationManager

  • void configure(WebSecurity web)用来配置 WebSecurity 。而 WebSecurity 是基于Servlet Filter用来配置 springSecurityFilterChain 。而 springSecurityFilterChain 又被委托给了 Spring Security 核心过滤器 Bean DelegatingFilterProxy 。 相关逻辑你可以在 WebSecurityConfiguration 中找到。我们一般不会过多来自定义 WebSecurity , 使用较多的使其 ignoring() 方法用来忽略 Spring Security 对静态资源的控制。

  • void configure(HttpSecurity http) 这个是我们使用最多的,用来配置 HttpSecurityHttpSecurity 用于构建一个安全过滤器链 SecurityFilterChain SecurityFilterChain 最终 被注入核心过滤器 。HttpSecurity 有许多我们需要的配置。我们可以通过它来进行自定义安全访问策略。

2.4 认证过程

从零搭建若依(Ruoyi-Vue)管理系统(10)--Spring Security核心内容梳理_第10张图片

(看mk网教程的一张图)

  1. 认证过滤器UsernamePasswordAuthenticationFilter:它的作用是拦截登录请求并获取账号和 密码,然后把账号密码封装到认证凭据 UsernamePasswordAuthenticationToken 中,然后把凭据交 给特定配置的 AuthenticationManager 去作认证。

从零搭建若依(Ruoyi-Vue)管理系统(10)--Spring Security核心内容梳理_第11张图片

​ (图片来源《Spring Security实战干货》)

理解了 UsernamePasswordAuthenticationFilter 工作流程后可以做这些事情:

  • 定制我们的登录请求URI和请求方式。

  • 登录请求参数的格式定制化,比如可以使用 JSON格式提交甚至几种并存。

  • 将用户名和密码封装入凭据 UsernamePasswordAuthenticationToken ,定制业务场景需要的特殊凭据。

  1. 认证管理器AuthenticationManager:这个接口方法非常奇特,入参和返回值的类型都是 Authentication 。该接口的作用是对用户的未授信凭据进行认证,认证通过则返回授信状态的凭据,否则将抛出认证异常AuthenticationException

认证过程:

AuthenticationManager 的实现 ProviderManager 管理了众多的 AuthenticationProvider 。每 一个 AuthenticationProvider 都只支持特定类型的 Authentication ,然后是对适配到的 Authentication 进行认证,只要有一个 AuthenticationProvider 认证成功,那么就认为认证成功,所有的都没有通过才认为是认证失败。认证成功后的 Authentication 就变成授信凭据,并触发认证成功的事件。认证失败的就抛出异常触发认证失败的事件。

从零搭建若依(Ruoyi-Vue)管理系统(10)--Spring Security核心内容梳理_第12张图片

认证管理器 AuthenticationManager 针对特定的 Authentication 提供了特定的 认证功能,我们可以借此来实现多种认证并存(多因子登录)。

2.5 过滤器和过滤链

Spring Security 以一个单 Filter(FilterChainProxy) 存在于整个过滤器链中,而 这个 FilterChainProxy 实际内部代理着众多的 Spring Security Filter 。

从零搭建若依(Ruoyi-Vue)管理系统(10)--Spring Security核心内容梳理_第13张图片

Spring Security 内置过滤器:

  • ChannelProcessingFilter:通常是用来过滤哪些请求必须用 https 协议, 哪些请求必须用 http 协议,哪些请求随便用哪个协议都行。
  • ConcurrentSessionFilter:主要用来判断 session 是否过期以及更新最新的访问时间。
  • WebAsyncManagerIntegrationFilter:用于集成SecurityContext到Spring异步执行机制中的 WebAsyncManager。用来处理异步请求的安全上下文。
  • SecurityContextPersistenceFilter:主要控制 SecurityContext 的在一次请求中的生命周期 。 请求来临时,创建 SecurityContext 安全上下文信息,请求结束时清空 SecurityContextHolder 。
  • HeaderWriterFilter: HeaderWriterFilter 用来给 http 响应添加一些 Header ,比如 X-Frame-Options , X-XSS- Protection ,X-Content-Type-Options 。
  • CorsFilter:跨域相关的过滤器。这是 Spring MVC Java 配置和 XML 命名空间 CORS 配置的替代方法, 仅对依赖 于 spring-web 的应用程序有用(不适用于 spring-webmvc )或 要求在 javax.servlet.Filter 级别 进行CORS检查的安全约束链接。
  • CsrfFilter:用于防止 csrf 攻击,前后端使用json交互需要注意的一个问题。
  • LogoutFilter:处理注销的过滤器。
  • OAuth2AuthorizationRequestRedirectFilter:这个需要依赖 spring-scurity-oauth2 相关的模块。该过滤器是处理 OAuth2 请求首选重定向相关逻辑的。
  • Saml2WebSsoAuthenticationRequestFilter:这个需要用到 Spring Security SAML 模块,这是一个基于SMALSSO 单点登录请求认证过滤器。
  • X509AuthenticationFilter: X509 认证过滤器。
  • AbstractPreAuthenticatedProcessingFilter: 处理经过预先认证的身份验证请求的过滤器的 基类其中认证主体已经由外部系统进行了身份验证。目的只是从传入请求中提取主体上的必要信息, 而不是对它们进行身份验证。可以继承该类来具体实现并通过HttpSecurity.addFilter 方法来添加自定义 AbstractPreAuthenticatedProcessingFilter
  • CasAuthenticationFilter: CAS 单点登录认证过滤器 。依赖 Spring Security CAS 模块。
  • OAuth2LoginAuthenticationFilter:这个需要依赖 spring-scurity-oauth2 相关的模块。 OAuth2 登录认证过滤器。处理通过 OAuth2 进行认证登录的逻辑。
  • Saml2WebSsoAuthenticationFilter:这个需要用到 Spring Security SAML 模块,这是一个基于 SMAL 的SSO 单点登录认证过滤器。
  • UsernamePasswordAuthenticationFilter:处理用户以及密码认证的核心过滤器。认证请求提交的 username 和 password ,被封装成 token 进行一系列的认证,便是主要通过这个过滤器完成的,在 表单认证的方法中,这是最最关键的过滤器。
  • OpenIDAuthenticationFilter:基于 OpenID 认证协议的认证过滤器。 你需要在依赖中依赖额外的相关模块才能启用它。
  • DefaultLoginPageGeneratingFilter:生成默认的登录页。默认 /login
  • DefaultLogoutPageGeneratingFilter:生成默认的退出页。 默认/logout
  • DigestAuthenticationFilter: Digest 身份验证是 Web 应用程序中流行的可选的身份验证机制 。 DigestAuthenticationFilter 能够处理 HTTP 头中显示的摘要式身份验证凭据。
  • BasicAuthenticationFilter: Digest 身份验证是 Web 应用程序中流行的可选的身份验证机制 。验证处理Basic Auth HTTP头中的数据。
  • RequestCacheAwareFilter:用于用户认证成功后,重新恢复因为登录被打断的请求。当匿名访问一个需要授权的资源时。会跳转到 认证处理逻辑,此时请求被缓存。在认证逻辑处理完毕后,从缓存中获取最开始的资源请求进行再次请 求。
  • SecurityContextHolderAwareRequestFilter:用来 实现 j2ee Servlet Api 一些接口方法, 比如 getRemoteUser 方法、isUserInRole 方法, 在使用 Spring Security 时其实就是通过这个过滤器来实现的。
  • JaasApiIntegrationFilter:适用于 JAAS (Java 认证授权服务)。 如果 SecurityContextHolder 中拥有的 Authentication 是一个JaasAuthenticationToken,那么该 JaasApiIntegrationFilter 将使用包含在 JaasAuthenticationToken 中的 Subject 继续执行 FilterChain
  • RememberMeAuthenticationFilter: 处理 记住我 功能的过滤器。
  • AnonymousAuthenticationFilter:匿名认证过滤器。 对于 Spring Security 来说,所有对资源的访问都是有 Authentication 的。对于无需登录(UsernamePasswordAuthenticationFilter )直接可以访问的资源,会授予其匿名用 户身份 。
  • SessionManagementFilter: Session 管理器过滤器,内部维护了一个SessionAuthenticationStrategy 用于管理 Session 。
  • ExceptionTranslationFilter:主要来传输异常事件。
  • FilterSecurityInterceptor:这个过滤器决定了访问特定路径应该具备的权限,访问的用户的角色,权限是什么?访问的路径需要什 么样的角色和权限?这些判断和处理都是由该类进行的。
  • SwitchUserFilter: 用来做账户切换的。默认的切换账号的 url 为 /login/impersonate ,默认注 销切换账号的 url 为 /logout/impersonate ,默认的账号参数为 username 。
    你可以通过此类实现自定义的账户切换。

向项目中添加过滤器:

在配置文件的configure(HttpSecurity httpSecurity)方法中:

从零搭建若依(Ruoyi-Vue)管理系统(10)--Spring Security核心内容梳理_第14张图片

4个方法分别是:addFilter–添加过滤器;addFilterAfter–把过滤器添加到某过滤器之后;addFilterAt–替代某过滤器;addFilterBefore–把过滤器添加到某过滤器之前

2.6 权限相关

一、基于配置表达式控制 URL 路径

在继承WebSecurityConfigurerAdapter 的配置类中的configure(HttpSecurity http)中进行配置。

例如:

protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/admin/**").hasRole("admin")
            .antMatchers("/user/**").hasAnyRole("admin", "user")
            .anyRequest().authenticated()
            .and()
            ...
}

常用的配置可选项:

表达式 备注
hasRole 用户具备某个角色即可访问资源
hasAnyRole 用户具备多个角色中的任意一个即可访问资源
hasAuthority 类似于 hasRole
hasAnyAuthority 类似于 hasAnyRole
permitAll 统统允许访问
denyAll 统统拒绝访问
isAnonymous 判断是否匿名用户
isAuthenticated 判断是否认证成功
isRememberMe 判断是否通过记住我登录的
isFullyAuthenticated 判断是否用户名/密码登录的
principle 当前用户
authentication 从 SecurityContext 中提取出来的用户对象

二、基于注解的接口权限控制

我们可以在任何 @Configuration 实例上使用 @EnableGlobalMethodSecurity 注解来启用全局方 法安全注解功能

例如:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true,jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    ...
    ...
}
  • 设置 prePostEnabled 为 true ,则开启了基于表达式 的方法安全控制。

这个配置开启了四个注解,分别是:

  1. @PreAuthorize:在标记的方法调用之前,通过表达式来计算是否可以授权访问。

示例:

@Service
public class TestService {
	// 只有当前登录用户名为 Gangbb 的用户才可以访问该方法。
    @PreAuthorize("principal.username.equals('Gangbb')")
    public String hello() {
        return "hello";
    }
    
    //用户名开头为 Gangbb 的用户才能访问。
    @PreAuthorize("principal.username.startsWith('Gangbb')")
    public String hello() {
        return "hello";
    }
	
	// 访问该方法的用户必须具备 ROLE_ADMIN 角色。
    @PreAuthorize("hasRole('ADMIN')")
    public String admin() {
        return "admin";
    }
	//*******************以下是基于SpEL 表达式****************
    
	// 表示访问该方法的 age 参数必须大于 98,否则请求不予通过。
    @PreAuthorize("#age>98")
    public String getAge(Integer age) {
        return String.valueOf(age);
    }
    
    //  入参id 必须同当前的用户名相同。
    @PreAuthorize("#id.equals(principal.username)")
    public String getId(Integer age) {
        return String.valueOf(age);
    }
    
    //......更多关于SpEL 表达式可参考官方文档
}
  1. @PostAuthorize:在标记的方法调用之后,通过表达式来计算是否可以授权访问。该注解是针对 @PreAuthorize 。区别 在于先执行方法。而后进行表达式判断。如果方法没有返回值实际上等于开放权限控制;如果有返回值 实际的结果是用户操作成功但是得不到响应。

  2. @PreFilter:基于方法入参相关的表达式,对入参进行过滤。

  3. @PostFilter:和 @PreFilter 不同的是, 基于返回值相关的表达式,对返回值进行过滤。分页慎用! 该过程发生接口进行数据返回之前。

示例:

测试数据: [“Gangbb”, “GangAA”, “Hangbb”] 都有ROLE_ADMIN 角色

// filterObject表示要过滤的元素对象。 如下会过滤掉 Gangbb GangAA,只通过 Hangbb
@PreFilter(value = "filterObject.startsWith('G')",filterTarget = "ids") 

// 如下配置都会通过。如果都没有ROLE_ADMIN 角色则过滤掉Hangbb
@PreFilter("hasRole('ADMIN') or filterObject.startsWith('H')")

// 对集合进行过滤,只返回后缀为 2 的元素
@PostFilter("filterObject.lastIndexOf('2')!=-1")
public List<String> getAllUser() {
    List<String> users = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        users.add("javaboy:" + i);
    }
    return users;
}

// 由于有两个集合,因此使用 filterTarget 指定过滤对象。
@PreFilter(filterTarget = "ages",value = "filterObject%2==0")
public void getAllAge(List<Integer> ages,List<String> users) {
    System.out.println("ages = " + ages);
    System.out.println("users = " + users);
}

  • 设置 securedEnabled 为 true ,就开启了角色注解 @Secured ,该注解功能要简单的多,默认情况下只能基于角色(默认需要带前缀 ROLE_ )集合来进行访问控制决策。
    该注解的机制是只要其声明的角色集合(value )中包含当前用户持有的任一角色就可以访问。也就是 用户的角色集合和@Secured注解的角色集合要存在非空的交集。 不支持使用 SpEL 表达式进行决策。

  • 设置 jsr250Enabled 为 true ,就开启了 JavaEE 安全 注解中的以下三个:

  1. @DenyAll 拒绝所有的访问
  2. @PermitAll 同意所有的访问
  3. @RolesAllowed 用法和@Secured 一样。

三、动态权限控制

这个若依中未有使用,也挺复杂,后续有时间再详细分析。

你可能感兴趣的:(#,从零搭建若依管理系统,java实战开发,spring,boot,java)