Spring Securityspring 整合使用开源框架-SpringSecurity系列

spring security 的webflux版本

2021-11-10  本文已影响0人  virtual灬zzZ

由于Web容器不同,在Gateway项目中使用的WebFlux,是不能和Spring-Web混合使用的。
Spring MVC和 WebFlux 的区别:

Spring-Security配置:

spring security设置要采用响应式配置,基于WebFlux中WebFilter实现,与Spring MVC的Security是通过Servlet的Filter实现类似,也是一系列filter组成的过滤链。
Reactor与传统MVC配置对应:

webflux mvc 作用
@EnableWebFluxSecurity @EnableWebSecurity 开启security配置
ServerAuthenticationSuccessHandler AuthenticationSuccessHandler 登录成功Handler
ServerAuthenticationFailureHandler AuthenticationFailureHandler 登陆失败Handler
ReactiveAuthorizationManager<AuthorizationContext> AuthorizationManager 认证管理
ServerSecurityContextRepository SecurityContextHolder 认证信息存储管理
ReactiveUserDetailsService UserDetailsService 用户登录
ReactiveAuthorizationManager AccessDecisionManager 鉴权管理
ServerAuthenticationEntryPoint AuthenticationEntryPoint 未认证Handler
ServerAccessDeniedHandler AccessDeniedHandler 鉴权失败Handler

引入的依赖:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

@EnableWebFluxSecurity
public class SecurityConfig {

    @Resource(name = "authReactiveUserDetailService")
    private AuthReactiveUserDetailService authReactiveUserDetailService;

    @Resource(name = "customServerAuthenticationEntryPoint")
    private ServerAuthenticationEntryPoint customServerAuthenticationEntryPoint;

    @Resource(name = "customServerAccessDeniedHandler")
    private ServerAccessDeniedHandler customServerAccessDeniedHandler;

    @Resource(name = "refreshTokenWebFilter")
    private RefreshTokenWebFilter refreshTokenWebFilter;

    @Resource(name = "accessTokenWebFilter")
    private AccessTokenWebFilter accessTokenWebFilter;

    @Resource(name = "customServerSuccessHandler")
    private CustomServerSuccessHandler customServerSuccessHandler;

    @Resource(name = "customServerFailureHandler")
    private CustomServerFailureHandler customServerFailureHandler;

    @Value("${security.white-paths}")
    private String[] whitePaths;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 如果没有,系统会自动创建一个
     */
    @Bean
    public ReactiveAuthenticationManager customAuthenticationManager() {
        UserDetailsRepositoryReactiveAuthenticationManager manager
                = new UserDetailsRepositoryReactiveAuthenticationManager(authReactiveUserDetailService);
        manager.setPasswordEncoder(passwordEncoder());
        return manager;
    }

    @Bean
    ServerSecurityContextRepository customServerSecurityContextRepository(){
        return NoOpServerSecurityContextRepository.getInstance();
    }


    @Bean
    public SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) {
        http
                .authorizeExchange()
                //白名单不拦截
                .pathMatchers(whitePaths).permitAll()
                //AJAX进行跨域请求时的预检,需要向另外一个域名的资源发送一个HTTP OPTIONS请求头,用以判断实际发送的请求是否安全
                .pathMatchers(HttpMethod.OPTIONS).permitAll().and()
                .authorizeExchange()
                .pathMatchers("/cs/apollo/**").hasAuthority("lll")
                .anyExchange().authenticated()
                .and()
                .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
                .csrf(ServerHttpSecurity.CsrfSpec::disable)
                .authenticationManager(customAuthenticationManager())
                .securityContextRepository(customServerSecurityContextRepository())
                .cors()
                .and()
                .formLogin().loginPage("/orionLogin")
                .authenticationSuccessHandler(customServerSuccessHandler)
                .authenticationFailureHandler(customServerFailureHandler)
                .and()
                .logout().logoutUrl("/orionLogout").logoutSuccessHandler(new CustomServerLogoutHandler())
                .and()
                .exceptionHandling().accessDeniedHandler(customServerAccessDeniedHandler)
                .authenticationEntryPoint(customServerAuthenticationEntryPoint);

        http.addFilterAt(refreshTokenWebFilter, SecurityWebFiltersOrder.HTTP_BASIC);
        http.addFilterAt(accessTokenWebFilter, SecurityWebFiltersOrder.AUTHORIZATION);

        return http.build();
    }
}

注解是@EnableWebFluxSecurity,其余跟spring security差不多,使用的语法不同,它都是由filter组成,上述使用了自定义的accessTokenFilter和refreshTokenFilter,添加的时候顺序需要注意一下,不同的filter都是由顺序的,如下图:

默认的顺序:

public enum SecurityWebFiltersOrder {
    FIRST(Integer.MIN_VALUE),
    HTTP_HEADERS_WRITER,
    /**
     * {@link org.springframework.security.web.server.transport.HttpsRedirectWebFilter}
     */
    HTTPS_REDIRECT,
    /**
     * {@link org.springframework.web.cors.reactive.CorsWebFilter}
     */
    CORS,
    /**
     * {@link org.springframework.security.web.server.csrf.CsrfWebFilter}
     */
    CSRF,
    /**
     * {@link org.springframework.security.web.server.context.ReactorContextWebFilter}
     */
    REACTOR_CONTEXT,
    /**
     * Instance of AuthenticationWebFilter
     */
    HTTP_BASIC,
    /**
     * Instance of AuthenticationWebFilter
     */
    FORM_LOGIN,
    AUTHENTICATION,
    /**
     * Instance of AnonymousAuthenticationWebFilter
     */
    ANONYMOUS_AUTHENTICATION,
    OAUTH2_AUTHORIZATION_CODE,
    LOGIN_PAGE_GENERATING,
    LOGOUT_PAGE_GENERATING,
    /**
     * {@link org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter}
     */
    SECURITY_CONTEXT_SERVER_WEB_EXCHANGE,
    /**
     * {@link org.springframework.security.web.server.savedrequest.ServerRequestCacheWebFilter}
     */
    SERVER_REQUEST_CACHE,
    LOGOUT,
    EXCEPTION_TRANSLATION,
    AUTHORIZATION,
    LAST(Integer.MAX_VALUE);

    private static final int INTERVAL = 100;

    private final int order;

    SecurityWebFiltersOrder() {
        this.order = ordinal() * INTERVAL;
    }

    SecurityWebFiltersOrder(int order) {
        this.order = order;
    }

    public int getOrder() {
        return this.order;
    }
}

第一次登陆的时候,会到 AuthenticationWebFilter,匹配上了requiresAuthenticationMatcher的路径(配置类中的loginPage设置)和方法(Post),就会执行后续authenticate,就是去ReactiveUserDetailsService验证身份了,如果不是loginPage的路径,经过这个filter时就不会
去authenticate了,最后会到AuthorizationWebFilter,这个需要注意:

ServerHttpSecurity -->Access类:(先说明配置类中配置成permitAll的路径)

public AuthorizeExchangeSpec permitAll() {
                return access( (a, e) -> Mono.just(new AuthorizationDecision(true)));
            }

public AuthorizeExchangeSpec access(ReactiveAuthorizationManager<AuthorizationContext> manager) {
                AuthorizeExchangeSpec.this.managerBldr
                    .add(new ServerWebExchangeMatcherEntry<>(
                        AuthorizeExchangeSpec.this.matcher, manager));
                AuthorizeExchangeSpec.this.matcher = null;
                return AuthorizeExchangeSpec.this;
            }
public class AuthorizationWebFilter implements WebFilter {
    private ReactiveAuthorizationManager<? super ServerWebExchange> accessDecisionManager;

    public AuthorizationWebFilter(ReactiveAuthorizationManager<? super ServerWebExchange> accessDecisionManager) {
        this.accessDecisionManager = accessDecisionManager;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return ReactiveSecurityContextHolder.getContext()
            .filter(c -> c.getAuthentication() != null)
            .map(SecurityContext::getAuthentication)
            .as(authentication -> this.accessDecisionManager.verify(authentication, exchange))
            .switchIfEmpty(chain.filter(exchange));
    }
}
public interface ReactiveAuthorizationManager<T> {
    /**
     * Determines if access is granted for a specific authentication and object.
     *
     * @param authentication the Authentication to check
     * @param object the object to check
     * @return an decision or empty Mono if no decision could be made.
     */
    Mono<AuthorizationDecision> check(Mono<Authentication> authentication, T object);

    /**
     * Determines if access should be granted for a specific authentication and object
     *

     * @param authentication the Authentication to check
     * @param object the object to check
     * @return an empty Mono if authorization is granted or a Mono error if access is
     * denied
     */
    default Mono<Void> verify(Mono<Authentication> authentication, T object) {
        return check(authentication, object)
            .filter( d -> d.isGranted())
            .switchIfEmpty(Mono.defer(() -> Mono.error(new AccessDeniedException("Access Denied"))))
            .flatMap( d -> Mono.empty() );
    }
}

可见:

check的方法在 ReactiveAuthorizationManager的接口类中,①和②按照各自的check方法校验即可。③是AuthorizationDecision为true的,默认应该通过了,因为①②是否通过最后都有一个AuthorizationDecision(boolean).

由上述代码可见,如果发生验证不通过,会产生new AccessDeniedException("Access Denied"),但这个exception最后会给到它上一个filter:ExceptionTranslationWebFilter,由它的commenceAuthentication方法处理,而其中他的 this.authenticationEntryPoint,就是我们在sercurity中自定义配置的entryPoint。

private <T> Mono<T> commenceAuthentication(ServerWebExchange exchange, AccessDeniedException denied) {
        return this.authenticationEntryPoint.commence(exchange, new AuthenticationCredentialsNotFoundException("Not Authenticated", denied))
            .then(Mono.empty());
    }

參考:Springboot WebFlux集成Spring Security实现JWT认证

上一篇 下一篇

猜你喜欢

热点阅读