Communications Security (D) between the micro-services -JWT optimization of the log, error handling, limiting and JWT execution process of combing after transformation

  We've already completed the authentication and authorization by the transformation of JWT, we can see that the filter code no authentication and authorization (Filter), and consists essentially of filter SpringSecurity to take over, and then we look at how coupled with our own logic on SpringSecurity the filter chain, such as logs and limiting.

1, a filter was added audit filter chain on SpringSecurity

1.1, create a log filter, because before we position our previous audit in accordance with the mechanism, then take the log filter into the authentication, authorization. JWT authentication token will filter into Authentication, then placed in a security context, getPrincipal () method to get to the login user name is.

/**
 * 审计过滤器
 *
 * @author caofanqi
 * @date 2020/2/9 22:06
 */
@Slf4j
public class GatewayAuditLogFilter extends OncePerRequestFilter {


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        String username = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

        log.info("1、create log for :{}",username);

        filterChain.doFilter(request,response);

        log.info("2、update log to success");

    }

}

1.2, added to the log filter SpringSecurity filter chain, before authorization to be added after the authentication. Security configuration based, on the filter chain SpringSecurity add filter has four methods, addFilterBefore added prior to a filter, after adding a filter addFilterAfter, addFilterAt replace a filter, addFilter added to the chain .

   Because both have a fixed order of execution of the filter on the SpringSecurity filter chain execution, we put our log filter is added before ExceptionTranslationFilter filter, the filter is abnormal processing filter, as the filter in the final authorization Lane, if you have not seen authorize an exception will be thrown 401, 403 is processed by this filter.

/ ** 
 * gateway resource server configuration 
 * 
 * @author caofanqi 
 * @date 2020/2/8 22:30 
 * / 
@Configuration 
@EnableResourceServer 
public  class GatewayResourceServerConfig the extends ResourceServerConfigurerAdapter { 


    @Resource 
    Private GatewayWebSecurityExpressionHandler gatewayWebSecurityExpressionHandler; 

    @Override 
    public  void Configure (ResourceServerSecurityConfigurer Resources ) throws Exception { 
        Resources 
                .resourceId ( "Gateway" )
                 // expression processor
                .expressionHandler (gatewayWebSecurityExpressionHandler); 
    } 

    @Override 
    public  void Configure (HttpSecurity HTTP) throws Exception { 
        HTTP 
                .addFilterBefore ( new new . GatewayAuditLogFilter (), the ExceptionTranslationFilter class ) 
                .authorizeRequests () 
                // let claim token request does not require authentication 
                . antMatchers ( "/ token / **" ) .permitAll ()
                 // All other requests if there privilege, to be judged by hasPermission method permissionService of 
                .anyRequest (). Access ( "# permissionService.hasPermission (request, authentication)" ) ; 
    }

}

1.3, start the project test, print log is as follows, for logging, to know who the current user is, it is, after certification, the middle printed in the authorization process we PermissionService write the log, so that added to between authorization, in line with our expectations.

Denial of access processing 2,403

  In SpringSecurity, for 403 access denied by the realization AccessDeniedHandler interface to handle the default used in the OAuth2 is OAuth2AccessDeniedHandler, we can write its own processor in the processor, you can log, you can customize return content.

2.1, custom AccessDeniedHandler

/ ** 
 * denied access to the processor 403 
 * 
 * @author caofanqi 
 * @date 2020/2/9 22:37 
 * / 
@ SLF4J 
@Component 
public  class GatewayAccessDeniedHandler the extends OAuth2AccessDeniedHandler { 

    @Override 
    public  void handle (the HttpServletRequest Request, Response the HttpServletResponse, AccessDeniedException is authException) throws IOException, ServletException { 

        log.info ( "2, update log to 403" );
         // make a mark, let the log filter already know the update log 
        request.setAttribute ( "updateLog", "yes" );
         / /Here you can return custom content, we have not changed, the default use OAuth2AccessDeniedHandler 
        Super .handle (Request, Response, authException); 

    } 
}

2.2, added to the configuration ResourceServerSecurityConfigurer

 2.3, modified log filter

 2.4, Access Denied console test print as follows

3,401 processing authentication failure

  In SpringSecurity, for 401 authentication process is implementation AuthenticationEntryPoint interface to handle the default used in the OAuth2 is OAuth2AuthenticationEntryPoint, equally, we can customize the interface to achieve AuthenticationEntryPoint logging, custom return content. Note that if the incoming error token, the authentication will fail authentication filter to be processed by the AuthenticationEntryPoint, this case does not pass the request log filter. If you do not pass the token, authentication filter creates an anonymous Authentication (AnonymousAuthenticationToken), continue to go down, as is not able to access, determined by the authorization.

3.1 First, change it PermissionService requires that all requests go through authentication

/**
 * 权限控制实现类
 *
 * @author caofanqi
 * @date 2020/2/9 14:51
 */
@Slf4j
@Service
public class PermissionServiceImpl implements PermissionService {

