Spring Security 身份验证的基本类/架构

news2024/11/23 11:05:47

目录

1、SecurityContextHolder 核心类

2、SecurityContext 接口

3、Authentication 用户认证信息接口

4、GrantedAuthority 拥有权限接口

5、AuthenticationManager 身份认证管理器接口

6、ProviderManager 身份认证管理器的实现

7、AuthenticationProvider 特定类型的身份验证接口

8、AuthenticationEntryPoint 身份验证方案接口

9、AbstractAuthenticationProcessingFilter 用户凭据验证过滤器


        // Spring Security 基于 Servlet 的身份验证和身份验证的基本构件

  • SecurityContextHolder(类)——用来存储身份验证详细信息//验证流程核心类
  • SecurityContext(接口)——从SecurityContextHolder 中获取,包含当前认证用户的身份验证信息
  • Authentication(接口) ——该对象可以通过 AuthenticationManager 进行赋值,作为用户身份验证的凭据,然后通过 SecurityContext 获取 Authentication 的详细信息。
  • GrantedAuthority(接口)——用于对用户的授权
  • AuthenticationManager(接口)——用于定义 Spring Security 过滤器如何执行身份验证的API。
  • ProviderManager(类)——AuthenticationManager 最常见的实现。//常规实现
  • AuthenticationProvider(接口)——提供给ProviderManager,用于执行特定类型的身份验证//身份验证的具体细节由xxxProvider提供
  • AuthenticationEntryPoint(接口)——用于从客户端请求用户凭据(例如,重定向到登录页面,发送WWW-Authenticate响应等)
  • AbstractAuthenticationProcessingFilter(抽象类)——用于身份验证的基本过滤器。通过该类可以很好地了解身份验证流程及各个部分如何协同工作的过程。//所有身份验证的过滤器都会实现该类

1、SecurityContextHolder 核心类

        SecurityContextHolder 类是 Spring Security 身份验证模型的核心, 在该类中包含了存储用户认证信息的上下文 SecurityContext。

        下边是一个对象之间的包含关系图,它很重要,对于理解 Spring Security 中的对象关系非常有用:

        SecurityContextHolder 用来存储身份验证详细信息。Spring Security 并不关心SecurityContextHolder 是如何填充的。如果其中包含值,则使用该值作为当前通过认证的用户信息。//只要SecurityContextHolder有值就会被使用

        所以,指示用户已被认证的最简单方法就是直接设置 SecurityContextHolder:

SecurityContext context = SecurityContextHolder.createEmptyContext(); 

//1-创建Authentication -> Token接口
Authentication authentication = new TestingAuthenticationToken("username", "password", "ROLE_USER"); 
//2-设置用户信息
context.setAuthentication(authentication);

SecurityContextHolder.setContext(context); 

        使用 TestingAuthenticationToken,是因为它非常简单。也可以使用 UsernamePasswordAuthenticationToken(UserDetails、密码、权限)等。

         // 定义一个Authentication -> 放入SecurityContext -> 放入 SecurityContextHolder

        在 SecurityContextHolder 上设置完 SecurityContextSpring Security 会使用此信息进行授权。如果要获取已验证用户的有关信息,可以通过SecurityContextHolder获得:

//1-获取Security上下文
SecurityContext context = SecurityContextHolder.getContext();

//2-获取用户通过身份验证的对象
Authentication authentication = context.getAuthentication();

