kcl-co / server-security

本项目是基于Spring Security实现的前后端分离项目,希望能提供一些前后端分离的解决方案给使用此框架(Spring Security)的开发者。

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

server-security

本项目是基于Spring Security实现的前后端分离项目,希望能提供一些前后端分离的解决方案给使用此框架(Spring Security)的开发者。

Security架构

对基于Servlet的应用,Spring Security是通过其Filter(过滤器)来实现的。在应用程序接收到Servlet请求后,容器就会创建一个FilterChain(包含FilterServlet)来进行处理,其中Filter(过滤器)可以阻止请求进一步向下执行或者对请求的参数/返回值进行修改,Servlet则会在所有Filter通过后进行业务处理。

虽然Servlet容器允许以它标准的方式来注册Filter,但是这样却不能识别到Spring所定义的Bean。为了实现这一点,Spring提供了一个Filter实现类DelegatingFilterProxy,它会将Servlet容器的生命周期与SpringApplicationContext桥接在一起,即DelegatingFilterProxy可以通过Servlet容器标准的方式进行注册,同时也能够将所有的工作委托给Spring中实现了FilterBean

因此,在经过Spring Security的一系列封装改造后,它的整体架构演变成下面这样:

                                                                          SecurityFilterChain0
                                                                      +----------------------------+
                                                                      |          /api/**           |
                                                                      |    +------------------+    |
                                                                      |    | Security Filter0 |    |
                                                                      |    +--------+---------+    |
                                                                      |             |              |
              +----------+                           +---------------->         +--------+         |
              |  Client  |                           |                |         |--------|         |
              +----+-----+                           |                |         |--------|         |
 FilterCain        |                                 |                |         |--------|         |
+---------------------------------------+            |                |         |--------|         |
|        +---------v-----------+        |            |                |         +--------+         |
|        |       Filter0       |        |            |                |             |              |
|        +---------+-----------+        |            |                |    +--------v---------+    |
|                  |                    |            |                |    | Security FilterN |    |
|                  |                    |            |                |    +------------------+    |
|   +--------------v----------------+   |            |                |                            |
|   |     DelegatingFilterProxy     |   |       +----+-----+          +----------------------------+
|   | +---------------------------+ |   |       |          |
|   | |      FilterChainProxy     |------------>|  select? |              +--------------------+
|   | +---------------------------+ |   |       |          |              |                    |
|   +-------------------------------+   |       +----+-----+              +--------------------+
|                  |                    |            |                 
|                  |                    |            |                    +--------------------+
|        +---------v-----------+        |            |                    |                    |
|        |       FilterN       |        |            |                    +--------------------+
|        +---------+-----------+        |            |
|                  |                    |            |                    +--------------------+
|                  |                    |            |                    |                    |
|        +---------v-----------+        |            |                    +--------------------+
|        |       Servlet       |        |            |
|        +---------------------+        |            |                     SecurityFilterChainN
+---------------------------------------+            |                +----------------------------+
                                                     |                |           /**              |
                                                     |                |                            |
                                                     |                |         +--------+         |
                                                     |                |         +--------+         |
                                                     |                |             |              |
                                                     +---------------->         +--------+         |
                                                                      |         +--------+         |
                                                                      |             |              |
                                                                      |         +--------+         |
                                                                      |         +--------+         |
                                                                      +----------------------------+

Security核心组件

关于Spring Security的“身份认证”和“权限校验”,在FilterChain过滤器链中首先通过身份认证的Security Filter进行判断,并将认证信息存放到SecurityContext中,然后在后面权限校验的Security Filter中从SecurityContext中获取认证信息进行权限校验。

Security身份认证

对于Authentication身份认证,Spring Secutity提供了很多预设的解决方案,其中最常用的一种就是通过username/password的方式进行认证,它们在实现上主要分为“身份认证”的处理方式和信息存储两个模块,即:

  1. 对于“身份认证”的处理方式,Spring Security主要提供了三种类型,分别是Form LoginBasic AuthenticationDigest Authentication
  2. 对于“身份认证”的信息存储,Spring Security主要提供了四种类型,分别是In-Memory存储、JDBC存储、自定义UserDetailsService存储和LDAP存储。

根据Spring Security的设计理念,这种类型的身份认证(username/password的方式)主要作用于登陆接口(首次登陆)。但是,如果我们通过这种方式实现登陆,就必然会加大对其开发的难度和压力。因此,笔者建议只通过Spring Security实现接口的认证/权限拦截,而要达到这种效果可以通过3种方式实现,即:

  • 通过实现AbstractAuthenticationProcessingFilter抽象类。
  • 通过实现AbstractPreAuthenticatedProcessingFilter抽象类。
  • 通过实现GenericFilterBean抽象类、OncePerRequestFilter抽象类或者Filter接口。

但是AbstractAuthenticationProcessingFilter作为实现UsernamePasswordAuthenticationFilter的抽象类,它定义了(首次)登陆/授权的执行流程。显然,从这样的定义或设计理念来说,它并不适合用于实现接口的认证/权限拦截功能(不推荐)。而GenericFilterBeanOncePerRequestFilter或者Filter等基础类虽然是最灵活的,但是笔者认为应该在现有能力无法提供的情况下才使用它们来实现(不推荐)。而AbstractPreAuthenticatedProcessingFilter主要是用于处理从外部系统获取的认证,我们完全可以将登陆授权接口当作是外部系统,不进行拦截处理。这样看来AbstractPreAuthenticatedProcessingFilter是相当适合我们了,所以笔者主要是基于AbstractPreAuthenticatedProcessingFilter来实现身份认证的功能。

其中,关于AbstractPreAuthenticatedProcessingFilter执行流程图如下所示:

                                           SecurityFilterChain                +----------------------------+
                                           +----------------------------+     |                            |
              +----------+                 |                            |     |                            |
              |  Client  |                 |    +------------------+    |     |                            v
              +----+-----+                 |    | Security Filter0 |    |     |                    +-------+--------+
 FilterCain        |                       |    +--------+---------+    |     |                    | Authentication |
+---------------------------------------+  |             |              |     |                    +-------+--------+
|        +---------v-----------+        |  |         +--------+         |     |                            |
|        |       Filter0       |        |  |         +--------+         |     |                            |
|        +---------+-----------+        |  |             |              |     |                            v
|                  |                    |  | +-----------+------------+ |     |                +-----------+-----------+
|                  |                    |  | |AbstractPreAuthenticated+-------+                | AuthenticationManager |
|   +--------------v----------------+   |  | |   ProcessingFilter     | |                      +-----------+-----------+
|   |     DelegatingFilterProxy     |   |  | +-----------+------------+ |                                  |
|   | +---------------------------+ |   |  |             |              |                                  |
|   | |      FilterChainProxy     +------->+         +--------+         |                                  v
|   | +---------------------------+ |   |  |         +--------+         |                           +------+-------+
|   +-------------------------------+   |  |             |              |                           |Authenticated?|
|                  |                    |  |  +----------v-----------+  |                           +------+-------+
|                  |                    |  |  |AbstractAuthentication|  |                                  |
|        +---------v-----------+        |  |  |  ProcessingFilter    |  |                     failure      |     success
|        |       FilterN       |        |  |  +----------+-----------+  |                 +----------------+-------------------+
|        +---------+-----------+        |  |             |              |                 |                                    |
|                  |                    |  |         +--------+         |                 |                                    |
|                  |                    |  |         +--------+         |  +--------------------------------+  +-----------------------------------+
|        +---------v-----------+        |  |             |              |  | +----------------------------+ |  | +-------------------------------+ |
|        |       Servlet       |        |  |    +--------v---------+    |  | |   SecurityContextHolder    | |  | |     SecurityContextHolder     | |
|        +---------------------+        |  |    | Security FilterN |    |  | +----------------------------+ |  | +-------------------------------+ |
+---------------------------------------+  |    +------------------+    |  | +----------------------------+ |  | +-------------------------------+ |
                                           |                            |  | |AuthenticationFailureHandler| |  | |   ApplicationEventPublisher   | |
                                           +----------------------------+  | +----------------------------+ |  | +-------------------------------+ |
                                                                           +--------------------------------+  | +-------------------------------+ |
                                                                                                               | |  AuthenticationSuccessHandler | |
                                                                                                               | +-------------------------------+ |
                                                                                                               +-----------------------------------+

对于AuthenticationManager,它最常用的实现类就是ProviderManagerProviderManager在执行身份认证时会委托给AuthenticationProvider列表进行判断,在列表中的每一个AuthenticationProvider都有机会去判断身份认证是成功或是失败(只要适配成功),如果当前AuthenticationProvider无法做出判断(不适配)就会继续流向下一个AuthenticationProvider。但是,如果遍历整个列表都找不到适配的AuthenticationProvider,那么就会抛出ProviderNotFoundException异常(表示不支持当前类型的Authentication)。

注意,如果存在AuthenticationProvider适配成功后,无论是认证成功还是认证失败都不会继续适配其他AuthenticationProvider了。

最终,将ProviderManager的实现结构整合AbstractPreAuthenticatedProcessingFilter就演变成这样了:

                                           SecurityFilterChain                +----------------------------+
                                           +----------------------------+     |                            |
              +----------+                 |                            |     |                            |
              |  Client  |                 |    +------------------+    |     |                            v
              +----+-----+                 |    | Security Filter0 |    |     |                    +-------+--------+                AuthenticationPro^iders
 FilterCain        |                       |    +--------+---------+    |     |                    | Authentication |            +-----------------------------+
+---------------------------------------+  |             |              |     |                    +-------+--------+            | +-------------------------+ |
|        +---------v-----------+        |  |         +--------+         |     |                            |                     | | AuthenticationProvider0 | |
|        |       Filter0       |        |  |         +--------+         |     |                            |                     | +------------+------------+ |
|        +---------+-----------+        |  |             |              |     |                            v                     |              |              |
|                  |                    |  | +-----------+------------+ |     |                +-----------+-----------+         |          +--------+         |
|                  |                    |  | |AbstractPreAuthenticated+-------+                |    ProviderManager    +-------->+          |--------|         |
|   +--------------v----------------+   |  | |   ProcessingFilter     | |                      +-----------+-----------+         |          |--------|         |
|   |     DelegatingFilterProxy     |   |  | +-----------+------------+ |                                  |                     |          +--------+         |
|   | +---------------------------+ |   |  |             |              |                                  |                     |              |              |
|   | |      FilterChainProxy     +------->+         +--------+         |                                  v                     | +------------v------------+ |
|   | +---------------------------+ |   |  |         +--------+         |                           +------+-------+             | | AuthenticationProvider0 | |
|   +-------------------------------+   |  |             |              |                           |Authenticated?|             | +-------------------------+ |
|                  |                    |  |  +----------v-----------+  |                           +------+-------+             +-----------------------------+
|                  |                    |  |  |AbstractAuthentication|  |                                  |
|        +---------v-----------+        |  |  |  ProcessingFilter    |  |                     failure      |     success
|        |       FilterN       |        |  |  +----------+-----------+  |                 +----------------+-------------------+
|        +---------+-----------+        |  |             |              |                 |                                    |
|                  |                    |  |         +--------+         |                 |                                    |
|                  |                    |  |         +--------+         |  +--------------------------------+  +-----------------------------------+
|        +---------v-----------+        |  |             |              |  | +----------------------------+ |  | +-------------------------------+ |
|        |       Servlet       |        |  |    +--------v---------+    |  | |   SecurityContextHolder    | |  | |     SecurityContextHolder     | |
|        +---------------------+        |  |    | Security FilterN |    |  | +----------------------------+ |  | +-------------------------------+ |
+---------------------------------------+  |    +------------------+    |  | +----------------------------+ |  | +-------------------------------+ |
                                           |                            |  | |AuthenticationFailureHandler| |  | |   ApplicationEventPublisher   | |
                                           +----------------------------+  | +----------------------------+ |  | +-------------------------------+ |
                                                                           +--------------------------------+  | +-------------------------------+ |
                                                                                                               | |  AuthenticationSuccessHandler | |
                                                                                                               | +-------------------------------+ |
                                                                                                               +-----------------------------------+

Security权限校验

在通过了身份认证的过滤器后(例如,AbstractAuthenticationProcessingFilter),马上就是进一步的权限校验,即Authorization。在实现上,对于权限校验的处理主要涉及了AbstractSecurityInterceptor过滤器。这样,整个Spring Security的执行流程就演变成这样:

                                           SecurityFilterChain
                                           +----------------------------+
                                           |                            |
                                           |    +------------------+    |
                                           |    | Security Filter0 |    |  +------------------------+
                                           |    +--------+---------+    |  |                        |
              +----------+                 |             |              |  |                        |
              |  Client  |                 |         +--------+         |  |                +-------v--------+       +-----------------------+
              +----+-----+                 |         +--------+         |  |                | Authentication +<------+ SecurityContextHolder |
 FilterCain        |                       |             |              |  |                +-------+--------+       +-----------------------+
+---------------------------------------+  |             v              |  |                        |
|        +---------v-----------+        |  | +-----------+------------+ |  |                +-------v--------+
|        |       Filter0       |        |  | |AbstractPreAuthenticated| |  |                |FilterInvocation|
|        +---------+-----------+        |  | |   ProcessingFilter     | |  |                +-------+--------+
|                  |                    |  | +-----------+------------+ |  |                        |
|                  |                    |  |             |              |  |                +-------v--------+       +-----------------------+
|   +--------------v----------------+   |  |         +--------+         |  |                |ConfigAttributes+<------+SecurityMetadataSource |
|   |     DelegatingFilterProxy     |   |  |         +--------+         |  |                +-------+--------+       +-----------------------+
|   | +---------------------------+ |   |  |             |              |  |                        |
|   | |      FilterChainProxy     +------->+  +----------v-----------+  |  |            +-----------v-----------+
|   | +---------------------------+ |   |  |  |AbstractAuthentication|  |  |            | AccessDecisionManager |
|   +-------------------------------+   |  |  |  ProcessingFilter    |  |  |            +-----------+-----------+
|                  |                    |  |  +----------+-----------+  |  |                        |
|                  |                    |  |             |              |  |                 +------v-------+
|        +---------v-----------+        |  |         +--------+         |  |                 | Authorized?  |
|        |       FilterN       |        |  |         +--------+         |  |                 +------+-------+
|        +---------+-----------+        |  |             |              |  |                        |
|                  |                    |  |  +----------v-----------+  |  |            Denied      v       success
|                  |                    |  |  |    FilterSecurity    +-----+            +-----------+-------------+
|        +---------v-----------+        |  |  |     Interceptor      |  |               |                         |
|        |       Servlet       |        |  |  +----------+-----------+  |               v                         v
|        +---------------------+        |  |             |              |  +-------------------------+ +----------+----------+
+---------------------------------------+  |             |              |  |-------------------------| | Continue Processing |
                                           |    +--------v---------+    |  || AccessDeniedException || |  Request Normally   |
                                           |    | Security FilterN |    |  |-------------------------| |                     |
                                           |    +------------------+    |  +-------------------------+ +---------------------+
                                           |                            |
                                           +----------------------------+

其中,FilterSecurityInterceptor就是以Filter的方式来实现权限校验的AbstractSecurityInterceptor实现类。除此之外,Spring Security为了能够更加方便地进行权限校验,其引入了权限的注解式配置(类注解/方法注解)。在实现上,它是通过MethodSecurityInterceptorAbstractSecurityInterceptor的实现类)以动态代理的方式来实现的(不是基于过滤器Filter实现的)。这样,整个Spring Security的执行流程就演变成这样:

                                           SecurityFilterChain
                                           +----------------------------+
                                           |                            |
                                           |    +------------------+    |
                                           |    | Security Filter0 |    |
                                           |    +--------+---------+    |
                                           |             |              |
                                           |         +--------+         |
                                           |         +--------+         |
                                           |             |              |
                                           |             v              |
                                           | +-----------+------------+ |
                                           | |AbstractPreAuthenticated| |
                                           | |   ProcessingFilter     | |
                                           | +-----------+------------+ |
              +----------+                 |             |              |
              |  Client  |                 |         +--------+         |
              +----+-----+                 |         +--------+         |
 FilterCain        |                       |             |              |
+---------------------------------------+  |             v              |
|                  |                    |  |  +----------+-----------+  |
|                  |                    |  |  |AbstractAuthentication|  |
|        +---------v-----------+        |  |  |  ProcessingFilter    |  |
|        |       Filter0       |        |  |  +----------+-----------+  |
|        +---------+-----------+        |  |             |              |   +-----------------------+
|                  |                    |  |         +--------+         |   | SecurityContextHolder |
|                  |                    |  |         +--------+         |   +----------+------------+
|                  |                    |  |             |              |              |
|   +--------------v----------------+   |  |  +----------v-----------+  |      +-------v--------+       +----------------+
|   |     DelegatingFilterProxy     |   |  |  |    FilterSecurity    +-------->+ Authentication +------>+FilterInvocation|
|   | +---------------------------+ |   |  |  |     Interceptor      |  |      +----------------+       +-------+--------+ +----------------------------------------------------------------------+
|   | |      FilterChainProxy     +------->+  +----------+-----------+  |                                       |          |                                                                      |
|   | +---------------------------+ |   |  |             |              |                                       |          | +-----------------------+                                            |
|   +-------------------------------+   |  |             v              |                                       |          | |SecurityMetadataSource |       AbstractSecurityInterceptor          |
|                  |                    |  |    +--------+---------+    |                                       |          | +-----------+-----------+                                            |
|                  |                    |  |    | Security FilterN |    |                                       |          |             |                                                        |
|                  |                    |  |    +------------------+    |                                       |          |     +-------v--------+     +-----------------------+                 |
|        +---------v-----------+        |  |                            |                                       +--------------->+ConfigAttributes+---->+ AccessDecisionManager |                 |
|        |       FilterN       |        |  +----------------------------+                                       |          |     +----------------+     +-----------+-----------+                 |
|        +---------+-----------+        |                                                                       |          |                                        |                             |
|                  |                    |  Spring IoC Container                                                 |          |                                 +------v-------+                     |
|                  |                    |  +----------------------------+                                       |          |                                 | Authorized?  |                     |
|                  |                    |  | MethodSecurityInterceptor  |                                       |          |                                 +------+-------+                     |
|        +---------v-----------+        |  |  +---------------------+   |                                       |          |                                        |                             |
|        |       Servlet       +---------->+  |     Proxy(AOP)      |   |      +----------------+       +-------+--------+ |                            Denied      v       success               |
|        +---------------------+        |  |  |  +---------------+  +--------->+ Authentication +------>+MethodInvocation| |                            +-----------+-------------+               |
|                                       |  |  |  |  plain object |  |   |      +-------+--------+       +----------------+ |                            |                         |               |
|                                       |  |  |  +---------------+  |   |              ^                                   |                            v                         v               |
+---------------------------------------+  |  +---------------------+   |   +----------+------------+                      |               +-------------------------+ +----------+----------+    |
                                           +----------------------------+   | SecurityContextHolder |                      |               |-------------------------| | Continue Processing |    |
                                                                            +-----------------------+                      |               || AccessDeniedException || |  Request Normally   |    |
                                                                                                                           |               |-------------------------| |                     |    |
                                                                                                                           |               +-------------------------+ +---------------------+    |
                                                                                                                           |                                                                      |
                                                                                                                           +----------------------------------------------------------------------+

Security异常处理

另外,对于Spring Security身份认证失败所抛出的AuthenticationException异常和权限校验失败所抛出的AccessDeniedException异常则是通过异常过滤器ExceptionTranslationFilter进行处理的。这样,整个Spring Security的执行流程就演变成这样:

                                           SecurityFilterChain
                                           +----------------------------+
                                           |                            |
                                           |    +------------------+    |
                                           |    | Security Filter0 |    |
              +----------+                 |    +--------+---------+    |
              |  Client  |                 |             |              |
              +----+-----+                 |         +--------+         |
 FilterCain        |                       |         +--------+         |
+---------------------------------------+  |             |              |
|        +---------v-----------+        |  |             v              |
|        |       Filter0       |        |  | +-----------+------------+ |
|        +---------+-----------+        |  | |AbstractPreAuthenticated| |
|                  |                    |  | |   ProcessingFilter     | |
|                  |                    |  | +-----------+------------+ |
|   +--------------v----------------+   |  |             |              |      +-----------------------+
|   |     DelegatingFilterProxy     |   |  |         +--------+         |      |                       |
|   | +---------------------------+ |   |  |         +--------+         |      |                       |
|   | |      FilterChainProxy     +------->+             |              |      |                       |
|   | +---------------------------+ |   |  |  +----------v-----------+  |      |             +---------+---------+
|   +-------------------------------+   |  |  |AbstractAuthentication|  |      |             |Continue Processing|
|                  |                    |  |  |  ProcessingFilter    |  |      |             | Request Normally  |
|                  |                    |  |  +----------+-----------+  |      |             +---------+---------+
|        +---------v-----------+        |  |             |              |      |                       |
|        |       FilterN       |        |  |         +--------+         |      |                       |
|        +---------+-----------+        |  |         +--------+         |      |                       +
|                  |                    |  |             |              |      |           Security Exception Judgment
|                  |                    |  |  +----------+-----------+  |      |       +---------------------------------+
|        +---------v-----------+        |  |  | ExceptionTranslation +---------+       |                                 |
|        |       Servlet       |        |  |  |        Filter        |  |              v                                 |
|        +---------------------+        |  |  +----------+-----------+  |  Start Authentication            Access Denied v
+---------------------------------------+  |             |              |  +----------------------------+  +-------------------------+
                                           |  +----------+-----------+  |  | +------------------------+ |  | +---------------------+ |
                                           |  |    FilterSecurity    |  |  | | SecurityContextHolder  | |  | | AccessDeniedHandler | |
                                           |  |     Interceptor      |  |  | +------------------------+ |  | +---------------------+ |
                                           |  +----------+-----------+  |  | +------------------------+ |  +-------------------------+
                                           |             |              |  | |      RequestCache      | |
                                           |    +--------v---------+    |  | +------------------------+ |
                                           |    | Security FilterN |    |  | +------------------------+ |
                                           |    +------------------+    |  | |AuthenticationEntryPoint| |
                                           |                            |  | +------------------------+ |
                                           +----------------------------+  +----------------------------+

Security实战:前后端分离

在实际开发中,为了能让身份认证和权限校验操作起来更加灵活,笔者建议在过滤器拦截阶段对所有请求实行放行策略,而在AOP拦截阶段通过注解的方式对需要身份认证和权限校验的方法请求进行拦截判断。下面,笔者将基于此设计理念展示代码的实现示例。

Security安全配置

首先需要声明Spring Security的配置类,并进行一系列的属性设置。

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 用于获取身份信息
     */
    private final AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService;
    /**
     * 用于处理身份认证失败的情况
     */
    private final AuthenticationEntryPoint authenticationEntryPoint;
    /**
     * 用于处理权限校验失败的情况
     */
    private final AccessDeniedHandler accessDeniedHandler;

    public WebSecurityConfig(
            AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService,
            AuthenticationEntryPoint authenticationEntryPoint,
            AccessDeniedHandler accessDeniedHandler) {
        this.authenticationUserDetailsService = authenticationUserDetailsService;
        this.authenticationEntryPoint = authenticationEntryPoint;
        this.accessDeniedHandler = accessDeniedHandler;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider = new PreAuthenticatedAuthenticationProvider();
        preAuthenticatedAuthenticationProvider.setPreAuthenticatedUserDetailsService(this.authenticationUserDetailsService);
        auth.authenticationProvider(preAuthenticatedAuthenticationProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // 跨域
                .cors()
                .and()
                // CSRF
                .csrf().disable()
                // header
                .headers()
                .httpStrictTransportSecurity().disable()
                .frameOptions().disable()
                .and()
                // 配置 anonymous
                .anonymous()
                .principal(0)
                .and()
                .addFilter(new JwtPreAuthenticatedProcessingFilter(authenticationManager()))
                // 授权异常
                .exceptionHandling()
                .authenticationEntryPoint(this.authenticationEntryPoint)
                .accessDeniedHandler(this.accessDeniedHandler)
                // 不创建会话
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                // 默认所有请求通过,在需要权限的方法加上安全注解
                .and()
                .authorizeRequests()
                .anyRequest().permitAll()
        ;
    }
}