    /**
     * 在这里可以去安全中心,获取该请求是否具有相应的权限
     *
     * @param request        请求
     * @param authentication 认证相关信息
     */
    @Override
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {

        //这里我们就不写具体的权限判断了,采用随机数模拟,百分之50的机率可以访问
        log.info("request uri : {}", request.getRequestURI());
        log.info("authentication : {}", ReflectionToStringBuilder.toString(authentication));

        /*
         * 如果是没传令牌的话,Authentication 是 AnonymousAuthenticationToken
         * 如果传入令牌经过身份认证 Authentication 是 OAuth2Authentication
         */
        if (authentication instanceof AnonymousAuthenticationToken){
            //要求必须通过身份认证
            throw new AccessTokenRequiredException(null);
        }

        boolean  hasPermission =  RandomUtils.nextInt() % 2 == 0;
        log.info("hasPermission is :{}",hasPermission);
        return hasPermission;
    }

}

3.2、自定义AuthenticationEntryPoint

/**
 * 401身份验证处理
 *
 * @author caofanqi
 * @date 2020/2/9 23:13
 */
@Slf4j
@Component
public class GatewayAuthenticationEntryPoint extends OAuth2AuthenticationEntryPoint {


    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {

        if(authException instanceof AccessTokenRequiredException){
            //是我们抛出的必须经过身份认证,说明没有传令牌,此时是匿名用户,请求经过了日志过滤器
            log.info("2、update log to 401");
        }else {
            //说明令牌是错误的,认证那里就不对,没有经过日志过滤器
            log.info("1、create log to 401");
        }

        //做一个标记,让日志过滤器知道已经更新日志了
        request.setAttribute("updateLog","yes");
        super.commence(request, response, authException);
    }
}

3.3、添加到ResourceServerSecurityConfigurer配置中

3.4、启动各项目测试

  3.4.1、不传令牌进行测试

   3.4.2、传入错误的令牌进行测试

4、在SpringSecurity过滤器链上添加限流过滤器

4.1、导入guava依赖

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>28.0-jre</version>
        </dependency>

4.2、编写限流过滤器

/**
 * 限流过滤器
 *
 * @author caofanqi
 * @date 2020/2/9 23:54
 */
public class GatewayRateLimitFilter extends OncePerRequestFilter {

    private RateLimiter rateLimiter = RateLimiter.create(1);

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        if (rateLimiter.tryAcquire()) {
            filterChain.doFilter(request, response);
        } else {
            response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.getWriter().write("{\"error\":\"too many request\"}");
            response.getWriter().flush();
        }

    }
}

4.3、添加到SpringSecurity过滤器链上,SecurityContextPersistenceFilter是整个SpringSecurity过滤器链上的第一个,添加在它之间即可

 

4.4、测试快速请求

5、梳理JWT改造后的整体流程

  上图就是在我们写的安全机制中主要涉及的过滤器和组件,左边的都是过滤器(FilterSecurityInterceptor虽然不是以Filter结尾的,但也是过滤器),右边都自己写的组件,组件的作用是改变或增强过滤器的行为。其中绿色的都是我们自己写的,蓝色的都是SpringSecurity写的,SpringSecurity写的有它自己的默认行为,我们自己写的组件注入到SpringSecurity的过滤器里面,来改变或者增强SpringSecurity过滤器的行为。

  执行的顺序就是左边从上到下,首先是GatewayRateLimitFilter我们自己写的用来限流的,然后第二个OAuth2ClientAuthenticationProcessingFilter作用是从令牌中将当前用户身份提取出来,下面是AuditLogFilter我们用来记录审计日志,后面ExceptionTranslationFilter是一个异常转换过滤器,本身没有任何业务逻辑,它作用就是cache后面FilterSecurityInterceptor抛出来的异常,FilterSecurityInterceptor的作用就是判断权限,我们写的PermissionService最终就是在这里生效的。

  一个请求过来,就会按顺序执行这些过滤器(SpringSecurity还有一个其他的过滤器,但是跟我们的核心没有关系),我们会把自己写的权限判断逻辑放到PermissionService里,然后把PermissionService给到GatewayWebSecurityExpressionHandler表达式处理器,然后把表达式处理器给到FilterSecurityInterceptor,最终,我们在代码中写的表达式("#permissionService.hasPermission(request,authentication)")会由GatewayWebSecurityExpressionHandler处理,然后交给PermissionService。在FilterSecurityInterceptor中进行权限判断时,如果没有权限会抛出相应异常,会被ExceptionTranslationFilter捕获住,然后根据抛出的异常去调用相应的处理器,在安全的错误中,一共就两种异常,401和403。401交给GatewayAuthenticationEntryPoint来处理,403交给GatewayAccessDeniedHandler来处理,这两个组件都是注到ExceptionTranslationFilter中来进行相应的处理,同时GatewayAuthenticationEntryPoint也会被注入到OAuth2ClientAuthenticationProcessingFilter里面,因为如果令牌传的不对,OAuth2ClientAuthenticationProcessingFilter会直接抛出401的异常给GatewayAuthenticationEntryPoint处理。

  这就是我们根据JWT改造完,在网关上所做的事情和逻辑。

 

 项目源码:https://github.com/caofanqi/study-security/tree/dev-jwt-success

 

Guess you like

Origin www.cnblogs.com/caofanqi/p/12289606.html
jwt