//-通过Authentication获取用户名称(密码方式)
String username = authentication.getName(); 
//-通过Authentication被认证用户的身份信息。
Object principal = authentication.getPrincipal(); 
//-通过Authentication用户权限信息
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

        默认情况下,SecurityContextHolder 使用 ThreadLocal 来存储 SecurityContext,这意味着 SecurityContext 对于同一线程中的方法总是可用的,并不需要把 SecurityContext 作为参数显式地传递给这些方法。//SecurityContext 属于线程私有,如果我们不想显示传参,也可以这样用

    //包含SecurityContext的成员变量
    private static SecurityContextHolderStrategy strategy; 

    //初始化方法
    private static void initialize() {
        //1-如果没有指定mode,使用MODE_THREADLOCAL
        if (!StringUtils.hasText(strategyName)) { 
            strategyName = "MODE_THREADLOCAL";
        }

        if (strategyName.equals("MODE_THREADLOCAL")) {
            //2-默认策略
            strategy = new ThreadLocalSecurityContextHolderStrategy(); 
        } else if (strategyName.equals("MODE_INHERITABLETHREADLOCAL")) {
            strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
        } else if (strategyName.equals("MODE_GLOBAL")) {
            strategy = new GlobalSecurityContextHolderStrategy();
        } else {
            try {
                Class<?> clazz = Class.forName(strategyName);
                Constructor<?> customStrategy = clazz.getConstructor();
                strategy = (SecurityContextHolderStrategy)customStrategy.newInstance();
            } catch (Exception var2) {
                ReflectionUtils.handleReflectionException(var2);
            }
        }
        ++initializeCount;
    }

    // MODE_THREADLOCAL策略
    final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {

        // 使用ThreadLocal保存SecurityContext
        private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal();

        ThreadLocalSecurityContextHolderStrategy() {
        }
        //省略...
    }

        因为使用了 ThreadLocal 存储值,如果处理完当前用户请求后需要清除线程中的ThreadLocal,避免内存泄漏。Spring SecurityFilterChainProxy会确保SecurityContext总是被清除//FilterChainProxy是一个过滤器链的总代理,在总体架构那篇文章中提到过

    //在FilterChainProxy中的doFilter方法中清除
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
        if (!clearContext) {
            this.doFilterInternal(request, response, chain);
        } else {
            try {
                request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
                this.doFilterInternal(request, response, chain);
            } catch (RequestRejectedException var9) {
                this.requestRejectedHandler.handle((HttpServletRequest)request, (HttpServletResponse)response, var9);
            } finally {
                //处理完一个用户请求时,SecurityContext在这里被清除
                SecurityContextHolder.clearContext();   
                request.removeAttribute(FILTER_APPLIED);
            }
        }
    }

2、SecurityContext 接口

        SecurityContext 从 SecurityContextHolder 中获取。SecurityContext 包含一个 Authentication 对象

// SecurityContext接口
public interface SecurityContext extends Serializable {

    //接口很简单,只包含对Authentication的get和set方法
    Authentication getAuthentication();

    void setAuthentication(Authentication var1);
}

        SecurityContext 的实现类 SecurityContextImpl 也很简单,也就是对 Authentication 对象 get 和 set 方法的实现,所以重点还是 Authentication 对象。

3、Authentication 用户认证信息接口

        在 Spring Security 中,Authentication 接口有两个主要用途:

  1. 作为 AuthenticationManager 对象 authenticate方法的入参,用于对用户进行身份验证。在此场景中,isAuthenticated() 返回 false。
  2. 获取已经过身份验证的用户信息,可以从 SecurityContext 中获取当前的用户信息。

        一旦请求通过了AuthenticationManager#authenticate,Authentication 通常会存储在线程本地的 SecurityContext 中,由 SecurityContextHolder 进行管理。//Authentication->SecurityContext

        注意,除非 Authentication 将 isAuthenticated 属性设置为 true,否则安全拦截器仍然会对其进行身份验证。

        Authentication 接口详情://每个方法的注释都值得好好去读

public interface Authentication extends Principal, Serializable {

    //1-权限:由AuthenticationManager设置,用于指示已授予主体的权限。认证后不能为空(即无权限)
    Collection<? extends GrantedAuthority> getAuthorities();

    //2-密码:证明委托人正确的凭据。通常是密码
    Object getCredentials();

    //3-用户详情:存储有关身份验证请求的其他详细信息。如IP地址,证书序列号等
    Object getDetails();

    //4-用户名:被认证主体的身份。通常是用户名,许多身份验证提供程序将创建UserDetails对象作为主体
    Object getPrincipal();

    //用于指示AbstractSecurityInterceptor是否应该向AuthenticationManager提供身份验证令牌。
    //如果令牌已经过身份验证,并且AbstractSecurityInterceptor不需要再次向AuthenticationManager提供令牌以进行重新身份验证,则为true。
    boolean isAuthenticated();

    //如果令牌应该被信任(这可能导致异常),则为true;如果令牌不应该被信任,则为false
    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