Security访问身份获取

其中,对于身份信息的获取/设置是通过JwtPreAuthenticatedProcessingFilter实现的,即:

public class JwtPreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter {

    public JwtPreAuthenticatedProcessingFilter(AuthenticationManager authenticationManager) {
        super.setAuthenticationManager(authenticationManager);
    }

    @Override
    protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
        return Optional.ofNullable(request.getHeader(RequestParamsConstants.TOKEN))
                .map(SecurityUtils::parseToken)
                .map(JwtObject::getUserId)
                .orElse(null);
    }

    @Override
    protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
        return request.getHeader(RequestParamsConstants.TOKEN);
    }
}

通过AbstractPreAuthenticatedProcessingFilter所提供的能力,在JwtPreAuthenticatedProcessingFilter的实现上我们只需要提供关于principalcredentials的获取方式即可。

Security访问身份校验

然后在用户身份信息获取完成后,我们就可以对它进行身份认证和权限校验了。而由于在Security配置时对过滤器的拦截阶段实行放行策略,所以我们需要进一步实现/设置以注解的方式对身份认证和权限校验进行拦截判断。

默认情况下,我们可以通过在@Configuration类中声明@EnableGlobalMethodSecurity注解并指定其中对应的属性来开启相关注解的使用,但是经过我们前面的改造和设计使用默认提供的注解显然不那么适合,因为它们难以区分身份认证造成的访问失败还是权限校验所造成的访问失败(只会抛出AccessDeniedException异常)。因此,为了更加灵活地进行身份认证和权限校验,我们需要自定义一些功能注解。

