Apache Shiro第2部分–领域,数据库和PGP证书

这是致力于Apache Shiro的系列文章的第二部分。 我们从简单的不安全Web应用程序开始了上一部分 。 完成后,该应用程序具有基本的身份验证和授权。 用户可以登录和注销。 所有网页和按钮均已分配和实施访问权限。 授权和身份验证数据都已存储在静态配置文件中。

正如我们在最后一部分中承诺的,我们将用户帐户数据移至数据库。 此外,我们将为用户提供通过PGP证书进行身份验证的选项。 因此,我们的应用程序将具有多个备用登录选项:使用用户名/密码登录和使用证书登录。 最后,我们将强制启用备用登录选项。

换句话说,我们将展示如何创建自定义领域以及如何处理多领域方案。 我们将创建三个不同版本的SimpleShiroSecuredApplication:

  • 版本,并将所有帐户信息移至数据库 ,
  • 允许PGP证书作为替代身份验证机制的版本 ,
  • 需要同时输入用户名/密码和PGP证书的版本 。

每个版本都有测试类RunWaitTest。 该类使用在http:// localhost:9180 / simpleshirosecuredapplication / url上部署的应用程序启动Web服务器。

注意:自第一版以来,我们更新了上一部分。 最显着的变化是新部分 ,该部分显示了如何向登录页面添加错误消息。 感谢大家的反馈。

境界

首先,我们解释什么是领域以及如何创建它们。 如果您对理论不感兴趣,请继续下一章 。
领域负责身份验证和授权。 每当用户想要登录到应用程序时,都会收集身份验证信息并将其传递到领域。 Realm验证提供的数据并决定是否应允许用户登录,访问资源或拥有特定角色。 认证信息包括两个部分:

  • 主体–代表帐户唯一标识符,例如用户名,帐户ID,PGP证书,…
  • 凭证–证明用户身份,例如密码,PGP证书,指纹等。

Shiro提供了能够从活动目录 , ldap , ini文件 , 属性文件和数据库中读取授权数据的领域。 在Shiro.ini文件的主要部分中配置领域:

realmName=org.apache.shiro.realm.jdbc.JdbcRealm

认证方式

所有领域都实现Realm接口。 有两种重要的接口方法:supports和getAuthenticationInfo。 两者都在身份验证令牌对象中接收主体和凭据。
Supports方法根据提供的身份验证令牌确定领域是否能够对用户进行身份验证。 例如,如果我的领域检查用户名和密码,则仅使用X509证书拒绝身份验证令牌。 方法getAuthenticationInfo本身执行身份验证。 如果来自身份验证令牌的主体和凭据表示有效的登录信息,则该方法返回身份验证信息对象。 否则,领域返回null。

授权书

如果领域也希望进行授权,则必须实现Authorizer接口。 每个Authorizer方法都将主体作为参数,并检查角色或权限。 重要的是要理解,该领域会获得所有授权请求,即使它们来自另一个领域进行了身份验证的用户也是如此。 当然,领域可以决定忽略任何授权请求。
权限以字符串或权限对象的形式提供。 除非有充分的理由,否则请使用WildcardPermissionResolver将字符串转换为权限对象。

其他选择

Shiro框架在运行时调查其他接口的领域。 如果领域实现了它们,则可以使用:

  • 有关用户注销的信息,
  • 有关系统启动的信息,
  • 全局缓存
  • 在配置文件中配置的名称 ,
  • 在权限字符串和权限对象之间配置的转换器 。

这些功能可用于实现其他接口的任何领域。 无需其他配置。
自定义领域

创建新领域的最简单方法是扩展AuthenticatingRealm或AuthorizingRealm类。 它们具有上一节中提到的所有有用接口的合理实现。 如果它们不能满足您的需求,则可以扩展CachingRealm或从头开始创建新领域。

移至数据库