        所以,综合上述,Authentication 包含三方面信息://总结起来就是:用户信息+权限

  1. Principal:用户主体信息,该信息通常是 UserDetails 的一个实例。
  2. Credentials:认证凭据,通常是密码。在许多情况下,用户通过身份验证后会被ProviderManager清除该属性,以确保凭据不会泄露。
  3. Authorities:用户权限,由 GrantedAuthority 进行授予。

4、GrantedAuthority 拥有权限接口

        表示授予身份验证对象的权限。该接口的内容提供给授权管理器AuthorizationManager使用,以确定身份验证是否有权访问特定对象。//保存的是用户角色

//GrantedAuthority接口
public interface GrantedAuthority extends Serializable {
    String getAuthority(); //返回的是一个字符串 String role
}

        // 所以,GrantedAuthority,这个玩意是用来授权的,存储已认证用户具备的访问权限。

        通过 Authentication.getAuthorities() 方法可以获取 GrantedAuthority 的实例。此方法提供了 GrantedAuthority 对象的集合。毫无疑问,GrantedAuthority 是授予用户的权限。这些权限通常是“角色”信息,例如 ROLE_ADMINISTRATOR ROLE_HR_SUPERVISOR。这些角色将配置给 Web 授权、方法授权和域对象授权使用。当使用基于用户名/密码的身份验证时,GrantedAuthority 实例通常由 UserDetailsService 加载。

        通常,GrantedAuthority 对象是应用程序范围的权限。它们并不特定于给定的域对象。因此,不太可能使用 GrantedAuthority 来表示一个对象的具体权限,因为如果有数千个这样的权限,将很快耗尽内存(或者,会导致应用程序花费很长时间来进行验证)。// 其意思是说,GrantedAuthority 中的权限是一类权限(角色),而不是某个具体的功能,比如管理员权限能操作所有模块,而所有模块的功能汇聚在一起可能有好几百个。

5、AuthenticationManager 身份认证管理器接口

        AuthenticationManager 定义 Spring Security 的过滤器如何执行身份验证的 API。用于处理身份验证请求

public interface AuthenticationManager {

    //尝试对传递的身份验证对象进行身份验证,
    //如果成功,则返回一个完全填充的身份验证对象(包括授予的权限)。
    Authentication authenticate(Authentication var1) throws AuthenticationException;
}

        Spring Security 的 Filters 实例会调用 AuthenticationManager 获取在 SecurityContextHolder上设置的 Authentication 对象进行身份验证。

        如果直接设置 SecurityContextHolder,就不需要使用 AuthenticationManager。//直接填充身份信息,跳过认证步骤,由此可以见所有的认证流程也只是去SecurityContextHolder中设置一个Authentication而已

        AuthenticationManager 可以有很多种实现,其中最常见的实现是 ProviderManager

6、ProviderManager 身份认证管理器的实现

        ProviderManager 是 AuthenticationManager 最常用的实现。ProviderManager 对用户的认证委托给一个 AuthenticationProvider 实例的列表。

//具体进行认证操作的实例列表
private List<AuthenticationProvider> providers;

        每个 AuthenticationProvider 都有机会表明身份验证应该是成功的、失败的,或者表明它不能做出决定,并允许下游的 AuthenticationProvider 做出决定。

        如果配置的 AuthenticationProvider 实例都不能进行身份验证,则身份验证失败并产生一个ProviderNotFoundException,这是一个特殊的 AuthenticationException,表明 ProviderManager 没有配置支持此 Authentication 对象认证的 Provider 类型。

        实际中,每个 AuthenticationProvider 都会去执行一个特定类型的身份验证。例如,一个AuthenticationProvider 可能能够验证用户名/密码,而另一个 AuthenticationProvider 可能能够验证 SAML 断言。Spring Security 同时支持多种身份验证类型,并且只公开一个通用的 AuthenticationManager Bean。//对应各种功能的过滤器

        ProviderManager 还允许配置一个父类 AuthenticationManager(可选),它在没有AuthenticationProvider 可以执行身份验证的情况下被使用。父类可以是任何类型的 AuthenticationManager,通常也是一个 ProviderManager 实例。

