首页 > 解决方案 > Spring Webflux:帮助将代码转换为非阻塞

问题描述

在 Spring Webflux 应用程序中,我有以下“AuthenticationManager”实现。在高层次上,以下是它正在尝试做的事情 -

  1. 某些 url 路径在 prop 文件中配置为需要基本身份验证,而另一些则需要不记名令牌。

  2. 对于基本身份验证,BcryptPasswordEncoder.matches 方法用于将传入密码与存储在 prop 文件中的编码密码进行比较。

  3. 对于不记名令牌身份验证,正在使用 io.jsonwebtoken 库来验证令牌。

     @Component
     public class AuthenticationManager implements ReactiveAuthenticationManager {
    
     /** The jwt util. */
     @Autowired
     private JWTUtil jwtUtil;
     @Override
     public Mono<Authentication> authenticate(Authentication authobj) {
     return Mono.just(authobj).flatMap(authentication -> {
             String credentials = authentication.getCredentials().toString();
             String path = authentication.getPrincipal().toString();
             BasicAuthPath basicAuthPath = jwtUtil.isBasicAuthUrl(path);
             if (jwtUtil.isBasicScheme(credentials) || jwtUtil.isBearerScheme(credentials)) {
                 if (jwtUtil.isBasicScheme(credentials) && basicAuthPath.isState()) {
                     credentials = credentials.substring(5);
                     if (jwtUtil.isBasicAuthSuccess(credentials.trim(), basicAuthPath.getPath())) {
                         UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(credentials, null, null);
                         return Mono.just(auth);
                     } else {
                         return Mono.empty();
                     }
                 } else if (jwtUtil.isBearerTokenAuthUrl(path) && jwtUtil.isBearerScheme(credentials)) {
                     credentials = credentials.substring(7);
                     String username;
                     try {
                         username = jwtUtil.getUsernameFromToken(credentials);
                     } catch (Exception e) {
                         username = null;
                     }
                     if (jwtUtil.validateToken(credentials)) {
    
                         Claims claims = jwtUtil.getAllClaimsFromToken(credentials);
                         List<String> rolesMap = claims.get("role", List.class);
                         List<Role> roles = new ArrayList<>();
                         for (String rolemap : rolesMap) {
                             roles.add(Role.valueOf(rolemap));
                         }
                         UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, null,
                                 roles.stream().map(authority -> new SimpleGrantedAuthority(authority.name())).collect(Collectors.toList()));
                         return Mono.just(auth);
                     } else {
                         return Mono.empty();
                     }
                 } else {
                     return Mono.empty();
                 }
    
             }
             return Mono.empty();
         });
         }
         }
    

JwtUtil 代码如下

@Component
@Slf4j
public class JWTUtil implements Serializable {

    /** The Constant serialVersionUID. */
    private static final long serialVersionUID = 1L;

    /** The env. */
    @Autowired
    private Environment env;

    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Autowired
    private JwtParser jwtParser;
    
    @Autowired
    private Key signingKey;

    private static final PathPatternParser DEFAULT_PATTERN_PARSER = new PathPatternParser();

    /**
     * Checks if is basic auth success.
     *
     * @param inputCredentials the input credentials
     * @param path the path
     * @return true, if is basic auth success
     */
    public boolean isBasicAuthSuccess(String inputCredentials, String path) {
        String pathtoprop = pathtoprop(path);
        String configpwd = env.getProperty(pathtoprop);
        if (StringUtils.isBlank(configpwd)) {
            configpwd = env.getProperty(pathtoprop.substring(0, pathtoprop.lastIndexOf(".")));
        }
        return passwordEncoder.matches(new String(Base64.getDecoder().decode(inputCredentials)), configpwd);
        //return true;
}

    /**
     * Checks if is basic auth url.
     *
     * @param path the path
     * @return true, if is basic auth url
     */
    public BasicAuthPath isBasicAuthUrl(String path) {
        BasicAuthPath basicAuthPath = new BasicAuthPath();
        if (StringUtils.isNotBlank(env.getProperty("gateway.basicauth.urls"))) {
            return pathMatcherBasic(StringUtils.split(env.getProperty("gateway.basicauth.urls"), ","), path);
        }
        basicAuthPath.setState(false);
        return basicAuthPath;
    }
    
