JAAS 程序化登录

程序化登录是一种表单登录类型,为了认证而支持特定于应用程序显示站点的登录形式。

当企业 Bean 客户机应用程序要求用户提供标识信息时,应用程序的写程序必须收集该信息并认证该用户。 可以根据实际执行用户认证所在的位置来将程序员的工作粗略划分为:

  • 在客户机程序中
  • 在服务器程序中

Web 应用程序的用户可使用许多方法来接收认证数据的提示。 这<登录配置> Web 应用程序部署描述符文件中的元素定义了用于收集此信息的机制。 要定制登录过程而不是依赖于通用的设备(如浏览器中的 401 对话窗口)的程序员,可以使用基于表单的登录,以提供用于搜集登录信息的特定于应用程序的 HTML 表单。

除非发生以下情况,否则不进行身份验证行政安全已启用。 如果要使用基于表单的 Web 应用程序登录,则必须指定形式在 auth-method 标签中<登录配置>每个 Web 应用程序的部署描述符中的元素。

应用程序可以使用以下方式呈现特定于站点的登录表单: WebSphere® Application Server表单登录类型。 这Java™ Platform, Enterprise Edition( Java EE )规范将表单登录定义为Web应用程序的身份验证方法之一。 WebSphere Application Server提供表单注销机制。

Java 身份验证和授权服务编程式登录

Java 身份验证和授权服务 (JAAS)是WebSphere Application Server。 这也是Java EE 1.4规格。 JAAS 是一组策略认证应用程序编程接口 (API),它们取代了公共对象请求代理体系结构 (CORBA) 程序化登录 API。 WebSphere Application Server提供了一些扩展JAAS:

在开始使用程序化登录 API 进行开发之前,请考虑以下要点:
  • 对于纯 Java 客户端应用程序或客户端容器应用程序,在执行JAAS登录。 在进行 JAAS 登录之前通过执行以下代码来完成此操作:
    ...
    import java.util.Hashtable;
    import javax.naming.Context;
    import javax.naming.InitialContext;
    ...
    // Perform an InitialContext and default lookup prior to logging 
    // in to initialize ORB security and for the bootstrap host/port 
    // to be determined for SecurityServer lookup. If you do not want 
    // to validate the userid/password during the JAAS login, disable 
    // the com.ibm.CORBA.validateBasicAuth property in the 
    // sas.client.props file.
    
    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY, 
        "com.ibm.websphere.naming.WsnInitialContextFactory");
    env.put(Context.PROVIDER_URL, 
         "corbaloc:iiop:myhost.mycompany.com:2809");
    Context initialContext = new InitialContext(env);
    Object obj = initialContext.lookup("");
    
  • 对于纯 Java 客户端应用程序或客户端容器应用程序,请确保正确指定目标 Java 命名和目录接口 (JNDI) 引导属性的主机名和端口号。 查看开发使用CosNaming(CORBA 命名接口)部分了解详情。
  • 如果应用程序使用自定义JAAS登录配置,确保风俗JAAS登录配置已正确定义
  • 一些JAASAPI 受 Java 2 安全权限保护。 如果这些 API 由应用程序代码使用,请确保这些许可权添加到应用程序 was.policy 文件。 看添加was.policy文件到应用程序以实现 Java 2 安全性使用PolicyTool编辑 Java 2 安全性的策略文件配置was.policyJava 2 安全性文件部分了解详情。 有关哪些 API 受 Java 2 安全权限保护的详细信息,请查看IBM®开发工具包,Java 技术版;JAAS和WebSphere Application Server公共 API 文档以了解更多详细信息。 以下列表包含在此文档中提供的样本代码中所使用的 API。
    • 受 javax.security.auth.AuthPermission "createLoginContext" 保护的 javax.security.auth.login.LoginContext 构造函数。
    • 受 javax.security.auth.AuthPermission "doAs" 保护的 javax.security.auth.Subject.doAs 和 com.ibm.websphere.security.auth.WSSubject.doAs。
    • 受 javax.security.auth.AuthPermission "doAsPrivileged" 保护的 javax.security.auth.Subject.doAsPrivileged 和 com.ibm.websphere.security.auth.WSSubject.doAsPrivileged。
  • com.ibm.websphere.security.auth.WSSubject:由于 JAAS 1.0 中的设计疏忽,javax.security.auth.Subject.getSubject 不返回与 java.security.AccessController.doPrivileged 代码块中正在运行的线程相关联的主体集。 这会产生有问题的不一致行为,并且要花费额外的精力来解决问题。 com.ibm.websphere.security.auth.WSSubject API 提供了使主体集与正在运行的线程相关联的变通方法。 这com.ibm.websphere.security.auth.WSSubject API 扩展了JAAS模型Java EE进行授权检查的资源。 与正在运行的线程关联的主题com.ibm.websphere.security.auth.WSSubject.doAs或者com.ibm.websphere.security.auth.WSSubject.doAsPrivileged代码块用于Java EE资源授权检查。
  • 管理控制台支持定义新的JAAS登录配置:您可以配置JAAS管理控制台中的登录配置并将其存储在WebSphere Application Server配置 API。 应用程序可以定义新的JAAS管理控制台中的登录配置,数据保存在与WebSphere Application Server配置 API。 然而, WebSphere Application Server仍然支持默认JAAS登录配置格式由JAAS默认实现。 如果在两个文件中都定义了重复登录配置WebSphere Application Server配置 API 和纯文本文件格式,登录配置在WebSphere Application Server配置 API 优先。 在中定义登录配置的优点WebSphere Application Server配置API包括:
    • 使用管理控制台定义 JAAS 登录配置。
    • 可以集中管理 JAAS 登录配置。
  • JAAS登录配置WebSphere Application Server WebSphere Application Server提供JAAS应用程序登录配置,用于执行编程身份验证WebSphere Application Server安全运行时。 这些JAAS登录配置WebSphere Application Server根据配置的身份验证机制执行身份验证,简单WebSphere身份验证机制 (SWAM) 或轻量级第三方身份验证 (LTPA),以及基于所提供的身份验证数据的用户注册表(本地操作系统、LDAP 或自定义)。 来自这些经过身份验证的主题JAAS登录配置包含所需的主体和凭据,可供WebSphere Application Server安全运行时执行授权检查Java EE基于角色的受保护资源。
    笔记: SWAM 已弃用WebSphere Application Server版本8.5并将在未来版本中被删除。
    这里有JAAS提供的登录配置WebSphere Application Server
    • 网页登录JAAS登录配置:通用JAASJava 客户端、客户端容器应用程序、servlet、JSP 文件、企业 Bean 等可以使用登录配置来执行基于用户 ID 和密码的身份验证,或基于令牌的身份验证WebSphere Application Server安全运行时。 但是,此配置不支持在客户机容器部署描述符中指定的 CallbackHandler 处理程序。
    • ClientContainer JAAS 登录配置:此 JAAS 登录配置识别在客户机容器部署描述符中指定的 CallbackHandler 处理程序。 此登录配置的登录模块将使用客户机容器部署描述符中的 CallbackHandler 处理程序(如果指定了一个的话),即使应用程序代码在登录上下文中指定了一个 CallbackHandler 处理程序。 这适用于客户机容器应用程序。
    • 使用先前提到的 JAAS 登录配置认证的主体集包含 com.ibm.websphere.security.auth.WSPrincipal 主体和 com.ibm.websphere.security.auth.WSCredential 凭证。 如果经过身份验证的主题被传递给com.ibm.websphere.security.auth.WSSubject.doAs方法或其他doAs方法, WebSphere Application Server安全运行时可以执行授权检查Java EE资源,基于主题com.ibm.websphere.security.auth.WSCredential凭据。
  • 客户定义JAAS登录配置:您可以定义其他JAAS登录配置。 使用这些登录配置来对定制认证机制执行程序化认证。 然而,这些客户定义的主题JAAS登录配置可能未被使用WebSphere Application Server如果主题不包含所需的主体和凭证,则安全运行时执行授权检查。

查找来自 JAAS 登录的根本原因登录异常

如果在发出 LoginContext.login API 之后产生 LoginException 异常,那么可从已配置的用户注册表中找到根本原因异常。 在登录模块中,注册表异常包括在 com.ibm.websphere.security.auth.WSLoginFailedException 类中。 此异常具有 getCause 方法,可以通过此方法拉出在发出上述命令后包括的异常。