        多个 ProviderManager 实例可以共享同一个父 AuthenticationManager。该方式在存在多个 SecurityFilterChain 的场景中比较常见,这些安全过滤器链有一些共同的身份验证(共享的父AuthenticationManager),但也有不同的身份验证机制(不同的 ProviderManager 实例)。

        默认情况下,ProviderManager 会尝试从成功的身份验证请求返回的 Authentication 对象中清除敏感的凭据信息,防止信息(如密码)在 HttpSession 中保留的时间超过所需的时间。// Authentication 认证成功后,ProviderManager 会清除其中的认证凭据

        // 因为ProviderManager会清除Authentication认证凭据,所以缓存Authentication信息时应该注意此问题。

        解决缓存问题的两种方案:创建 Authentication 对象的副本或者禁用 ProviderManager 上的 eraseCredentialsAfterAuthentication 属性。

        【ProviderManager.authenticate 源码分析】:

//执行认证的AuthenticationProvider列表
private List<AuthenticationProvider> providers;  

//认证流程
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        AuthenticationException parentException = null;
        Authentication result = null;
        Authentication parentResult = null;
        int currentPosition = 0;
        int size = this.providers.size();
        //1-获取Provider列表迭代器
        Iterator var9 = this.getProviders().iterator(); 

        //2-开始循环遍历Provider列表
        while(var9.hasNext()) { 
            AuthenticationProvider provider = (AuthenticationProvider)var9.next();
            if (provider.supports(toTest)) {

                //省略...

                try {
                    //3-使用其中一个provider进行身份验证
                    result = provider.authenticate(authentication);
                    if (result != null) {
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (InternalAuthenticationServiceException | AccountStatusException var14) {
                    this.prepareException(var14, authentication);
                    throw var14;
                } catch (AuthenticationException var15) {
                    lastException = var15;
                }
            }
        }

        //省略...(这里省略了付实例的认证,也很简单,可查看源码)

        //4-如果认证成功,在这里擦除密码/认证凭据等信息
        if (result != null) { 
            if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
                ((CredentialsContainer)result).eraseCredentials(); //擦除认证凭据
            }

            if (parentResult == null) {
                this.eventPublisher.publishAuthenticationSuccess(result);
            }

            return result;
        } else {
            //5-抛出providerNotFound异常
            if (lastException == null) {
                lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
            }

            if (parentException == null) {
                this.prepareException((AuthenticationException)lastException, authentication);
            }

            throw lastException;
        }
    }

7、AuthenticationProvider 特定类型的身份验证接口

        用于处理特定身份验证的统一接口。

        通常可以将多个 AuthenticationProvider 实例注入到 ProviderManager 中。每个AuthenticationProvider 执行特定类型的身份验证。例如,DaoAuthenticationProvider 支持基于用户名/密码的身份验证,而 JwtAuthenticationProvider 支持验证 JWT 令牌。

public interface AuthenticationProvider {
    //执行身份验证
    Authentication authenticate(Authentication var1) throws AuthenticationException;

    //如果此AuthenticationProvider支持指定的Authentication对象,则返回true。
    boolean supports(Class<?> var1);
}

8、AuthenticationEntryPoint 身份验证方案接口

        AuthenticationEntryPoint 作用:由 ExceptionTranslationFilter 用于启动一个身份验证方案

public interface AuthenticationEntryPoint {
    //启动认证方案
    void commence(HttpServletRequest var1, HttpServletResponse var2, AuthenticationException var3) throws IOException, ServletException;
}

        ExceptionTranslationFilter 将填充 AbstractAuthenticationProcessingFilter HttpSession 属性。在调用此方法之前,使用所请求的目标URL

        实现根据需要修改 ServletResponse 上的报文头,以启动身份验证过程。AuthenticationEntryPoint 的实现可能会执行重定向到登录页面、使用 WWW-Authenticate 头响应或采取其他操作。

9、AbstractAuthenticationProcessingFilter 用户凭据验证过滤器

        AbstractAuthenticationProcessingFilter 用作验证用户凭据的基本过滤器。在对凭证进行身份验证之前,Spring Security 通常会使用 AuthenticationEntryPoint 来请求凭证。

        【AbstractAuthenticationProcessingFilter.doFilter 源码】//总流程