对于自定义的权限注解,我们只需要进行3个处理步骤:

  1. 定义权限注解
  2. 定义权限ConfigAttribute
  3. 定义权限SecurityMetadataSource

其中,关于从SecurityMetadataSource中获取ConfigAttribute属性在SecuredAnnotationSecurityMetadataSource上其实已经构建了一个很好用的框架了,通过它将大大地降低我们定义权限注解的成本。在使用上,我们只需要声明对应的AnnotationMetadataExtractor,定义从注解转换为ConfigAttribute的逻辑,最后通过构造参数传入即可。

基于这种设计思路,下面笔者定义了两个权限注解,即:

@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Auth {
}

public class AuthorizeConfigAttribute implements ConfigAttribute {
    @Override
    public String getAttribute() {
        return "";
    }
}

public class JwtAuthorizeMetadataSource extends SecuredAnnotationSecurityMetadataSource {
    public JwtAuthorizeMetadataSource() {
        super(new AuthorizeMetadataExtractor());
    }

    public JwtAuthorizeMetadataSource(AnnotationMetadataExtractor annotationMetadataExtractor) {
        super(annotationMetadataExtractor);
    }

    private static class AuthorizeMetadataExtractor implements AnnotationMetadataExtractor<Auth> {

        public Collection<ConfigAttribute> extractAttributes(Auth auth) {
            return Collections.singletonList(new AuthorizeConfigAttribute());
        }
    }
}
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Permission {
    /**
     * Returns the list of security configuration attributes (e.g.&nbsp;permission1, permission2).
     *
     * @return String[] The secure method attributes
     */
    String[] value();
}

