• Spring WebFlux 支持 Spring Security 实现 JWT 鉴权


    移动端项目,鉴权需求比较简单,Spring Cloud Gateway 只做 JWT 校验及角色鉴权,登录之类的全部是自定义处理的,微服务间传递 JWS 达到传递凭证的目的,下游服务无需鉴权也不依赖 Spring Security,需要当前用户的代码直接解析 JWS 获取当前用户

    还有就是 Spring Security 5.4 + WebSecurityConfigurerAdapter 已被标记为弃用,非 reactive 项目也开始推荐使用 Bean 注入了,所以下面的代码也可以算是 5.4 + 的迁移指南了

    核心代码:

    JwsService 提供 JWS 的签名与校验,返回对应的 JwsPayload 对象,JwsPayload 为 POJO,提供当前 principal,包含一个 List<Authority> authoritys 属性,Authority 为 POJO,因为放在了 lib 模块中,没有引入 Spring Security 依赖,直接继承自 Object,也可以看到下面代码手动进行转换为 GrantedAuthority

    package com.seliote.bubble.gwsvr.security;
    
    import com.seliote.bubble.svrlib.config.JwsProps;
    import com.seliote.bubble.svrlib.service.JwsService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.lang.NonNull;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.context.ReactiveSecurityContextHolder;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import org.springframework.web.server.WebFilter;
    import org.springframework.web.server.WebFilterChain;
    import reactor.core.publisher.Mono;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * JWS Filter
     * If authorization header exists, will set to security context
     *
     * @author seliote
     * @since 2022-07-06
     */
    @Slf4j
    @Component
    public class JwsFilter implements WebFilter {
    
        private final JwsService jwsService;
        private final String headerName;
    
        @Autowired
        public JwsFilter(JwsService jwsService, JwsProps jwsProps) {
            this.jwsService = jwsService;
            this.headerName = jwsProps.getHeader();
        }
    
        @NonNull
        @Override
        public Mono<Void> filter(@NonNull ServerWebExchange exchange, @NonNull WebFilterChain chain) {
            var header = exchange.getRequest().getHeaders().getFirst(headerName);
            var payloadOpt = jwsService.verify(header);
            if (payloadOpt.isPresent() && payloadOpt.get().available()) {
                var payload = payloadOpt.get();
                List<? extends GrantedAuthority> authorities = new ArrayList<>();
                if (payload.getAuthorities() != null && payload.getAuthorities().size() != 0) {
                    authorities = payload.getAuthorities().stream().map(r -> (GrantedAuthority) r::getAuthority).toList();
                }
                var authentication = new UsernamePasswordAuthenticationToken(payload, null, authorities);
                log.trace("Set security context {}", payload);
                return chain.filter(exchange)
                        .contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication));
            }
            return chain.filter(exchange);
        }
    }
    

    因为是完全自定义去实现登录之类的操作,所以提供了一个空的 ReactiveAuthenticationManager,对应 MVC 里默认实现的 authenticationManager()

    package com.seliote.bubble.gwsvr.security;
    
    import com.seliote.bubble.svrlib.domain.Authority;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.security.authentication.ReactiveAuthenticationManager;
    import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
    import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
    import org.springframework.security.config.web.server.ServerHttpSecurity;
    import org.springframework.security.web.server.SecurityWebFilterChain;
    import reactor.core.publisher.Mono;
    
    /**
     * Security context config
     *
     * @author seliote
     * @since 2022-07-06
     */
    @Slf4j
    @EnableWebFluxSecurity
    public class SecurityConfig {
    
        private final JwsFilter jwsFilter;
    
        @Autowired
        public SecurityConfig(JwsFilter jwsFilter) {
            this.jwsFilter = jwsFilter;
        }
    
        @Bean
        public ReactiveAuthenticationManager authenticationManager() {
            return authentication -> Mono.empty();
        }
    
        @Bean
        public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
            final var permitAll = new String[]{"/user-svr/country/list"};
            return http.csrf().disable()
                    .httpBasic().disable()
                    .formLogin().disable()
                    .authorizeExchange().pathMatchers(permitAll).permitAll()
                    .pathMatchers("/**").hasAuthority(Authority.USER)
                    .anyExchange().authenticated()
                    .and().addFilterAt(jwsFilter, SecurityWebFiltersOrder.AUTHENTICATION)
                    .build();
        }
    }
    
  • 相关阅读:
    Codeforces #364 DIV2
    uva10635 LIS
    hdu3714 三分找最值
    【转】三分查找
    NBUT 1457 莫队算法 离散化
    HYSBZ 2038 莫队算法
    莫队算法
    poj3417 LCA + 树形dp
    hdu3087 LCA + 暴力
    hdu2874 LCA在线算法
  • 原文地址:https://www.cnblogs.com/seliote/p/16459929.html
Copyright © 2020-2023  润新知