spring

“我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证。

在本文中,您将研究使用Spring Boot 2.1实现登录功能的各种选项。 您将从最简单的基本身份验证开始,除了内部后端工具外,您可能永远不想使用它,然后转到简单的基于表单的身份验证页面。 接下来,您将通过覆盖一些默认模板和控制器来自定义默认的,自动生成的表单。 最后,您将继续使用OAuth 2.0添加单一登录。 您将看到如何允许用户使用Github和Okta登录。

本教程的假设是非常基本的。 我假设您已经基本了解Spring和Spring Boot,但不一定对Spring Security或Spring的各种安全功能有深入的了解。 您将需要安装git ,并且gradle会很好,但是您可以对所有gradle命令使用Gradle包装器,因此这并不是绝对必要的。

继续克隆我为本教程创建的存储库:

git clone https://github.com/oktadeveloper/okta-spring-boot-login-options-example.git

该存储库包含五个子目录:

  • basic-auth
  • form-auth
  • custom-form-auth
  • oauth-start
  • oauth-okta-github
  • oauth-okta-starter

这些对应于本教程的四个部分(最后三个部分为OAuth部分,其中三个为相应部分)。 除了oauth-start ,这些都是功能齐全的应用程序。 在本教程中,您将遍历代码的含义以及如何构建它们。

在Spring Boot中构建HTTP基本登录

基本身份验证是基本的。 这是直接内置在HTTP协议中的简单方案。 它来自田园时代,大量的数据和金钱流过互联网的各种管道。 因此,它确实不是非常安全。 根据规范,密码和用户名使用HTTP authorization标头中的Base64进行编码。 因为Base64可能也是纯文本,所以如果您要对任何内容使用基本身份验证,请确保始终使用HTTPS / SSL,因为每个请求都将发送身份验证凭据。

使用您最喜欢的IDE或编辑器打开basic-auth Spring Boot项目。

您要签出的第一件事是build.gradle文件。 复制如下。 我不会详细介绍所有这些内容,但是如果您还不熟悉,我想让您了解这里发生的一些事情。

plugins {  id 'org.springframework.boot' version '2.1.4.RELEASE'  id 'java'
}  apply plugin: 'io.spring.dependency-management'  group = 'com.okta.springsecurityauth'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'  repositories {  mavenCentral()
}  dependencies {  implementation 'org.springframework.boot:spring-boot-starter-security'  implementation 'org.springframework.boot:spring-boot-starter-web'  testImplementation 'org.springframework.boot:spring-boot-starter-test'  testImplementation 'org.springframework.security:spring-security-test'
}

有两个Spring插件被添加到构建中: org.springframework.bootio.spring.dependency-management 。 请注意,版本设置为2.1.4.RELEASE 。 过去,Spring的真正麻烦之一就是依赖管理。 Spring是一个庞大的库集合,当您引入一个库时,您需要引入其他兼容版本的库。 当您更新一个时,通常这会导致其他人失去兼容性。 过去解决此问题可能会使您陷入所谓的“依赖地狱”。

幸运的是,Spring使事情变得更加轻松。 查看我们的两个Spring依赖项:

dependencies {  implementation 'org.springframework.boot:spring-boot-starter-security'  implementation 'org.springframework.boot:spring-boot-starter-web'
...
}

请注意,它们没有版本号。 这些基本上是功能集(在这种情况下为安全性和Web)的大型元依赖关系,并且Spring插件根据此行中设置的版本为您管理所有子依赖关系:

id 'org.springframework.boot' version '2.1.4.RELEASE'

build.gradle文件的全部执行摘要将加载Spring Boot版本2.1.4,并将添加Web和安全功能模块。 Spring的所有可用启动程序的列表 可以在其docs中找到。

除了gradle.file ,实际上只有三个其他感兴趣的文件,所有Java文件。 在src/main/java/com/okta/springsecurityauth查看,您将看到它们:

  • Application.java (自动魔术化整个Spring Boot框架的主类)
  • SecurityConfiguration.java (配置安全性选项)
  • WebController.java (一个非常基本的HTTP请求控制器)

Application.javaApplication.java的入口点。 在这种情况下,而且在许多情况下,这非常简单。 最重要的是@SpringBootApplication批注,它告诉您的Spring依赖项引导整个Spring Boot框架。 当然,还有main()方法,这是Spring加载和运行Application类的地方。