当前版本的SimpleShiroSecuredApplication使用默认领域进行身份验证和授权。 默认领域– IniRealm从配置文件读取用户帐户信息。 这样的存储仅对于最简单的应用是可接受的。 任何稍微复杂的事情都需要将凭据存储在更好的持久性存储中。
新要求:帐户凭据和访问权限存储在数据库中。 存储的密码经过哈希处理和加盐处理。 在本章中,我们将应用程序连接到数据库并创建表以存储所有用户帐户数据。 然后,我们将IniRealm替换为能够从数据库和salt密码读取的领域。

数据库基础架构

本节介绍示例应用程序基础结构。 它不包含有关Shiro的信息,因此您可以自由地跳过它 。
示例应用程序以嵌入式模式使用Apache Derby数据库。
我们使用Liquibase进行数据库部署和升级。 它是开源库,用于跟踪,管理和应用数据库更改。 数据库更改(新表,新列,外键)存储在数据库更改日志文件中。 启动后,Liquibase会调查数据库并应用所有新更改。 结果,数据库始终保持一致并且是最新的,而我们却没有付出任何努力。 将对Derby和Liquibase的依赖项添加到SimpleShiroSecuredApplication pom.xml中 :

<dependency><groupid>org.apache.derby</groupid><artifactid>derby</artifactid><version>10.7.1.1</version>
</dependency>
<dependency><groupid>org.liquibase</groupid><artifactid>liquibase-core</artifactid><version>2.0.1</version>
</dependency>

将jndi添加到码头:

<dependency><groupid>org.mortbay.jetty</groupid><artifactid>jetty-naming</artifactid><version>${jetty.version}</version><scope>test</scope>
</dependency>  
<dependency><groupid>org.mortbay.jetty</groupid><artifactid>jetty-plus</artifactid><version>${jetty.version}</version><scope>test</scope>
</dependency>

使用数据库结构描述创建db.changelog.xml文件。 它创建用于存储用户,角色和权限的表。 它还用初始数据填充这些表。 我们使用random_salt_value_username作为盐,并使用以下方法创建哈希加盐的密码:

public static String simpleSaltedHash(String username, String password) {Sha256Hash sha256Hash = new Sha256Hash(password, (new SimpleByteSource('random_salt_value_' + username)).getBytes());String result = sha256Hash.toHex();System.out.println(username + ' simple salted hash: ' + result);return result;
}

在WEB-INF / jetty-web.xml文件中创建指向derby的数据源:

<configure class='org.mortbay.jetty.webapp.WebAppContext' id='SimpleShiroSecuredApplication'><new class='org.mortbay.jetty.plus.naming.Resource' id='SimpleShiroSecuredApplication'><arg>jdbc/SimpleShiroSecuredApplicationDB</arg><arg><new class='org.apache.derby.jdbc.EmbeddedDataSource'><set name='DatabaseName'>../SimpleShiroSecuredApplicationDatabase</set><set name='createDatabase'>create</set></new></arg></new>
</configure>

在web.xml文件中配置数据源和liquibase:

<resource-ref><description>Derby Connection</description><res-ref-name>jdbc/SimpleShiroSecuredApplicationDB</res-ref-name><res-type>javax.sql.DataSource</res-type><res-auth>Container</res-auth>
</resource-ref><context-param><param-name>liquibase.changelog</param-name><param-value>src/main/resources/db.changelog.xml</param-value>
</context-param><context-param><param-name>liquibase.datasource</param-name><param-value>jdbc/SimpleShiroSecuredApplicationDB</param-value>
</context-param><listener><listener-class>liquibase.integration.servlet.LiquibaseServletListener</listener-class>
</listener>

最终,在启用了jndi的情况下配置为读取jetty-web.xml的jetty在AbstractContainerTest类中。

创建新领域

Shiro提供的JDBCRealm能够执行身份验证和授权。 它使用可配置的SQL查询从数据库中读取用户名,密码,权限和角色。 不幸的是,该领域有两个缺点:

  • 它无法从JNDI加载数据源( 未解决的问题 )。
  • 它无法添加密码( 未解决的问题 )。

我们对其进行扩展,并创建新的类JNDIAndSaltAwareJdbcRealm 。 由于所有属性都可以在ini文件中进行配置,因此新属性jndiDataSourceName也将自动进行配置。 只要设置了新属性,该领域就会在JNDI中查找数据源:

