我々はすでにJWTの転換による認証と承認を完了した、我々は、フィルターコード番号認証および承認(フィルター)ことがわかり、そして引き継ぐためにフィルタSpringSecurity本質的に構成され、その後、私たちは見ることができますどのようなログや制限などSpringSecurityフィルタチェーンに当社独自のロジックで結合されました。
図1に示すように、フィルタはSpringSecurityの監査フィルタチェーンを加え
1.1、我々はメカニズムに基づいて、当社の以前の監査を配置する前に、認証にログフィルタを取るので、ログフィルタを作成し、承認。JWT認証トークン認証にフィルタリングします、そしてセキュリティコンテキストに置かれ、getPrincipal()メソッドは、ログインユーザ名があるに取得します。
/ ** *审计过滤器 * * @author caofanqi * @date 2020年2月9日夜10時06分 * / @ SLF4J パブリック クラス GatewayAuditLogFilterは延びOncePerRequestFilterは{ @Overrideが 保護 ボイド doFilterInternal(HttpServletRequestのリクエスト、HttpServletResponseの応答れるFilterChainフィルターチェーン)がスローServletExceptionがをIOException { 文字列名 = (文字列)SecurityContextHolder.getContext()getAuthentication()getPrincipal()。。。 log.info( "1、ログ作成:{}" 、ユーザ名) filterChain.doFilter(リクエスト、レスポンス)。 log.info( "2、成功への更新ログ" ); } }
承認前に、ログフィルタSpringSecurityフィルタチェーンに追加1.2は、認証後に添加されます。フィルタチェーンSpringSecurityアドフィルタに、ベースのセキュリティ設定は、4つのメソッドがあり、addFilterBeforeは、前フィルタに追加フィルタaddFilterAfterを追加した後、addFilterAtは、フィルタを交換、かaddFilterはチェーンに追加。
ので、両方がSpringSecurityフィルタチェーンの実行時にフィルタの実行の固定順序を持って、私たちは私たちのログフィルタを入れて追加されExceptionTranslationFilterフィルタの前に、フィルタは、異常処理フィルタで、最終的な承認でフィルタとしてあなたはAUTHORIZEを見ていない場合はレーンは、例外は403は、このフィルタによって処理され、401スローされます。
/ ** *ゲートウェイのリソースサーバの設定 * * @author caofanqi * @date 2020年2月8日22:30 * / @Configuration @EnableResourceServer パブリック クラス GatewayResourceServerConfig 拡張ResourceServerConfigurerAdapter { @Resource プライベートGatewayWebSecurityExpressionHandler gatewayWebSecurityExpressionHandlerを; @Override 公共 無効の設定(ResourceServerSecurityConfigurerリソース)スロー例外{ リソース .resourceId(「ゲートウェイ」) // 式プロセッサ .expressionHandler(gatewayWebSecurityExpressionHandlerが); } @Override 公共 無効の設定(HttpSecurity HTTP)スロー例外を{ HTTP .addFilterBefore(新しい新しい。GatewayAuditLogFilter()、ExceptionTranslationFilterのクラス) .authorizeRequests() // 認証を必要としない、請求トークン要求をしましょう 。 antMatchers( "/トークン/ **" ).permitAll() // 他のすべての要求があっ特権は、hasPermissionの方法permissionServiceによって判断される場合 .anyRequest()。アクセス( "#permissionService.hasPermission(要求、認証)" ) ; } }
1.3、プロジェクトのテストを開始し、次のように印刷ログが、ログ記録のために、現在のユーザーが誰であるか知っている、認証後、承認プロセスで印刷途中で、我々はされてPermissionService私たちに沿って、承認の間に追加さそうという、ログを書き込みます期待。
アクセス処理2403の拒否
SpringSecurityでのOAuth2で使用されるデフォルト値を処理するために実現AccessDeniedHandlerインタフェースによって拒否された403回のアクセスがOAuth2AccessDeniedHandlerであるため、我々はあなたがログインすることができ、プロセッサに独自のプロセッサを書くことができ、あなたはリターンをカスタマイズすることができますコンテンツ。
2.1、カスタムAccessDeniedHandler
/ ** *プロセッサへのアクセス拒否403 * * @author caofanqi * @date 2020年2月9日午後10時37分 * / SLF4J @ @Component パブリック クラス GatewayAccessDeniedHandler 拡張OAuth2AccessDeniedHandler { @Override 公共 のボイドハンドル(HttpServletRequestのリクエスト、レスポンスのHttpServletResponse、AccessDeniedExceptionですauthException)スローにIOException、ServletExceptionが{ log.info( "403から2、更新ログを" ); // マークを作る、ログフィルタが既に更新ログを知らせ request.setAttribute( "updateLog"、 "はい"を); / /ここでは、カスタムコンテンツを返すことができ、我々は、デフォルトの使用OAuth2AccessDeniedHandler変更されていない スーパーの.handle(要求、応答、authExceptionを); } }
2.2、構成ResourceServerSecurityConfigurerに追加
2.3修飾ログフィルタ
2.4次のように、アクセスがコンソールテストプリントを拒否されました
3401処理の認証失敗
SpringSecurityでは、401の認証プロセスのためのOAuth2で使用されるデフォルト値を処理するために実装AuthenticationEntryPointインタフェースはOAuth2AuthenticationEntryPointです、同じように、我々はAuthenticationEntryPointロギング、カスタムリターン含有量を達成するためのインタフェースをカスタマイズすることができます。注入ってくるエラートークン場合、認証はAuthenticationEntryPointによって処理される認証フィルタを失敗することを、この場合は、要求ログフィルタを渡しません。あなたがトークンを渡さない場合、認証フィルタは、として承認により決定、アクセスすることができない、匿名認証(AnonymousAuthenticationToken)は、ダウンして行くを続けて作成されます。
3.1まず、それPermissionServiceは、すべてのリクエストが認証を経ることが必要です変更します
/** * 权限控制实现类 * * @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.4、测试快速请求
上图就是在我们写的安全机制中主要涉及的过滤器和组件,左边的都是过滤器(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