    //身份认证过滤器的处理流程
    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (!this.requiresAuthentication(request, response)) {
            chain.doFilter(request, response);
        } else {
            try {
                //1- 尝试进行身份验证
                Authentication authenticationResult = this.attemptAuthentication(request, response);
                if (authenticationResult == null) {
                    return;
                }
                //2-如果验证成功,创建新会话,更新sessionId
                this.sessionStrategy.onAuthentication(authenticationResult, request, response);
                if (this.continueChainBeforeSuccessfulAuthentication) {
                    chain.doFilter(request, response);
                }
                //3-调用认证成功后处理流程
                this.successfulAuthentication(request, response, chain, authenticationResult);
            } catch (InternalAuthenticationServiceException var5) {
                this.logger.error("An internal error occurred while trying to authenticate the user.", var5);
                //4-调用认证失败后处理流程
                this.unsuccessfulAuthentication(request, response, var5);
            } catch (AuthenticationException var6) {
                //4-调用认证失败后处理流程
                this.unsuccessfulAuthentication(request, response, var6);
            }
        }
    }

        接下来,AbstractAuthenticationProcessingFilter 可以对提交给它的任何身份验证请求进行身份验证

    //过滤器中尝试进行身份验证的抽象方法
    public abstract Authentication attemptAuthentication(HttpServletRequest var1, HttpServletResponse var2) throws AuthenticationException, IOException, ServletException;

    //一个具体实现类:UsernamePasswordAuthenticationFilter的实现逻辑
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            String username = this.obtainUsername(request);
            username = username != null ? username : "";
            username = username.trim();
            String password = this.obtainPassword(request);
            password = password != null ? password : "";

            //使用用户名和密码创建一个Authentication对象
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);

            //调用特定的认证管理器进行认证,通常为(ProviderManager)
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

        AbstractAuthenticationProcessingFilter 进行认证的流程图示:

        当用户提交凭据时,AbstractAuthenticationProcessingFilter HttpServletRequest 创建一个要进行身份验证的 Authentication 对象。创建的身份验证类型取决于AbstractAuthenticationProcessingFilter 的子类。例如,UsernamePasswordAuthenticationFilter HttpServletRequest 中提交的用户名和密码中创建 UsernamePasswordAuthenticationToken//看上边的源码展示

        接下来,将 Authentication 对象 传递到 AuthenticationManager 中进行身份验证。

        如果身份验证失败,则执行 Failure 流程://失败流程,有许多的拓展点

        (1)调用 SecurityContextHolder.clearContext 方法清除 Security 上下文。//ThreadLocal.remove

        (2)调用 RememberMeServices.loginFail 方法。有两个实现类 NullRememberMe 和 RememberMe。 //RememberMe会失效Cookie

        (3)调用 AuthenticationFailureHandler.onAuthenticationFailure 方法。//抛出错误信息或者重定向

        AbstractAuthenticationProcessingFilter.unsuccessfulAuthentication 源码】

    //认证失败处理流程
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {

        //1-清除SecurityContextHolder
        SecurityContextHolder.clearContext();

        this.logger.trace("Failed to process authentication request", failed);
        this.logger.trace("Cleared SecurityContextHolder");
        this.logger.trace("Handling authentication failure");

        //2-调用RememberMeServices.loginFail
        this.rememberMeServices.loginFail(request, response);

        //3-调用AuthenticationFailureHandler
        this.failureHandler.onAuthenticationFailure(request, response, failed);
    }

        如果身份验证成功,则执行 Success 流程:

        (1)SessionAuthenticationStrategy.onAuthentication 会收到新的登录通知。

        (2)在 SecurityContextHolder 中设置通过认证的用户信息,方便后续取用。

        (3)调用 RememberMeServices.loginSuccess 方法。//通过此方法可自定义成功后的处理方式。

        (4)调用 ApplicationEventPublisher.publishEvent 方法发布一个InteractiveAuthenticationSuccessEvent 事件。//用户可通过监听此事件来做进一步操作

        (5)调用 AuthenticationSuccessHandler.onAuthenticationSuccess 方法。//处理url重定向等

        【AbstractAuthenticationProcessingFilter.successfulAuthentication 源码】

    //认证成功处理流程
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {

        //1-在 SecurityContextHolder 中设置通过认证的用户信息,方便后续取用
        SecurityContextHolder.getContext().setAuthentication(authResult);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
        }

        //2-调用 RememberMeServices.loginSuccess 方法
        this.rememberMeServices.loginSuccess(request, response, authResult);
        if (this.eventPublisher != null) {

            //3-发布认证成功事件
            this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
        }

        //4-调用AuthenticationSuccessHandler.onAuthenticationSuccess方法
        this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }

        最后,熟知用户的认证流程,对熟练使用 Spring Security 非常重要。需要多学习,多总结。

        至此,全文到此结束。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/785950.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