protected String jndiDataSourceName;public String getJndiDataSourceName() {return jndiDataSourceName;
}public void setJndiDataSourceName(String jndiDataSourceName) {this.jndiDataSourceName = jndiDataSourceName;this.dataSource = getDataSourceFromJNDI(jndiDataSourceName);
}private DataSource getDataSourceFromJNDI(String jndiDataSourceName) {try {InitialContext ic = new InitialContext();return (DataSource) ic.lookup(jndiDataSourceName);} catch (NamingException e) {log.error('JNDI error while retrieving ' + jndiDataSourceName, e);throw new AuthorizationException(e);}
}

方法doGetAuthenticationInfo从数据库读取帐户身份验证信息,并将其转换为身份验证信息对象。 如果找不到帐户信息,则返回null。 父类AuthenticatingRealm将身份验证信息对象与原始用户提供的数据进行比较。
我们重写doGetAuthenticationInfo以从数据库中读取密码哈希和盐,并将它们存储在身份验证信息对象中:

doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {...// read password hash and salt from db PasswdSalt passwdSalt = getPasswordForUser(username);...// return salted credentialsSimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, passwdSalt.password, getName());info.setCredentialsSalt(new SimpleByteSource(passwdSalt.salt));return info;
}

这里的示例仅包含最重要的代码段。 完整的课程在Github上可用。

配置新领域

在Shiro.ini文件中配置领域和jndi名称:

[main] 
# realm to be used
saltedJdbcRealm=org.meri.simpleshirosecuredapplication.realm.JNDIAndSaltAwareJdbcRealm
# any object property is automatically configurable in Shiro.ini file
saltedJdbcRealm.jndiDataSourceName=jdbc/SimpleShiroSecuredApplicationDB 
# the realm should handle also authorization
saltedJdbcRealm.permissionsLookupEnabled=true

配置SQL查询:

# If not filled, subclasses of JdbcRealm assume 'select password from users where username = ?'
# first result column is password, second result column is salt 
saltedJdbcRealm.authenticationQuery = select password, salt from sec_users where name = ?
# If not filled, subclasses of JdbcRealm assume 'select role_name from user_roles where username = ?'
saltedJdbcRealm.userRolesQuery = select role_name from sec_users_roles where user_name = ?
# If not filled, subclasses of JdbcRealm assume 'select permission from roles_permissions where role_name = ?'
saltedJdbcRealm.permissionsQuery = select permission from sec_roles_permissions where role_name = ?

JdbcRealm使用credetials匹配器的方式与IniRealm完全相同:

# password hashing specification
sha256Matcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher
sha256Matcher.hashAlgorithmName=SHA-256
saltedJdbcRealm.credentialsMatcher = $sha256Matcher

注意:我们从配置文件中删除了[用户]和[角色]部分。 否则,Shiro将同时使用IniRealm和JdbcRealm。 这将创建超出本章范围的多领域方案。

从用户的角度来看,应用程序的工作方式与以前完全相同。 他可以登录到与以前相同的用户帐户。 但是,用户名,密码,盐,权限和角色现在存储在数据库中。

完整的源代码可在Github上的'authentication_stored_in_database'分支中找到。

备用登录–证书

某些系统允许用户登录使用多种身份验证方式。例如,用户可以提供用户名/密码,使用Google帐户,Facebook帐户或其他任何方式登录。 我们将添加与简单应用程序类似的内容。 我们将为用户提供使用PGP证书进行身份验证的选项。
新要求:应用程序支持PGP证书作为替代身份验证机制。 仅当用户不具有与应用程序帐户关联的有效证书时,才会显示登录屏幕。 如果用户具有有效的已知PGP证书,则会自动登录。 用户尝试登录应用程序时,必须提供身份验证数据。 这些数据由servlet过滤器捕获。 筛选器将数据转换为身份验证令牌,并将令牌传递给领域。 如果有任何领域希望对用户进行身份验证,它将身份验证令牌转换为身份验证信息对象。 如果该领域不希望这样做,则返回null。 开箱即用Shiro框架过滤器会忽略请求中的PGP证书。 可用的身份验证令牌无法保存它们,并且领域完全不知道PGP证书。 因此,我们必须创建:

  • 身份验证令牌来移动证书,
  • Servlet过滤器能够读取证书,
  • 验证证书并将其与用户帐户匹配的领域。