package com.okta.springsecurityauth;  import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;  @SpringBootApplication
public class Application {  public static void main(String[] args) {  SpringApplication.run(Application.class, args);  }  }

WebController.java足够聪明,它是您的Web请求控制器。 它定义了请求端点并确定了响应。

package com.okta.springsecurityauth;  import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;  @Controller
public class WebController {  @RequestMapping("/")  @ResponseBody  public String index() {  return "That's pretty basic!";  }  }

在这种情况下,Web控制器将返回一个简单的字符串,而不是路由到模板文件,这将在稍后介绍。 @ResponseBody批注允许该方法直接返回字符串。

在本教程中,将执行SecurityConfiguration.java 。 这是Spring Boot配置为使用基本身份验证的地方。 您还可以在这里配置硬编码的默认用户名和密码(显然,这不是我在生产中要做的,但是非常适合教程)。

package com.okta.springsecurityauth;  import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;  @Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {  @Override  public void configure(HttpSecurity http) throws Exception {  http  .authorizeRequests()  .anyRequest().authenticated()  .and()  .httpBasic();  }  @Override  protected void configure(AuthenticationManagerBuilder auth) throws Exception {  auth.inMemoryAuthentication()  .withUser("user")  .password("{noop}pass") // Spring Security 5 requires specifying the password storage format  .roles("USER");  }  }

如果您查看configure()方法,您将看到http对象及其流畅接口用于告诉spring认证所有请求并使用HTTP基本认证。 这是相当琐碎的,但是稍后您会看到,此界面中有大量功能。

configure()方法主要是本教程的一个技巧,目的是在内存身份验证管理器中创建用户。 您正在使用用户名user和password pass创建一个用户。 用户已分配了USER角色。

而已!

打开一个终端,并确保您位于basic-auth项目的根目录中。 使用以下命令运行项目:

./gradlew bootRun

等待Spring Boot应用完成加载。

导航到http://localhost:8080

您会看到一个登录窗口。

使用user登录并作为凭据pass

系统将显示一个成功屏幕,显示“这很简单!”

配置基于Spring Boot Form的登录

使用基本身份验证时,登录表单实际上是由浏览器而不是由应用程序生成的。 它显示为无样式的弹出窗口。 这不是很好的流程,也不是非常专业的外观。

只需对代码进行一些很小的更改,您就可以让Spring Boot自动生成外观更专业的登录表单。 完整的代码可以在GitHub存储库的form-auth子目录中找到,或者如果仍然打开,则可以对basic-auth项目进行更改。

您想要将SecurityConfiguration.java文件的configure()方法更改为如下所示:

@Override
public void configure(HttpSecurity http) throws Exception {  http  .authorizeRequests()  .anyRequest().authenticated()  .and()  .formLogin()  .and()  .httpBasic();
}

唯一的不同是添加了.and().formLogin() 。 除此之外,这两个项目是相同的。

使用./gradlew bootRun再次运行它。

导航到http://localhost:8080 (您可能需要打开一个隐身窗口以触发重新认证)。

这次,您将看到Spring为您生成的登录页面。

自定义Spring Boot登录表单

我们日益复杂的Spring Boot身份验证过程的下一步是自定义登录表单。 春天使这个超级容易。 但是,有许多新文件,并且对代码的更改比上次更多,因此我建议打开在回购的custom-form-auth文件夹下找到的项目。

build.gradle文件中,有一个新的依赖项。 这引入了Thymeleaf模板引擎,这是与Spring Boot一起使用的默认Web模板引擎。

dependencies {  ...implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'  ...
}

现在, SecurityConfiguration.java文件如下所示(为简洁起见,省略了一些部分):

package com.okta.springsecurityauth;  ...@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {  @Override  public void configure(HttpSecurity http) throws Exception {  http  .authorizeRequests()  .antMatchers( "/public/**").permitAll()  .anyRequest().authenticated()  .and()  .formLogin()  .loginPage("/login.html")  .failureUrl("/login-error.html")  .permitAll();  }...}

请注意, .httpBasic()已消失, .formLogin()具有一些新选项。 在这里,您将设置一个登录页面和一个登录失败页面,并确保它们是公开可用的。

还要注意这一行:

.antMatchers( "/public/**").permitAll()

在这里,无需进行身份验证即可使类路径根目录上的public目录可用。 在src/main/resources/static有一个public目录。 启动该应用程序时, src/main/resources/static目录下的所有内容都将复制到类路径,因此public目录最终位于类路径根目录下,并且其中的所有文件无需身份验证即可使用。 这非常适合图像,JavaScript文件和CSS文件之类的东西。

WebController.java也有一些新的端点:

package com.okta.springsecurityauth;  ...@Controller
public class WebController {  @RequestMapping("/")  @ResponseBody  public String index() {  return "You made it!";  }  // Login form  @RequestMapping("/login.html")  public String login() {  return "login.html";  }  // Login form with error  @RequestMapping("/login-error.html")  public String loginError(Model model) {  model.addAttribute("loginError", true);  return "login.html";  }  }

请注意,虽然index()方法具有@RequestBody批注,这意味着它将直接以字符串形式返回其请求主体,但login()loginError()方法却没有。 相反,他们返回的是Spring Boot将呈现的Thymeleaf模板的名称。 默认情况下,这些模板位于src/main/resources/templates 。 此外,请注意, loginError()有点路由技巧。 实际上,它只是返回login.html模板,但是在模型中注入了error属性。

src/main/resources/templates/login.html是一个新文件。 这是登录模板文件。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>  <title>Login page</title>  <link th:href="@{/public/style.css}" rel="stylesheet" />
</head>
<body>
<div id="container">  <h2>Login page</h2>  <form th:action="@{/login.html}" method="post">  <label for="username">Username</label>  <input type="text" id="username" name="username" autofocus="autofocus" />  <label for="password">Password</label>  <input type="password" id="password" name="password" />  <input id="submit" type="submit" value="Log in" />  </form>  <p th:if="${loginError}" class="error">Wrong user or password</p>
</div>
</body>
</html>

关于Thymeleaf的真实,深入的解释远远超出了本教程的范围。 如果愿意,请访问他们的网站进行深入研究。

还记得您将public目录公开吗? 这行有一个非常基本的样式表。 通常,我将其包括在内是为了演示一种在模板文件中包含静态资源的方法。

<link th:href="@{/public/style.css}" rel="stylesheet" />

根据模型的loginError属性有条件地呈现错误行,该属性由控制器的loginError()方法注入。

<p th:if="${loginError}" class="error">Wrong user or password</p>

除此之外,这是一个非常简单的登录表单!

再次使用./gradlew bootRun运行它。

您将看到样式化的自定义登录表单:

使用user:pass再次登录,您将看到我们的成功屏幕,“您成功了!”

使用GitHub和Single Sign-On的Spring Boot OAuth 2.0登录

到目前为止,您已经使用临时内存AuthenticationManager在本地完成了所有AuthenticationManager 。 在生产中更常见的是,应用程序支持OAuth 2.0和OIDC(开放ID连接)。 OAuth 2.0是开放的授权标准。 OIDC建立在OAuth 2.0之上,并添加了身份验证,以实现更完整的身份管理协议。

在本教程中,您首先将看到如何使用GitHub添加单点登录(SSO)。 之后,您将看到如何使用软件即服务身份解决方案提供商Okta。

两者都非常简单,并且允许用户重用现有的身份提供者确实有好处。 强迫用户处理和管理数百个网站的单独的强密码是一个愚蠢的事情,最终会导致弱密码和在多个站点之间重复使用的密码(乘以其易受攻击性); 更不用说旧的“只是在便签上随意写,然后将其粘贴到显示器上”密码管理解决方案。

通过GitHub Single Sign-on简化您的Spring Boot App登录

现在是时候实现GitHub OAuth 2.0客户端了。

首先,您需要在GitHub上注册一个新的OAuth应用程序。 转到他们的网站并立即执行此操作。 填写如下所示的值。

请特别注意Authorization回调URL ,因为该URL必须为http://localhost:8080/login/oauth2/code/github

单击注册应用程序。 请注意客户端ID客户端密钥,因为您很快就会需要它们。

现在,回到Java。 在您的IDE中打开oauth-start项目。

build.gradle文件中,注意几个新的依赖项:

...
dependencies {  implementation 'org.springframework.boot:spring-boot-starter-security'  implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'  implementation 'org.springframework.boot:spring-boot-starter-web'  implementation 'org.springframework.security:spring-security-oauth2-client'  implementation 'org.springframework.security:spring-security-oauth2-jose'  ...
}

安全性,Thymeleaf和网络启动程序仍然存在。 但是,有两个新的Spring启动器: oauth2-clientoauth2-jose

oauth2-client了实现OAuth 2.0客户端所需的库。 oauth2-jose了一些用于签名和加密的通用库。 JOSE代表Java对象签名和加密。

OConfiguration登录的SecurityConfiguration.java文件已更新:

package com.okta.spring.SpringBootOAuth;
...@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {  @Override  public void configure(HttpSecurity http) throws Exception {  http.antMatcher("/**")  .authorizeRequests()  .antMatchers("/", "/login**").permitAll()  .anyRequest().authenticated()  .and()  .oauth2Login();  }
}

需要注意的两件事:1)对/login所有请求都是公共的,以及2) oauth2Login()方法是导致Spring Boot配置OAuth 2.0客户端的原因。

如果这似乎太容易了,那是正确的。 一些配置已移至application.yml文件。

打开src/main/resources/application.yml文件:

spring:  thymeleaf:  cache: false  security:  oauth2:  client:  registration:  github:  client-id: << your GitHub client ID>>  client-secret: << your GitHub client secret >>

您需要从上方将Client IDClient Secret填写到该文件中。

Web路由和模板也已更改。 查看WebController.java文件:

package com.okta.spring.SpringBootOAuth;  ...@Controller
public class WebController {  @RequestMapping("/securedPage")  public String securedPage(Model model,  @RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient,  @AuthenticationPrincipal OAuth2User oauth2User) {  model.addAttribute("userName", oauth2User.getName());  model.addAttribute("clientName", authorizedClient.getClientRegistration().getClientName());  model.addAttribute("userAttributes", oauth2User.getAttributes());  return "securedPage";  }  @RequestMapping("/")  public String index() {  return "index";  }  }

该控制器定义了两个端点,这些端点返回两个模板文件:

  • / –> src/main/resources/templates/index.html
  • /securedPage –> src/main/resources/templates/securedPage.html

securedPage()方法中,请注意如何使用依赖性注入来获取有关已认证用户的信息,以便可以将其注入模型中,然后将其传递到模板文件中。

src/main/resources/templates/index.html非常简单:

<!DOCTYPE html>
<html lang="en">
<head>  <meta charset="UTF-8">  <title>Home</title>
</head>
<body>  <h1>Spring Security SSO</h1>  <a href="securedPage">Login</a>
</body>
</html>

以及src/main/resources/templates/securedPage.html模板文件:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>  <meta charset="UTF-8">  <title>Secured Page</title>
</head>
<body>  <h1>Secured Page</h1>  <div>User Name: <span th:text="${userName}"></span></div>  <div>Client Name: <span th:text="${clientName}"></span></div>  <div>User Attributes: <span th:text="${userAttributes}"></span></div>
</body>
</html>

现在您可以尝试了。 再次,确保您在项目根目录中,然后运行./gradlew bootRun

导航到http://localhost:8080 ,然后单击“登录”链接。

如果您的浏览器绕过登录页面并直接进入/securedPage端点,请打开一个隐身浏览器,然后重试。

首先,您需要授权Spring Boot Login应用程序使用您的Github帐户进行OAuth登录。

接下来,您将看到GitHub OAuth登录屏幕:

如果成功,您将看到一个简单的文本页面,该页面以粗体显示“安全页面”,并显示有关您的OAuth用户的信息。

很漂亮吧?

现在,您将添加第二个OAuth提供程序:Okta。

将使用Okta登录的OAuth 2.0登录到您的Spring Boot应用

首先,如果您还没有开发人员帐户,请访问developer.okta.com并注册一个免费的开发人员帐户。

拥有帐户后,要创建OAuth / OIDC应用,请打开Okta开发人员信息中心。 单击“应用程序”顶部菜单项,然后单击“添加应用程序”

选择Web作为平台,然后单击Next

为应用命名。 我将其命名为“ Spring Boot Login”,但您可以随意命名。

登录重定向URI更改为http://localhost:8080/login/oauth2/code/okta

其他默认值也可以。

单击完成

在下一页上记下客户端ID客户端密钥

更新Okta的application.yml文件:

spring:  ...security:  oauth2:  client:  registration:  github:  client-id: << Your GitHub Client ID >>  client-secret: << Your GitHub Client Secret >>  okta:  client-id: << Your Okta Client ID >>  client-secret: << Your Okta Client Secret >> client-name: Oktaprovider:  okta:  issuer-uri: https://{yourOktaDomain}/oauth2/default

您需要添加Okta注册和提供程序条目,并确保填写您的Client IDClient Secret和Okta发行者。

重新启动应用程序,转到http://localhost:8080 ,然后单击“登录”链接。

这次,您将可以选择OAuth提供商。 该屏幕由Spring Boot自动生成。

单击Okta ,您将被带到Okta登录页面(通常有关注销和/或隐姓埋名的警告)。

成功登录,您将看到我们的成功页面。

至此,您已经将oauth-start项目与oauth-github-okta项目文件夹进行了更新,并且已经看到了将多个OAuth提供程序添加到Spring Boot应用程序是多么容易。

使用Okta的Spring Boot Starter简化OAuth 2.0登录

在本教程中,您要做的最后一件事是将Okta配置为允许新用户注册。 但是,在此之前,我想向您介绍Okta Spring Boot Starter。 该项目简化了Spring Boot和Okta的集成。 该项目的自述文件是获取更深入信息的好资源。

您将把Okta Spring Boot Starter集成到该项目中,并稍微简化配置。 为了简单起见,您还将删除GitHub OAuth集成。

build.gradle文件添加一个依赖build.gradle

dependencies {...implementation 'com.okta.spring:okta-spring-boot-starter:1.1.0'...
}

现在更新application.yml文件:

okta:  oauth2:  issuer: https://{yourOktaDomain}/oauth2/default  client-id: <<yourOktaClientID>>client-secret: <<yourOktaClientSecret>>
spring:  thymeleaf:  cache: false

填写您的客户密码客户ID

试试看。 运行./gradlew bootRun

您应该能够通过Okta登录。 这次您将无法选择OAuth 2.0提供程序。

启用用户注册

登录到您的developer.okta.com帐户。

将鼠标悬停在“用户”上,然后单击“注册”

启用注册。 另外,启用登录页面中的“显示“注册””链接。 这将导致注册链接显示在托管登录页面中。

使用隐身窗口再次登录。 这次您会注意到该用户可以选择注册为新用户。

输入您的电子邮件地址,密码和名称; 设置一些安全问题,您就完成了! 您使用Okta为您的应用程序注册了一个新用户。

Okta的自助注册选项可以做更多的事情。 所有这些都可以配置和定制。 看看他们的文档以进行更深入的研究。

oauth-okta-starter目录中可以找到使用Okta Spring Boot Starter并删除GitHub OAuth的最终产品。

了解有关Spring Boot登录选项和安全身份验证的更多信息

在本教程中,您涵盖了很多领域。 您使用基本身份验证,基于表单的身份验证和定制的基于表单的身份验证实现了Spring Boot应用程序。 然后,您使用OAuth 2.0和OIDC使用GitHub和Okta实施SSO。 最后,您了解了如何使用Okta Spring Boot Starter简化Spring Boot中的OAuth / OIDC SSO配置,以及如何允许用户通过Okta自助注册。

您可以在GitHub上的本教程中找到示例的所有代码。

要深入研究创建自定义登录表单,请查看有关该主题的Spring文档。

Spring Security的oauth2login示例还提供了一些很好的信息和更多示例。

这是一些相关的博客文章,它们演示了如何使用Spring Boot和Spring Security进行登录和身份验证:

  • Java应用程序的简单令牌认证
  • 在15分钟内使用Spring Boot和Spring Security构建一个Web应用程序
  • 创建一个安全的Spring REST API
  • 使用Spring Boot和Vue.js构建一个简单的CRUD应用

如果您喜欢这篇文章,请在社交媒体{ Twitter , Facebook , LinkedIn , YouTube }上关注我们,以了解我们何时发布类似的文章。

“ Spring Boot登录选项快速指南”最初于2019年5月15日发布在Okta开发人员博客上。

“我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证。

翻译自: https://www.javacodegeeks.com/2019/06/quick-guide-spring-boot-options.html

spring

spring_Spring Boot登录选项快速指南相关推荐

  1. Spring Boot登录选项快速指南

    "我喜欢编写身份验证和授权代码." 〜从来没有Java开发人员. 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证. 在本文中 ...

  2. 《树莓派Python编程指南》—— 1.3 树莓派快速指南

    本节书摘来自华章计算机<树莓派Python编程指南>一书中的第1章,第1.3节,作者:(美) Alex Bradbury Ben Everard更多章节内容可以访问云栖社区"华章 ...

  3. oauth2_带有Spring Security的OAuth 2.0快速指南

    oauth2 "我喜欢编写身份验证和授权代码." 〜从来没有Java开发人员. 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验 ...

  4. 带有Spring Security的OAuth 2.0快速指南

    "我喜欢编写身份验证和授权代码." 〜从来没有Java开发人员. 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证. 在构建W ...

  5. linux拨号教程,Linux拨号服务快速指南

    一.什么是PPP协议 既然是拨号上网,就不能不提到PPP(point-to-point protocol)协议.PPP协 议具有以下特点: 是应用最广泛的广域网协议: 通过提供连接控制协议(LCP)和 ...

  6. react 快速上手开发_React中测试驱动开发的快速指南

    react 快速上手开发 by Michał Baranowski 通过MichałBaranowski React中测试驱动开发的快速指南 (A quick guide to test-driven ...

  7. (译)快速指南:用UIViewPropertyAnimator做动画

    翻译自:QUICK GUIDE: ANIMATIONS WITH UIVIEWPROPERTYANIMATOR 译者:Haley_Wong iOS 10 带来了一大票有意思的新特性,像 UIViewP ...

  8. 高通机器视觉快速指南二

    高通机器视觉快速指南 二 3 校准 3.1 一个良好的位置 3.2 追踪相机 3.3 立体相机校准 3.4 高分辨率相机校准 3 校准 尽管设计用于演示可集成到系统中的 MV,但这些应用程序也可用于校 ...

  9. Python深度学习-快速指南

    Python深度学习-快速指南 (Python Deep Learning - Quick Guide) Python深度学习-简介 (Python Deep Learning - Introduct ...

最新文章

  1. 2017.4.17------软件测试的艺术+整理以前的摘记
  2. Android笔记 消息机制handler+http之 网络图片浏览器demo
  3. php 计算日期差几周,PHP计算两个时间之差的函数(年,月,周,日,小时,分钟,秒数)
  4. stc和sac_短期成本曲线
  5. C#中IL反汇编工具的使用 其具体含义如下文
  6. 娇小可人女友9号 4K无反相机松下GF9评测
  7. 【人脸识别】基于matlab GUI肤色人脸识别定位【含Matlab源码 674期】
  8. sir模型初始值_经典传染病的SIR模型(基于MATLAB)
  9. (生物信息学)R语言与统计学入门(十)—— 多因素Cox回归分析
  10. C/C++编程:std::move(将左值强制转换为右值)
  11. 使用requests爬虫制作自己的天气预报“Api”
  12. flasgger手写phpwind接口文档
  13. pymysql 向MySQL 插入数据无故报错
  14. 移动终端软件测试基础知识,移动终端软件测试基础知识 - Mr.南柯 - 51Testing软件测试网 51Testing软件测试网-软件测试人的精神家园...
  15. python画图时设置分辨率和画布大小-plt.figure()
  16. 详解ENet | CPU可以实时的道路分割网络
  17. 使用git控制word版本
  18. QT编译在注释中遇到意外的文件结束
  19. ZOJ 3981(Balloon Robot)
  20. ArcGIS基础实验操作100例--实验25统一多分辨率栅格数据

热门文章

  1. agc007B - Construct Sequences(构造)
  2. 微服务实战(五):落地微服务架构到直销系统(构建高性能大并发系统)
  3. orabbix 报错No suitable driver found for
  4. JavaScript性能优化 DOM编程
  5. 一次bug死磕经历之Hbase堆内存小导致regionserver频繁挂掉
  6. scala_until
  7. 正确使用硬盘的方法与维护
  8. CVS/SVN 托管服务
  9. Java 慎用方法级别的synchronized关键字
  10. maven集成tomcat进行web应用测试