西北乱跑娃 -- CSS动态旋转果冻效果

<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>旋转果冻</title> <style> #myDIV {margin: 250px;width: 250px;height: 250px;background: orange;position: relative;font-size: 20px;animation: anima…

【前缀和】LeetCode 560. 和为k的字数组

文章目录 题目描述方法1 暴力方法2 暴力优化方法3 前缀和方法4 前缀和优化 题目描述 力扣560题&#xff0c;链接&#xff1a;https://leetcode.cn/problems/subarray-sum-equals-k 方法1 暴力 暴力法&#xff0c;三重for循环&#xff0c;时间复杂度 O ( N 3 ) O(N^3) O(N3)&a…

[回馈]ASP.NET Core MVC开发实战之商城系统(二)

经过一段时间的准备&#xff0c;新的一期【ASP.NET Core MVC开发实战之商城系统】已经开始&#xff0c;在之前的文章中&#xff0c;讲解了商城系统的整体功能设计&#xff0c;页面布局设计&#xff0c;环境搭建&#xff0c;系统配置&#xff0c;及首页商品类型&#xff0c;bann…

一百三十五、Azkaban——AzkabanWebServer服务开启后秒退

一、问题 Azkaban的AzkabanWebServer服务开启后秒退&#xff0c;造成Azkaban的页面登录不上 AzkabanWebServer服务开启后&#xff0c;第一个jps里面有AzkabanWebServer&#xff0c;随后第二个jps里面没有AzkabanWebServer 二、问题原因 MySQL中azkaban数据库的表executors的…

PDF转换成word乱码了怎么办?最实用的方法在这里!

在日常办公中&#xff0c;我们常常需要将PDF文件转换成Word文件&#xff0c;以便于编辑和修改。然而有时候在PDF转Word的过程中可能会遇到乱码的问题&#xff0c;让人感到困扰。在面对这种情况时&#xff0c;我们需要选择正确的方法&#xff0c;避免文件转换后出现乱码。下面我…

面积阵列封装技术-BGA CSP/Flip Chip

面积阵列封装有不同的类型&#xff0c;因此也就有着不 同的结构特点和组装方式。面积阵列封装以其结构 形 式 可 分 为 两 大 类 :BGA/CSP 和 倒 装 芯 片&#xff08;Flip Chip&#xff09;。 1 BGA技术 BGA 形式封装是 1980 年由富士通公司提出&#xff0c; 在日本 IBM 公司…

Excel处理数据的常用函数整理

1.常用函数 2.常见数学和三角函数 excel求逆矩阵的例子&#xff1a; 3.统计函数

图像滤波器

图像噪声 • 图像噪声是图像在获取或是传输过程中受到随机信号干扰&#xff0c;妨碍人们对图像理解及分析处理 的信号。 • 图像噪声的产生来自图像获取中的环境条件和传感元器件自身的质量&#xff0c;图像在传输过程中产 生图像噪声的主要因素是所用的传输信道受到了噪声…

mysql用is null查不生效的一种情况

今天发现了一个诡异的情况&#xff1a; 首先是在记录中可以明显看到 update_time有null记录。 但用一下sql查询&#xff1a; SELECT count(*) FROM risknoting WHERE noting_state未标注 and update_time is NULL得到结果却是0条。 但应该无论如何都要大于等于1条。 因此&am…

互联网医院系统|互联网医院软件开发|互联网医院搭建方案