您并不是总会获得 WSLoginFailedException 异常,但是,用户注册表中生成的大多数异常都显示在此处。 以下示例说明了 LoginContext.login API 以及相关联的 catch 块。 如果想要发出 getCause API,那么将 WSLoginFailedException 异常强制类型转换为 com.ibm.websphere.security.auth.WSLoginFailedException 类。

下面的 determineCause 示例可用于处理 CustomUserRegistry 异常类型。
try 
    {
         lc.login(); 
    } 
    catch (LoginException le)
    {
	// drill down through the exceptions as they might cascade through the runtime
	Throwable root_exception = determineCause(le);
	
	// now you can use "root_exception" to compare to a particular exception type
	// for example, if you have implemented a CustomUserRegistry type, you would 
  //  know what to look for here.
    }


/* Method used to drill down into the WSLoginFailedException to find the 
"root cause" exception */

    public Throwable determineCause(Throwable e) 
      {
				Throwable root_exception = e, temp_exception = null;

				// keep looping until there are no more embedded WSLoginFailedException or 
				// WSSecurityException exceptions 
         while (true) 
				{
						if (e instanceof com.ibm.websphere.security.auth.WSLoginFailedException)
						{
							temp_exception = ((com.ibm.websphere.security.auth.WSLoginFailedException)
							e).getCause();
						}
						else if (e instanceof com.ibm.websphere.security.WSSecurityException)
						{
							temp_exception = ((com.ibm.websphere.security.WSSecurityException)
							e).getCause();
						}
						else if (e instanceof javax.naming.NamingException)
								// check for Ldap embedded exception
								{
										temp_exception = ((javax.naming.NamingException)e).getRootCause();
								}
						else if (e instanceof your_custom_exception_here)
						{
								// your custom processing here, if necessary
						}
						else
						{
								// this exception is not one of the types we are looking for,
								// lets return now, this is the root from the WebSphere 
								//  Application Server perspective
								return root_exception;
						}
						if (temp_exception != null)
						{
								// we have an exception; go back and see if this has another
								// one embedded within it.
								root_exception = temp_exception;
								e = temp_exception;
								continue;
						}
						else
						{
								// we finally have the root exception from this call path, this
								// has to occur at some point
								return root_exception;
						}
				}
		}

查找来自 Servlet 过滤器的根本原因登录异常

您还会在处理 post-form 登录处理时接收到来自 Servlet 过滤器的根本原因异常。 此异常很有用,这是因为它会告诉用户发生的情况。 可以发出以下 API 以获取根本原因异常:
Throwable t = com.ibm.websphere.security.auth.WSSubject.getRootLoginException();  
if (t != null)  	
         t = determineCause(t);

发生异常之后,可以通过前面的 determineCause 示例运行它,以获得本机注册表根本原因。

允许将根本原因登录异常传播至纯 Java 客户端

当前,为安全起见,根本原因不会传播到纯客户机。 但是,可能要在可信环境中将根本原因传播到纯客户机。 如果要启用将根源登录异常传播到纯客户端,请单击安全>全球安全>自定义属性WebSphere Application Server管理控制台并设置以下属性:

com.ibm.websphere.security.registry.propagateExceptionsToClient=true

不提示程序化登录

WebSphere Application Server提供了一个非提示的实现javax.security.auth.callback.CallbackHandler接口,称为com.ibm.websphere.security.auth.callback.WSCallbackHandlerImpl。 使用此接口,应用程序可将认证数据推入 WebSphere LoginModule 实例以执行认证。 此功能对于服务器端应用程序代码验证身份以及使用该身份调用下游Java EE资源。
javax.security.auth.login.LoginContext lc = null;

try {
lc = new javax.security.auth.login.LoginContext("WSLogin",
new com.ibm.websphere.security.auth.callback.WSCallbackHandlerImpl("user", 
      "securityrealm", "securedpassword"));

// create a LoginContext and specify a CallbackHandler implementation
// CallbackHandler implementation determine how authentication data is collected
// in this case, the authentication data is "push" to the authentication mechanism
//   implemented by the LoginModule.
} catch (javax.security.auth.login.LoginException e) {
System.err.println("ERROR: failed to instantiate a LoginContext and the exception: " 
+ e.getMessage());
e.printStackTrace();

// maybe javax.security.auth.AuthPermission "createLoginContext" is not granted
//   to the application, or the JAAS login configuration is not defined.
}