我们的应用程序将有两个不同的领域。 一种使用名称标识帐户和密码来验证用户身份,另一种使用PGP证书两者都进行。
在开始编码之前,我们必须处理应用程序周围的PGP证书和基础结构。 如果您对设置的PGP证书不感兴趣,

基础设施

当用户访问Web应用程序时,他的Web浏览器可能会将PGP证书的副本发送到Web服务器。 证书由某个证书颁发机构或证书本身(自签名证书)签名。 Web服务器将其信任的证书列表保存在称为truststore的存储中。 如果信任库包含用户证书或对其进行签名的授权证书,则Web服务器将信任用户证书。 受信任的证书将传递到应用程序。
我们会:

  • 为每个用户创建证书,
  • 创建信任库,
  • 配置Web服务器,
  • 将证书与用户帐户关联。

在portecle中创建和管理证书。 SimpleShiroSecuredApplication的示例证书位于src \ test \ resources \ clients目录中。 所有商店和证书都具有通用密码“秘密”。

创建证书

为portecle中的每个用户创建自签名证书:

  • 创建新的jks密钥库:在File-> New Keystore中,选择jks。
  • 生成新证书:工具->生成密钥对。 将密码字段保留为空,证书将继承密钥库的密码。
  • 导出公共证书:选择新证书->右键单击->导出,选择“头证书”。 这将创建.cer文件。
  • 导出私钥和证书:选择新证书->右键单击->导出,选择私钥和证书。 这将创建.p12文件。

.cer文件仅包含公共证书,因此您可以将其提供给任何人。 另一方面,.p12文件包含用户私钥,因此必须保密。 仅将其分发给用户(例如,将其导入浏览器进行测试)。

创建信任库

创建新的信任库并将公共证书.cer文件导入到其中:

  • 在文件->新密钥库中,选择jks。
  • 工具->导入可信证书。

配置Web服务器

Web服务器必须请求证书,并根据信任库验证它们。 无法从Java请求证书。 每个Web服务器的配置都不同。 Github上的Look at AbstractContainerTest类中提供了Jetty配置。

将证书与帐户关联

每个证书由序列号和签署证书的证书颁发机构的名称唯一标识。 我们将它们与用户名和密码一起存储在数据库表中。 数据库更改位于db.changelog.xml文件中,有关新列,请参见changeset 3 ,有关数据初始化,请参见changeset 4 。

认证令牌

身份验证令牌表示身份验证尝试期间的用户数据和凭据。 它必须实现身份验证令牌接口,并保存我们希望在servlet过滤器和领域之间传递的所有数据。
由于我们希望同时使用用户名/密码和证书进行身份验证,因此我们扩展了UsernamePasswordToken类,并向其添加了证书属性。 新的身份验证令牌X509CertificateUsernamePasswordToken实现了新的接口X509CertificateAuthenticationToken ,两者在Github上都可用:

public class X509CertificateUsernamePasswordToken extends UsernamePasswordToken implements X509CertificateAuthenticationToken {private X509Certificate certificate;@Overridepublic X509Certificate getCertificate() {return certificate;}public void setCertificate(X509Certificate certificate) {this.certificate = certificate;}}

Servlet过滤器

Shiro过滤器将用户数据转换为身份验证令牌。 到目前为止,我们使用了FormAuthenticationFilter 。 如果传入的请求来自登录的用户,则过滤器允许用户进入。如果用户正尝试对其进行身份验证,则过滤器将创建身份验证令牌并将其传递给框架。 否则,它将用户重定向到登录屏幕。
我们的过滤器CertificateOrFormAuthenticationFilter扩展了FormAuthenticationFilter 。