public class PermissionConfigAttribute extends SecurityConfig {

    public PermissionConfigAttribute(String config) {
        super(config);
    }
}

public class JwtPermissionMetadataSource extends SecuredAnnotationSecurityMetadataSource {

    public JwtPermissionMetadataSource() {
        super(new PermissionMetadataExtractor());
    }

    public JwtPermissionMetadataSource(AnnotationMetadataExtractor annotationMetadataExtractor) {
        super(annotationMetadataExtractor);
    }

    private static class PermissionMetadataExtractor implements AnnotationMetadataExtractor<Permission> {

        public Collection<ConfigAttribute> extractAttributes(Permission permission) {
            return Arrays.stream(permission.value())
                    .map(PermissionConfigAttribute::new)
                    .collect(Collectors.toList());
        }
    }
}

在完成权限注解、权限ConfigAttribute和权限SecurityMetadataSource的定义后,我们将它们注册到GlobalMethodSecurityConfiguration中,即:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {

    /**
     * 重写AccessDecisionManager。让自定义的AuthorizeVoter和PermissionVoter具有更高的优先级。
     */
    @Override
    protected AccessDecisionManager accessDecisionManager() {
        AccessDecisionManager accessDecisionManager = super.accessDecisionManager();
        if (Objects.nonNull(accessDecisionManager)
                && accessDecisionManager instanceof AffirmativeBased) {
            AffirmativeBased affirmativeBased = (AffirmativeBased) accessDecisionManager;
            List<AccessDecisionVoter<?>> voters = new ArrayList<>();
            voters.add(new AuthorizeVoter());
            voters.add(new PermissionVoter());
            voters.addAll(affirmativeBased.getDecisionVoters());
            return new UnanimousBased(voters);
        }

        return accessDecisionManager;
    }

    /**
     * 重写MethodSecurityMetadataSource,让自定义的AuthorizeMetadataSource和PermissionMetadataSource具有更高的优先级
     */
    @Bean
    @Override
    public MethodSecurityMetadataSource methodSecurityMetadataSource() {
        DelegatingMethodSecurityMetadataSource delegating = (DelegatingMethodSecurityMetadataSource) super.methodSecurityMetadataSource();
        List<MethodSecurityMetadataSource> metadataSourceList = new ArrayList<>();
        metadataSourceList.add(new JwtAuthorizeMetadataSource());
        metadataSourceList.add(new JwtPermissionMetadataSource());
        metadataSourceList.addAll(delegating.getMethodSecurityMetadataSources());
        return new JwtDelegatingMetadataSource(metadataSourceList);
    }

}