if (lc != null)
try {
lc.login();  // perform login
javax.security.auth.Subject s = lc.getSubject();
// get the authenticated subject

// Invoke a Java EE resource using the authenticated subject
com.ibm.websphere.security.auth.WSSubject.doAs(s,
new java.security.PrivilegedAction() {
public Object run() {
try {
bankAccount.deposit(100.00);  // where bankAccount is a protected EJB
} catch (Exception e) {
System.out.println("ERROR: error while accessing EJB resource, exception: " 
+ e.getMessage());
e.printStackTrace();
}
return null;
}
}
);
} catch (javax.security.auth.login.LoginException e) {
System.err.println("ERROR: login failed with exception: " + e.getMessage());
e.printStackTrace();

// login failed, might want to provide relogin logic
}

您可以使用com.ibm.websphere.security.auth.callback.WSCallbackHandlerImpl带有纯 Java 客户端的回调处理程序、客户端应用程序容器、企业 Bean、 JavaServer页面(JSP)文件、servlet 或其他 Java 2 平台,Enterprise Edition( Java EE ) 资源。

笔记:这WSCallbackHandlerImpl回调处理程序会有所不同,具体取决于您是否使用WebSphere Application Server安全或 Web 服务安全。 如果使用 WebSphere Application Server 安全性,那么该回调处理程序将位于 sas.jar 文件中;如果使用 Web Service 安全性,那么位于 was-wssecurity.jar 文件中。

用户界面提示程序化登录

WebSphere Application Server还提供了用户界面实现javax.security.auth.callback.CallbackHandler通过用户界面登录提示从用户收集身份验证数据的实现。 com.ibm.websphere.security.auth.callback.WSGUICallbackHandlerImpl 回调处理程序会提供一个用户界面登录面板,以提示用户输入认证数据。
javax.security.auth.login.LoginContext lc = null;

try {
lc = new javax.security.auth.login.LoginContext("WSLogin",
new com.ibm.websphere.security.auth.callback.WSGUICallbackHandlerImpl());

// create a LoginContext and specify a CallbackHandler implementation
// CallbackHandler implementation determine how authentication data is collected
// in this case, the authentication date is collected by GUI login prompt
//   and pass to the authentication mechanism implemented by the LoginModule.
} catch (javax.security.auth.login.LoginException e) {
System.err.println("ERROR: failed to instantiate a LoginContext and the exception: " 
+ e.getMessage());
e.printStackTrace();

// maybe javax.security.auth.AuthPermission "createLoginContext" is not granted
//   to the application, or the JAAS login configuration is not defined.
}

if (lc != null)
try {
lc.login();  // perform login
javax.security.auth.Subject s = lc.getSubject();
// get the authenticated subject

// Invoke a Java EE resources using the authenticated subject
com.ibm.websphere.security.auth.WSSubject.doAs(s,
new java.security.PrivilegedAction() {
public Object run() {
try {
bankAccount.deposit(100.00);  // where bankAccount is a protected enterprise bean
} catch (Exception e) {
System.out.println("ERROR: error while accessing EJB resource, exception: " 
+ e.getMessage());
e.printStackTrace();
}
return null;
}
}
);
} catch (javax.security.auth.login.LoginException e) {
System.err.println("ERROR: login failed with exception: " + e.getMessage());
e.printStackTrace();

// login failed, might want to provide relogin logic
}
注意力:请勿使用com.ibm.websphere.security.auth.callback.WSGUICallbackHandlerImpl服务器端资源(例如企业 Bean、servlet、JSP 文件等)的回调处理程序。 用户界面登录提示阻拦服务器的用户输入。 此行为对于服务器进程不利。

WebSphere Application Server还提供了Kerberos凭证缓存实现javax.security.auth.callback.CallbackHandler界面。 回调处理程序 com.ibm.websphere.security.auth.callback.WSCcacheCallBackHandlerImpl。 使用此接口,应用程序可将认证数据推入 WebSphere LoginModule 实例以执行认证。

此功能仅适用于客户端应用程序代码进行身份验证WebSphere Application Server与Kerberos凭证缓存。

如果 wsjaas_client.conf 文件中存在下列选项,请将它们设置为 false:
useDefaultKeytab=false
useDefaultCcache=false
tryFirstPass=false
useFirstPass=false
可转发=false
可再生=假
可再生=假
无地址=false
javax.security.auth.login.LoginContext lc = null;