    /**
     * pathMatcher
     * 
     * @param pathPatterns
     * @param path
     * @return
     */
    private BasicAuthPath pathMatcherBasic(String[] pathPatterns, String path) {
        BasicAuthPath basicAuthPath = new BasicAuthPath();
        for (String pathPattern : pathPatterns) {
            PathPattern pattern = DEFAULT_PATTERN_PARSER.parse(pathPattern);
            if (pattern.matches(PathContainer.parsePath(path))) {
                basicAuthPath.setState(true);
                basicAuthPath.setPath(pathPattern);
                return basicAuthPath;
            }
        }
        basicAuthPath.setState(false);
        return basicAuthPath;
    }

    /**
     * Checks if is bearer token auth url.
     *
     * @param path the path
     * @return true, if is bearer token auth url
     */
    public boolean isBearerTokenAuthUrl(String path) {
        if (StringUtils.isNotBlank(env.getProperty("gateway.bearertoken.urls"))
                && pathMatcher(StringUtils.split(env.getProperty("gateway.bearertoken.urls"), ","), path)) {
            return true;
        }
        return false;
    }

    /**
     * pathMatcher
     * 
     * @param pathPatterns
     * @param path
     * @return
     */
    private boolean pathMatcher(String[] pathPatterns, String path) {
        for (String pathPattern : pathPatterns) {
            PathPattern pattern = DEFAULT_PATTERN_PARSER.parse(pathPattern);
            if (pattern.matches(PathContainer.parsePath(path))) {
                return true;
            }
        }
        return false;
    }

    /**
     * isBasicScheme
     * 
     * @param credentials
     * @return
     */
    public boolean isBasicScheme(String credentials) {
        if (StringUtils.isNotBlank(credentials) && credentials.startsWith(RestUriConstants.BASIC_TOKE_PREFIX)) {
            return true;
        }
        return false;
    }

    /**
     * isBearerScheme
     * 
     * @param credentials
     * @return
     */
    public boolean isBearerScheme(String credentials) {
        if (StringUtils.isNotBlank(credentials) && credentials.startsWith(RestUriConstants.BEARER_TOKE_PREFIX)) {
            return true;
        }
        return false;
    }

    /**
     * Gets the all claims from token.
     *
     * @param token the token
     * @return the all claims from token
     */
    public Claims getAllClaimsFromToken(String token) {
        return jwtParser.parseClaimsJws(token).getBody();
    }

    /**
     * Gets the username from token.
     *
     * @param token the token
     * @return the username from token
     */
    public String getUsernameFromToken(String token) {
        return getAllClaimsFromToken(token).getSubject();
    }

    /**
     * Gets the expiration date from token.
     *
     * @param token the token
     * @return the expiration date from token
     */
    public Date getExpirationDateFromToken(String token) {
        return getAllClaimsFromToken(token).getExpiration();
    }

    /**
     * Checks if is token expired.
     *
     * @param token the token
     * @return the boolean
     */
    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    /**
     * Generate token.
     *
     * @param user the user
     * @return the string
     */
    public String generateToken(User user) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("role", user.getRoles());
        return doGenerateToken(claims, user.getUsername());
    }

    /**
     * Do generate token.
     *
     * @param claims the claims
     * @param username the username
     * @return the string
     */
    private String doGenerateToken(Map<String, Object> claims, String username) {
        Long expirationTimeLong = Long.parseLong(env.getProperty("gateway.apollo.token.encryption.exp")); // in second

        final Date createdDate = new Date();
        final Date expirationDate = new Date(createdDate.getTime() + expirationTimeLong * 1000);
        return Jwts.builder().setClaims(claims).setSubject(username).setIssuedAt(createdDate).setExpiration(expirationDate).signWith(signingKey)
                .compact();
    }

    /**
     * Validate token.
     *
     * @param token the token
     * @return the boolean
     */
    public Boolean validateToken(String token) {
        Boolean result = false;
        try {
            result = !isTokenExpired(token);
        } catch (Exception e) {
            log.error("Exception in validateToken - {}",token);
        }
        return result;
    }

    private String pathtoprop(String path) {
        return "gateway.basicauth.credentials" + path.replace('/', '.');
    }

}

在使用大量并行线程完成 PST 期间,观察到高 CPU 利用率。我认为这可能是因为上面的很多代码都是阻塞的。如果这是真的,任何人都可以帮助提供有关如何以反应方式重构此代码的指针吗?我是否应该使用如下所示的线程池以获得更好的性能 -

Mono.just(authobj).publishOn(Schedulers.newParallel("password-encoder", Schedulers.DEFAULT_POOL_SIZE, true);).......

标签: jwtspring-webfluxproject-reactorbcrypt

解决方案


推荐阅读