需要注意,在继承GlobalMethodSecurityConfiguration重写时需要把注解@EnableGlobalMethodSecurity放到其类上修饰,否则在加入@Configuration注解进行注册时会报Bean已重复错误。

最后,在完成定义和注册后我们就可以在对应的方法上声明校验身份的@Auth注解或者校验权限的@Permission注解,即:

@RestController
@Api(tags = "访问路由")
@RequestMapping(value = "/example")
public class ExampleController {

    @ApiOperation(value = "匿名访问")
    @PostMapping(value = "/v1/anonymousAccess")
    public void anonymousAccess() {
    }

    @Auth
    @ApiOperation(value = "认证访问")
    @PostMapping(value = "/v1/authAccess")
    public void authAccess() {
    }

    @Auth
    @ApiOperation(value = "权限访问")
    @Permission(value = {"permission:access"})
    @PostMapping(value = "/v1/permissionAccess")
    public void permissionAccess() {
    }

}

更多

通过阅读本文应该对Spring Security的整体结构有一个基本的认识了,如果读者想更进一步了解更多详情,可阅读笔者的博客Spring Security快速入门》

About

本项目是基于Spring Security实现的前后端分离项目,希望能提供一些前后端分离的解决方案给使用此框架(Spring Security)的开发者。

License:Apache License 2.0


Languages

Language:Java 100.0%