String krb5Ccache = /etc/krb5/krb5cc_utle;

try {
lc = new javax.security.auth.login.LoginContext("WSKRB5Login",
new com.ibm.websphere.security.auth.callback.WSCcacheCallBackHandlerImpl(user, krb5Realm, krb5Ccache, false));
// create a LoginContext and specify a CallbackHandler implementation
// CallbackHandler implementation determines how authentication data is collected
// in this case, the authentication date is collected by stdin prompt
// and passed to the authentication mechanism implemented by the LoginModule.
} catch (javax.security.auth.login.LoginException e) {
System.err.println("ERROR: failed to instantiate a LoginContext and the exception: 
          " + e.getMessage());
e.printStackTrace();

// maybe javax.security.auth.AuthPermission "createLoginContext" is not granted
//   to the application, or the JAAS login configuration is not defined.
}

if (lc != null)
try {
lc.login();  // perform login
javax.security.auth.Subject s = lc.getSubject();
// get the authenticated subject

// Invoke a Java EE resource using the authenticated subject
com.ibm.websphere.security.auth.WSSubject.doAs(s,
new java.security.PrivilegedAction() {
public Object run() {
try {
bankAccount.deposit(100.00);  
// where bankAccount is a protected enterprise bean
} catch (Exception e) {
System.out.println("ERROR: error while accessing EJB resource, exception: " 
       + e.getMessage());
e.printStackTrace();
}
return null;
}
}
);
} catch (javax.security.auth.login.LoginException e) {
System.err.println("ERROR: login failed with exception: " + e.getMessage());
e.printStackTrace();

// login failed, might want to provide relogin logic
}

Application Server with the default Kerberos credential cache.

javax.security.auth.login.LoginContext lc = null;

try {
lc = new javax.security.auth.login.LoginContext("WSKRB5Login",
new com.ibm.websphere.security.auth.callback.WSCcacheCallBackHandlerImpl(user, krb5Realm, null, true));
// create a LoginContext and specify a CallbackHandler implementation
// CallbackHandler implementation determines how authentication data is collected
// in this case, the authentication date is collected by stdin prompt
// and passed to the authentication mechanism implemented by the LoginModule.
} catch (javax.security.auth.login.LoginException e) {
System.err.println("ERROR: failed to instantiate a LoginContext and the exception: 
          " + e.getMessage());
e.printStackTrace();

// maybe javax.security.auth.AuthPermission "createLoginContext" is not granted
//   to the application, or the JAAS login configuration is not defined.
}

if (lc != null)
try {
lc.login();  // perform login
javax.security.auth.Subject s = lc.getSubject();
// get the authenticated subject

// Invoke a Java EE resource using the authenticated subject
com.ibm.websphere.security.auth.WSSubject.doAs(s,
new java.security.PrivilegedAction() {
public Object run() {
try {
bankAccount.deposit(100.00);  
// where bankAccount is a protected enterprise bean
} catch (Exception e) {
System.out.println("ERROR: error while accessing EJB resource, exception: " 
       + e.getMessage());
e.printStackTrace();
}
return null;
}
}
);
} catch (javax.security.auth.login.LoginException e) {
System.err.println("ERROR: login failed with exception: " + e.getMessage());
e.printStackTrace();

// login failed, might want to provide relogin logic
}

Application Server with the Microsoft native Kerberos credential cache. The client must
login to the Microsoft Domain Controller.

javax.security.auth.login.LoginContext lc = null;

try {
lc = new javax.security.auth.login.LoginContext("WSKRB5Login",
new com.ibm.websphere.security.auth.callback.WSCcacheCallBackHandlerImpl(null, null, null, true));
// create a LoginContext and specify a CallbackHandler implementation
// CallbackHandler implementation determines how authentication data is collected
// in this case, the authentication date is collected by stdin prompt
// and passed to the authentication mechanism implemented by the LoginModule.
} catch (javax.security.auth.login.LoginException e) {
System.err.println("ERROR: failed to instantiate a LoginContext and the exception: 
          " + e.getMessage());
e.printStackTrace();

// maybe javax.security.auth.AuthPermission "createLoginContext" is not granted
//   to the application, or the JAAS login configuration is not defined.
}

if (lc != null)
try {
lc.login();  // perform login
javax.security.auth.Subject s = lc.getSubject();
// get the authenticated subject

// Invoke a Java EE resource using the authenticated subject
com.ibm.websphere.security.auth.WSSubject.doAs(s,
new java.security.PrivilegedAction() {
public Object run() {
try {
bankAccount.deposit(100.00);  
// where bankAccount is a protected enterprise bean
} catch (Exception e) {
System.out.println("ERROR: error while accessing EJB resource, exception: " 
       + e.getMessage());
e.printStackTrace();
}
return null;
}
}
);
} catch (javax.security.auth.login.LoginException e) {
System.err.println("ERROR: login failed with exception: " + e.getMessage());
e.printStackTrace();

// login failed, might want to provide relogin logic
}

WSKRB5Login 模块

这WSKRB5LoginJAAS登录配置:是通用的JAASJava 客户端、客户端容器应用程序、servlet、JSP 文件或企业 Bean 可以使用该登录配置来执行基于Kerberos主体名称密码或Kerberos凭证缓存WebSphere Application Server安全运行时。 但是,此配置不支持在客户机容器部署描述符中指定的 CallbackHandler 处理程序。

将您所创建的 krb5.inikrb5.conf 文件放入缺省位置。 如果其中任一文件不在缺省位置,那么必须使用正确的路径和 Kerberos 配置文件名称来设置 java.security.krb5.conf JVM 系统属性。

在 Windows® 平台上,缺省位置为 c:\winnt\krb5.ini。

在 Linux® 平台上,缺省位置为 /etc/krb5.conf。

在其他 Unix 平台上,缺省位置为 /etc/krb5/krb5.conf。

在 z/OS 平台上,缺省位置为 /etc/krb5/krb5.conf。

Kerberos 配置设置、Kerberos 密钥分发中心 (KDC) 名称和领域设置将在 Kerberos 配置文件中提供,或者通过 java.security.krb5.kdc 和 java.security.krb5.realm 系统属性文件提供。

Stdin 提示程序化登录

WebSphere Application Server还提供了javax.security.auth.callback.CallbackHandler界面。 回调处理程序 com.ibm.websphere.security.auth.callback.WSStdinCallbackHandlerImpl 通过 stdin 提示来提示用户输入认证数据和搜集认证数据。
javax.security.auth.login.LoginContext lc = null;

try {
lc = new javax.security.auth.login.LoginContext("WSLogin",
new com.ibm.websphere.security.auth.callback.WSStdinCallbackHandlerImpl());

// create a LoginContext and specify a CallbackHandler implementation
// CallbackHandler implementation determines how authentication data is collected
// in this case, the authentication date is collected by stdin prompt
// and passed to the authentication mechanism implemented by the LoginModule.
} catch (javax.security.auth.login.LoginException e) {
System.err.println("ERROR: failed to instantiate a LoginContext and the exception: 
          " + e.getMessage());
e.printStackTrace();

// maybe javax.security.auth.AuthPermission "createLoginContext" is not granted
//   to the application, or the JAAS login configuration is not defined.
}

if (lc != null)
try {
lc.login();  // perform login
javax.security.auth.Subject s = lc.getSubject();
// get the authenticated subject

// Invoke a Java EE resource using the authenticated subject
com.ibm.websphere.security.auth.WSSubject.doAs(s,
new java.security.PrivilegedAction() {
public Object run() {
try {
bankAccount.deposit(100.00);  
// where bankAccount is a protected enterprise bean
} catch (Exception e) {
System.out.println("ERROR: error while accessing EJB resource, exception: " 
       + e.getMessage());
e.printStackTrace();
}
return null;
}
}
);
} catch (javax.security.auth.login.LoginException e) {
System.err.println("ERROR: login failed with exception: " + e.getMessage());
e.printStackTrace();

// login failed, might want to provide relogin logic
}
注意力:请勿使用com.ibm.websphere.security.auth.callback.WSStdinCallbackHandlerImpl服务器端资源(例如企业 Bean、servlet、JSP 文件等)的回调处理程序。 来自 stdin 提示的输入未发送到服务器环境。 多数服务器在后台运行,并且没有控制台。 但是,如果服务器具有控制台,那么 stdin 提示阻拦服务器的用户输入。 此行为对于服务器进程不利。