随着互联网技术的发展&#xff0c;互联网医院系统逐渐成为医疗服务的新模式&#xff0c;为患者和医生提供了更加方便和高效的医疗体验。下面将介绍互联网医院系统的功能优势。   在线挂号和预约&#xff1a;互联网医院系统可以提供在线挂号和预约功能&#xff0c;患者可以通过…

Mysql关于进程中的死锁和解除锁

Mysql 经常会遇到语句或者存储过程长时间没有反应&#xff0c;大概率就是挂掉了&#xff0c;或者死锁了。 可通过如下几种方式来查看当前进程状态 1. 查询数据库所有的进程状态 SHOW PROCESSLIST SELECT * FROM information_schema.PROCESSLIST; 2. 查询在锁的事务 SELECT…

Unity TMP (TextMeshPro) 创建字体材质

1 TMP 简介 完整名称&#xff1a;Text Mesh Pro &#xff0c;unity新一代主流字体插件 1.1 组件变化 内置的Text组件以及与内置Text组件绑定的Button、DropDown、InputField均被替换为使用TextMeshPro的版本 内置的Text组件以及与内置Text组件绑定的Button、DropDown、Input…

Python基于PyTorch实现循环神经网络分类模型(LSTM分类算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 LSTM网络是目前更加通用的循环神经网络结构&#xff0c;全称为Long Short-Term Memory&#xff0c;翻…

事业单位-语言理解1】逻辑填空-词的辨析04

事业单位-语言理解1】逻辑填空04 一、词的辨析1.1词义侧重瞻前顾后 三、程度轻重四、感情色彩五&#xff0c;今日词义搭配 一、词的辨析 2022年联考常见词汇 1.1词义侧重 推脱&#xff0c;推托&#xff0c;可以把拆开&#xff0c;单单看脱和托的区别。 注意前后的主语和宾…

性能测试请求重试实现思路

文章目录 一、背景二、尝试的解决方案三、解决方案1&#xff1a;jmeter retrier插件&#xff01;有点用但是不是特别有用-_-||四&#xff0c;最终解决方案&#xff1a;lucust! 一、背景 最近系统需要压测一些活动&#xff0c;场景是新建抽奖活动之后&#xff0c;每隔2s查询1次…

Java 稀疏数组

实际需求&#xff08;应用场景&#xff09; 稀疏数组基本介绍 上面表格的第一行中的6、7、8 表示左边的二维数组一共有 6 行 7 列&#xff0c;在数组中有 8 个不同的值&#xff0c;分别是22、15、 11、17、 -6、 39、 91、 28. 表格的其他行表示每个值所在的行列信息&#xff0…

斯坦福开源FlashAttention,大模型速度翻倍

一年时间&#xff0c;斯坦福大学提出的新型 Attention 算法 ——FlashAttention 完成了进化。这次在算法、并行化和工作分区等方面都有了显著改进&#xff0c;对大模型的适用性也更强了。 近来&#xff0c;几种长上下文语言模型陆续问世&#xff0c;包括 GPT-4&#xff08;上下…

【C#性能】C# 语言中的数组迭代

一、说明 可迭代性&#xff0c;是数组等操作的根本&#xff1b;在C程序开发过程中&#xff0c;可迭代操作是非常普遍、非常广泛的&#xff0c;然而&#xff0c;对这种操作知道多少&#xff0c;又不知道多少&#xff0c;都将影响开发灵活性、开发的进度。因此&#xff0c;本文干…

vs2013 编译wxwidgets界面库

首先进入官网下载&#xff0c;本人再git上下载的基本都编译失败 https://www.wxwidgets.org/ 在网站里面找最新的就可以&#xff0c;下载之后放在一个目录&#xff0c;找到vs的目录 然后找到wx_vc12.sln&#xff0c;打开编译即可 Debug、Release编译出来的是静态库 DLL Deb…

P1149火柴棒等式题解

P1149[NOIP2008 提高组] 火柴棒等式 题目描述 给你 n n n 根火柴棍&#xff0c;你可以拼出多少个形如 A B C ABC ABC 的等式&#xff1f;等式中的 A A A、 B B B、 C C C 是用火柴棍拼出的整数&#xff08;若该数非零&#xff0c;则最高位不能是 0 0 0&#xff09;。用…