首先,我们必须说服它,不仅具有用户名和密码的请求,而且具有PGP证书的任何请求都可以视为尝试登录。 其次,我们必须修改过滤器以在身份验证令牌中发送PGP证书以及用户名和密码。
方法isLoginSubmission确定请求是否表示身份验证尝试:

@Overrideprotected boolean isLoginSubmission(ServletRequest request, ServletResponse response) {return super.isLoginSubmission(request, response) || isCertificateLogInAttempt(request, response);}private boolean isCertificateLogInAttempt(ServletRequest request, ServletResponse response) {return hasCertificate(request) && !getSubject(request, response).isAuthenticated();}private boolean hasCertificate(ServletRequest request) {return null != getCertificate(request);}private X509Certificate getCertificate(ServletRequest request) {X509Certificate[] attribute = (X509Certificate[]) request.getAttribute('javax.servlet.request.X509Certificate');return attribute==null? null : attribute[0];}

方法createToken创建身份验证令牌:

@Overrideprotected AuthenticationToken createToken(String username, String password, ServletRequest request, ServletResponse response) {boolean rememberMe = isRememberMe(request);String host = getHost(request);X509Certificate certificate = getCertificate(request);return createToken(username, password, rememberMe, host, certificate);}protected AuthenticationToken createToken(String username, String password, boolean rememberMe, String host, X509Certificate certificate) {return new X509CertificateUsernamePasswordToken(username, password, rememberMe, host, certificate);}

在配置文件中用CertificateOrFormAuthenticationFilter过滤器替换FormAuthenticationFilter:

[main]
# filter configuration
certificateFilter = org.meri.simpleshirosecuredapplication.servlet.CertificateOrFormAuthenticationFilter
# specify login page
certificateFilter.loginUrl = /simpleshirosecuredapplication/account/login.jsp
# name of request parameter with username; if not present filter assumes 'username'
certificateFilter.usernameParam = user
# name of request parameter with password; if not present filter assumes 'password'
certificateFilter.passwordParam = pass
# does the user wish to be remembered?; if not present filter assumes 'rememberMe'
certificateFilter.rememberMeParam = remember
# redirect after successful login
certificateFilter.successUrl  = /simpleshirosecuredapplication/account/personalaccountpage.jsp

将所有URL重定向到新的过滤器:

[urls]
# force ssl for login page 
/simpleshirosecuredapplication/account/login.jsp=ssl[8443], certificateFilter# only users with some roles are allowed to use role-specific pages 
/simpleshirosecuredapplication/repairmen/**=certificateFilter, roles[repairman]
/simpleshirosecuredapplication/sales/**=certificateFilter, roles[sales]
/simpleshirosecuredapplication/scientists/**=certificateFilter, roles[scientist]
/simpleshirosecuredapplication/adminarea/**=certificateFilter, roles[Administrator]# enable certificateFilter filter for all application pages
/simpleshirosecuredapplication/**=certificateFilter

自定义领域

我们的新领域将仅负责身份验证。 授权(访问权限)将由JNDIAndSaltAwareJdbcRealm处理。 只要PGP证书将用户身份验证为与用户名/密码相同的帐户,这种配置就起作用。 否则,新领域返回的主要主体必须与JNDIAndSaltAwareJdbcRealm返回的主要主体相同。
我们的领域不需要缓存,也不需要可选接口提供的任何其他服务。 因此,我们只需要实现两个接口:Realm和Nameable。 X509CertificateRealm仅支持带有PGP证书的身份验证令牌:

@Overridepublic boolean supports(AuthenticationToken token) {if (token!=null)return  token instanceof X509CertificateAuthenticationToken;return false;}

方法getAuthentcationInfo负责身份验证。 如果提供的证书有效并且与用户帐户关联,则领域将创建认证信息对象。 请记住,主要主体必须与JNDIAndSaltAwareJdbcRealm返回的主体相同:

@Override
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {// the cast is legal, since Shiro will let in only X509CertificateAuthenticationToken tokensX509CertificateAuthenticationToken certificateToken = (X509CertificateAuthenticationToken) token;X509Certificate certificate = certificateToken.getCertificate();// verify certificateif (!certificateOK(certificate)) {return null;}// the issuer name and serial number uniquely identifies certificateBigInteger serialNumber = certificate.getSerialNumber();String issuerName = certificate.getIssuerDN().getName();// find account associated with certificateString username = findUsernameToCertificate(issuerName, serialNumber);if (username == null) {// return null as no account was foundreturn null;}// sucesfull verification, return authentication inforeturn new SimpleAuthenticationInfo(username, certificate, getName());
}

请注意,领域具有两个新属性:trustStore和trustStorePassword。 两者都是PGP证书验证所必需的。 与其他任何属性一样,两者都可以在配置文件中进行配置。
将新的领域添加到Shiro.ini文件中:

[main]
certificateRealm = org.meri.simpleshirosecuredapplication.realm.X509CertificateRealm
certificateRealm.trustStore=src/main/resources/truststore
certificateRealm.trustStorePassword=secret

现在可以使用PGP证书登录到应用程序。 如果证书不可用,则用户名和密码也可以使用。

应用程序源代码在Github上的'certificates_as_alternative_log_in_method'分支中可用。

多个领域

如果配置文件包含多个领域,则将全部使用。 在这种情况下,Shiro尝试使用所有已配置的领域对用户进行身份验证,并将身份验证结果合并在一起。 负责合并的对象称为身份验证策略。 框架提供了三种身份验证策略:

  • 所有成功的策略
  • 至少一项成功的策略 ,
  • 第一个成功的策略 。

默认情况下,使用“至少一个成功的策略”,这非常适合我们的目的。 同样,可以创建自定义身份验证策略。 例如,我们可能要求用户同时提供PGP证书和用户名/密码凭据才能登录。
新要求:用户必须同时提供PGP证书和用户名/密码凭据才能登录。

换句话说,我们需要的策略是:

  • 如果某些领域不支持令牌,则失败,
  • 如果某些领域无法验证用户身份,则失败,
  • 如果两个领域认证不同的主体,则失败。

认证策略是一个实现认证策略接口的对象。 在身份验证尝试之后和之前调用接口方法。 我们从“所有成功策略”(可用的最接近策略)创建“ 主要主体相同的身份验证策略 ”。 在每次领域身份验证尝试之后,我们将比较主体:

@Override
public AuthenticationInfo afterAttempt(...) {validatePrimaryPrincipals(info, aggregate, realm);return super.afterAttempt(realm, token, info, aggregate, t);
}private void validatePrimaryPrincipals(...) {...Object aggregPrincipal = aggregPrincipals.getPrimaryPrincipal();Object infoPrincipal = infoPrincipals.getPrimaryPrincipal();if (!aggregPrincipal.equals(infoPrincipal)) {String message = 'All realms are required to return the same primary principal. Offending realm: ' + realm.getName();log.debug(message);throw new AuthenticationException(message);}
}

身份验证策略在Shiro.ini文件中配置:

# multi-realms strategy
authenticationStrategy=org.meri.simpleshirosecuredapplication.authc.
PrimaryPrincipalSameAuthenticationStrategy
securityManager.authenticator.authenticationStrategy = $authenticationStrategy

最后,我们必须改回CertificateOrFormAuthenticationFilter的isLoginSubmission方法。 现在仅将具有用户名和密码的请求视为登录尝试。 证书不足:

@Override
protected boolean isLoginSubmission(ServletRequest request, ServletResponse response) {return super.isLoginSubmission(request, response);
}

如果立即运行该应用程序,则必须同时使用证书和用户名/密码登录方法。

这个版本可以在Github的'certificates_as_mandatory_log_in_method'分支中找到。

结束

此部分专用于Shiro领域。 我们创建了三个不同的应用程序版本,所有版本都可以在Github上获得。 它们涵盖了基本且可能是最重要的领域功能。

如果您需要了解更多信息,请从此处链接的类开始并阅读其javadocs。 他们写得很好,内容广泛。

参考: Apache Shiro第2部分–我们的JCG合作伙伴 Maria Jurcovicova在This is Stuff博客上获得的领域,数据库和PGP证书 。


翻译自: https://www.javacodegeeks.com/2012/05/apache-shiro-part-2-realms-database-and.html

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

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

相关文章

js中变量作用域的小理解

一&#xff1a;变量作用域 在js代码中每个变量都是有自己的作用域的&#xff0c;js中不像C语言有块级作用域的概念&#xff0c;取而代之的是函数作用域&#xff0c;看如下代码&#xff1a; var scope"global"; function init(){ alert(scope);var scope "local…

安卓linux开机画面,Android系统的开机画面显示过程分析(1)

好几个月都没有更新过博客了&#xff0c;从今天开始&#xff0c;老罗将尝试对Android系统的UI实现作一个系统的分析&#xff0c;也算是落实之前所作出的承诺。提到Android系统的UI&#xff0c;我们最先接触到的便是系统在启动过程中所出现的画面了。Android系统在启动的过程中&…

如果你的NavigationDrawer里面的Item没有响应,Drawer不能左滑关闭

如果你的NavigationDrawer里面的Item没有响应&#xff0c;Drawer不能左滑关闭&#xff0c;应该是因为你没有把主要内容放在DrawerLayout标签下的第一位。 The main content view (the FrameLayout above) must be the first child in the DrawerLayout because the XML order i…

JAXB和未映射的属性

JAXB&#xff08;JSR-222&#xff09;是例外配置&#xff0c;这意味着存在默认映射应用于域对象。 这意味着有时您需要显式排除字段/属性。 在本文中&#xff0c;我将讨论如何使用XmlTransient或XmlAccessorType&#xff08;XmlAccessType.NONE&#xff09;以及何时使用每个选项…

sublime text3 使用SVN插件

Simon在项目中经常使用SVN&#xff0c;每次都要切换提交&#xff0c;很麻烦&#xff0c;有了这个SVN插件就很方便了&#xff0c;使用快捷方式提交&#xff0c;更新。 安装: Ctrl Shift P 调用出Sublime Text的包管理工具&#xff0c;输入TortoiseSVN&#xff0c;回车进行安装…

c语言空格有什么作用,空格在c语言中怎么表示 C语言中的空格字符怎么表示

c语言中表示空格的是什么代码&#xff1f;分析如下&#xff1a; 不是所有字符都需要转义的&#xff0c;空格直接就敲空格&#xff0c;或者使用ASCII码值赋值为32。 空格没有转义字符。合法转义字符如下&#xff1a;\a 响铃(BEL) 、\b 退格(BS)、\f 换页(FF)、\n 换行(LF)、\r 回…

二维数组实现八皇后问题

之前关八皇后的问题全部使用的是一维数组进行实现(http://www.cnblogs.com/SeaSky0606/p/4604955.html)&#xff0c;现改一种数据存储方式&#xff0c;按照8x8的二维棋盘存储皇后。基本逻辑不变&#xff0c;可参见如下代码&#xff1a; #include<cstdio> #include<alg…

Java的深度:通过协方差暴露的API泄漏

Java有时可能非常棘手&#xff0c;特别是在API设计中。 让我们看一个非常有趣的展示柜。 jOOQ强烈地将API与实现分开。 所有API都在org.jooq包中&#xff0c;并且是公共的。 大多数实现都在org.jooq.impl包和package-private中。 只有工厂和一些专用的基础实现是公开的。 这允许…

StringMVC 中如何做数据校验

步骤一&#xff1a;引入四个jar包 步骤二&#xff1a;注册类型转换器 <context:component-scan base-package"cn.happy.controller"></context:component-scan><!-- 配置验证器 --><bean id"myvalidator" class"org.springframe…

ibm+x3650+m4+linux+raid驱动,IBM X3650M4阵列卡驱动下载

ibm X3650M4raid阵列卡驱动适合安装windowsserver2008,windowsserver2008R2,系统问题&#xff0c;服务器问题&#xff0c;可以联系我们也可以到5分享论坛发帖求助。IBM System x3650 M4服务器是一款应用最为广泛的2U机架服务器&#xff0c;支持Xeon E5-2600机架服务器的所有产品…

为什么在Java 6上Math.round(0.499999999999999917)舍入为1

总览 错误表示错误和算术舍入错误有两种类型&#xff0c;它们在浮点计算中很常见。 在此简单示例中&#xff0c;这两个错误组合在一起&#xff0c;在Java 6中Math.round&#xff08;0.4999999999999999999917&#xff09;舍入为1。 表示错误 浮点数是以2为底的格式&#xff0c…

单利模式

class Singleton{ public:static Singleton* GetInstance(){if (m_pInstance nullptr){m_pInstance new Singleton;}return m_pInstance;} private:Singleton(){}//需要将构造和析构定义成私有的防止外界构造和析构~Singleton(){}static Singleton* m_pInstance;//static所有…

C语言switch中break的作用,C语言中switch...case语句中break的重要性

在C语言中switch...case语句是经常用到的&#xff0c;下面我介绍一下在使用该语句时候需要注意的一个细节问题。话不多说&#xff0c;直接举例子&#xff1a;例子1&#xff1a;switch(fruit){case 1:printf("apple"); break;case 2:printf("banana"); brea…

BZOJ 1898: [Zjoi2005]Swamp 沼泽鳄鱼 [矩阵乘法]

1898: [Zjoi2005]Swamp 沼泽鳄鱼 Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 1082 Solved: 602[Submit][Status][Discuss]Description 潘塔纳尔沼泽地号称世界上最大的一块湿地&#xff0c;它地位于巴西中部马托格罗索州的南部地区。每当雨季来临&#xff0c;这里碧波荡漾…

从Spring开始,Java EE 6必须具备哪些附加功能?

我是一名高级Java开发人员&#xff0c;必须研究应用程序架构师选择的技术。 我最多只能表达对特定技术的看法&#xff0c;不能做出/影响技术选择的决定。 因此&#xff0c;在我的正式项目中&#xff0c;我别无选择从Spring迁移到JavaEE6或从JavaEE6迁移到Spring。 我坚信&#…

UML类图与类的关系详解

在画类图的时候&#xff0c;理清类和类之间的关系是重点。类的关系有泛化(Generalization)、实现&#xff08;Realization&#xff09;、依赖(Dependency)和关联(Association)。其中关联又分为一般关联关系和聚合关系(Aggregation)&#xff0c;合成关系(Composition)。下面我们…

教程:Hibernate,JPA和Spring MVC –第2部分

本教程将向您展示如何使用基本的Hibernate / JPA应用程序&#xff0c;如何将其转换为Spring MVC Web项目&#xff0c;以便能够在Web浏览器中查看数据库&#xff0c;以及最后使用Spring的Transactional注释来减少样板代码。 本教程假定您熟悉Java和Maven&#xff0c;并且已经完成…

算法转换c语言程序,(转)C语言实现卡尔曼滤波算法程序

非常感谢原作者&#xff0c;我在这个的基础上转换成纯整形运算。STM32F103 12位ADC先放大1000倍再运算&#xff0c;理论上可以保留小数点后三位的结果。效果非常不错&#xff0c;运算速度也快&#xff0c;72M时钟 1-2uS左右(根据MDK周期数)。]uint32_t KalmanFilter(int32_t Re…

Java 8的烹调方式–拼图项目

什么是Project Jigsaw&#xff1a;Project Jigsaw是使Java编译器模块知道的项目。 多年以来&#xff0c;Java API一直是整体的&#xff0c;即从代码的任何部分都可以平等地看到整个API。 还没有任何方法可以声明代码对任何其他用户库的依赖关系。 拼图项目试图以非常有效的方式…

python之路-SQLAlchemy

SQLAchemy SQLAlchemy是Python编程语言下的一款ORM框架&#xff0c;该框架建立在数据库API之上&#xff0c;使用关系对象映射进行数据库操作&#xff0c;简言之便是&#xff1a;将对象转换成SQL&#xff0c;然后使用数据API执行SQL并获取执行结果。 安装&#xff1a; pip3 inst…