使用Okta的身份管理平台轻松部署您的应用程序 使用Okta的API在几分钟之内即可对任何应用程序中的用户进行身份验证,管理和保护。 今天尝试Okta。
Jetty是一个小型,高度可扩展的基于Java的Web服务器和servlet引擎。 它支持HTTP / 2,WebSockets和许多其他协议。 它为大型和小型网站和框架(例如Google AppEngine)提供支持。 因为它是一个Eclipse项目,所以其开源项目称为Eclipse Jetty。 它符合标准,是开源的,并且可以商业使用。 当托管Java应用程序时,它是Tomcat的主要替代方法。 就像使用Tomcat一样,您可以同时使用嵌入式和独立的Jetty。
默认情况下,Spring Boot使用嵌入式Web服务器创建应用程序,这意味着该服务器是嵌入在应用程序代码本身中的,因此您不必运行单独的Web服务器即可发布Java Web应用程序。 但是,只需进行一些配置,您还可以将WAR文件发布到单独的Jetty或Tomcat Servlet容器(老式的应用程序服务器样式)。 Spring默认情况下也使用Tomcat,但是您可以轻松地更改它,如您所见。
在本教程中,您将构建一个嵌入了Jetty的简单Web服务。 之后,您将在Spring Boot和Jetty中构建相同的Web服务。 最后,您将使用方法级安全性(以Okta作为OAuth / OIDC提供者)将JWT(JSON Web令牌)身份验证和授权添加到Web服务。
安装项目依赖项
开始之前,您需要先安装一些东西。
Java 11 :该项目使用Java11。如果没有Java 11,则可以安装OpenJDK 。 您也可以使用Homebrew安装OpenJDK。 SDKMAN是用于安装和管理Java SDK的另一个不错的选择。
HTTPie :这是用于发出HTTP请求的简单命令行实用程序。 您将使用它来测试REST应用程序。 在其网站上查看安装说明 。
Okta开发人员帐户 :您将Okta用作OAuth / OIDC提供程序,以向应用程序添加JWT身份验证和授权。 如果尚未登录,请访问他们的网站并注册他们的免费开发者帐户之一。
Gradle :这是可选安装。 如果您从仓库中下载了本教程的项目,则可以使用Gradle包装器运行该项目,而无需安装Gradle。 。 如果您想从头开始构建项目,则需要安装Gradle 。
使用Java和Jetty构建简单的Web服务
本教程的第一步是使用Java和Gradle构建一个简单的Web服务。 为此,您将使用Gradle的Gretty插件 。 Gretty使使用Gradle在嵌入式servlet容器上运行Web应用程序变得超级容易,并支持Tomcat和Jetty。
如果您选择从GitHub存储库下载本教程的项目 ,请遵循以下几个步骤,同时我将介绍如何从头开始构建项目。
git clone https://github.com/oktadeveloper/okta-spring-boot-jetty-example.git
首先,打开一个shell并导航到您想要项目驻留的适当目录(或创建一个目录)。 使用Gradle CLI初始化项目。
mkdir jetty
cd jetty
gradle init --type=basic --dsl=groovy --project-name=JettyEmbedded
编辑项目根目录中的build.gradle
文件:
plugins {
id 'java'
id 'war'
id 'org.gretty' version '2.3.1'
}
repositories {
jcenter()
}
dependencies {
providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
}
gretty {
contextPath = '/'
}
我想在这里指出一些事情。 注意plugins
块中的org.gretty
插件。 此外,注意javax.servlet:javax.servlet-api
您添加依赖使用providedCompile
。 这会将其添加到编译类路径中,但不会将其添加到打包的war文件中,因为这将由servlet容器在部署时提供。 最后,请注意,嵌入式servlet容器的上下文路径已设置为gretty
块中的gretty
。
现在,为Java文件创建根目录( src/main/java
是标准Java根文件夹,加上com.okta.jettyembedded
包):
mkdir -p src/main/java/com/okta/jettyembedded
创建一个简单的servlet:
src/main/java/com/okta/jettyembedded/Hello.java
package com.okta.jettyembedded;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "HelloServlet", urlPatterns = {"hello"}, loadOnStartup = 1)
public class Hello extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().print("Howdy");
}
}
现在您可以运行该应用程序:
gradlew apprun
要测试它,请打开另一个shell窗口并使用HTTPie:
http :8080/hello
HTTP/1.1 200 OK
Content-Length: 5
Date: Fri, 06 Sep 2019 20:23:40 GMT
Server: Jetty(9.2.26.v20180806)
Howdy
您已经构建了一个超级简单的Web servlet。 它还没有做很多,但是(希望)可以工作。 注意Hello.java
类中的@WebServlet
批注。 在这里,您可以配置一些servlet参数,而不是在web.xml
文件中。 将此配置移动到代码中,可以更轻松地构建和维护某些Servlet配置。
接下来,您将看到功能更全的Web Servlet。
通过添加和删除改进Java Servlet
现在,您将创建一个Web应用程序以跟踪远足列表。 它将演示如何支持POST和DELETE操作以及简单的GET和一些简单的错误处理。
创建一个新的Java文件:
src/main/java/com/okta/jettyembedded/HikesTodoServlet.java
package com.okta.jettyembedded;
import java.io.IOException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@WebServlet(name = "HikesTodoServlet", urlPatterns = {"hikes"}, loadOnStartup = 1)
public class HikesTodoServlet extends HttpServlet {
// Not synchronized
private List<String> hikes = new ArrayList<>(Arrays.asList(
"Wonderland Trail", "South Maroon Peak", "Tour du Mont Blanc",
"Teton Crest Trail", "Everest Base Camp via Cho La Pass", "Kesugi Ridge"
));
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
response.getWriter().print(String.join("\n", this.hikes));
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException {
String hike = request.getParameter("hike");
if (hike == null) {
response.setStatus(400);
response.getWriter().print("Param 'hike' cannot be null.");
}
else if (this.hikes.contains(hike)) {
response.setStatus(400);
response.getWriter().print("The hike '"+hike+"' already exists.");
}
else {
this.hikes.add(hike);
response.getWriter().print(String.join("\n", this.hikes));
}
}
protected void doDelete(HttpServletRequest request, HttpServletResponse response)
throws IOException {
String hike = request.getParameter("hike");
if (hike == null) {
response.setStatus(400);
response.getWriter().print("Param 'hike' cannot be null.");
}
else {
this.hikes.remove(hike);
response.getWriter().print(String.join("\n", this.hikes));
}
}
}
使用Control-C
停止服务器,然后使用gradle apprun
重启服务器。
获取远足清单:
http :8080/hikes
HTTP/1.1 200 OK
...
Wonderland Trail
South Maroon Peak
Tour du Mont Blanc
Teton Crest Trail
Everest Base Camp via Cho La Pass
Kesugi Ridge
发布新的加息:
http -f POST :8080/hikes hike="Pear Lake"
HTTP/1.1 200 OK
...
Wonderland Trail
South Maroon Peak
Tour du Mont Blanc
Teton Crest Trail
Everest Base Camp via Cho La Pass
Kesugi Ridge
Pear Lake
删除远足:
http DELETE :8080/hikes hike=="South Maroon Peak"
HTTP/1.1 200 OK
...
Wonderland Trail
Tour du Mont Blanc
Teton Crest Trail
Everest Base Camp via Cho La Pass
Kesugi Ridge
Pear Lake
现在,尝试删除不存在的加息,或发送一个空值:
http DELETE :8080/hikes
HTTP/1.1 400 Bad Request
...
Param 'hike' cannot be null.
要将其部署到实时服务器,您可以按原样部署项目,使用gradle apprun
通过嵌入式Jetty服务器运行应用程序。 您还可以通过使用gradle war
构建war文件并将war文件(位于build/libs
)复制到服务器上,从而将其部署到外部Jetty服务器。
注意:这是REST服务的非常幼稚的实现。 它使用内存中的
ArrayList
作为不同步的数据源(因此将在实际的Web servlet中遇到线程问题)。 对于超出本教程范围的任何内容,您都需要实现某种数据库后端。 有关如何执行此操作的帮助,请参阅教程末尾列出的示例博客文章。 通常,您还将添加一个PUT端点,并为每个项目分配一个ID用作索引,以便可以更新数据,但这超出了本教程的范围。
到目前为止,一切进展顺利。 在下一部分中,您将使用Spring Boot重新创建相同的Hikes ToDo应用,并使用Okta作为OAuth / OIDC提供者对应用进行JWT令牌认证。
创建一个OIDC应用程序
现在,让我们前往Okta进行一些实地考察,并为OAuth / OpenID Connect(OIDC)进行设置。 它们一起是用于实施安全授权和身份验证的一组开放标准。 在本教程中,Okta将充当身份提供者,而您的Spring Boot应用将成为客户端。
您应该已经在Okta注册了免费的开发者帐户。 浏览至https://developer.okta.com上的开发人员仪表板。 如果这是您第一次登录,则可能需要单击“ 管理员”按钮。
要配置JWT身份验证和授权,您需要创建一个OIDC应用程序。
在顶部菜单中,单击“ 应用程序”按钮。 单击添加应用程序按钮。
选择应用程序类型Web 。
单击下一步 。
为应用命名。 我将其命名为“ Spring Boot Jetty”。
在登录重定向URI下 ,添加两个新的URI:
-
https://oidcdebugger.com/debug
-
http://localhost:8080/login/oauth2/code/okta
在“ 允许的授予类型”下 ,选中“ 隐式(混合)” 。
其余的默认值将起作用。
单击完成 。
使页面保持打开状态或记下Client ID 。 生成令牌时需要一点时间。
注意:您将使用oidcdebugger.com重定向URI和隐式授予类型创建访问令牌,您可以在命令行中使用HTTPie。 第二个URI是Spring Security在使用OAuth登录功能时用于Okta的默认重定向URI。
使用Jetty创建一个Spring Boot项目
要创建Spring Boot项目,您将使用Spring Initializr 。 查看其GitHub项目以查看其代码。 Initializr有一个不错的Web表单,用于配置和下载Spring Boot入门项目,但是对于该项目,您将使用其REST API。
从外壳执行以下命令,以下载已配置的启动程序项目的zip文件。
http https://start.spring.io/starter.zip \
javaVersion==11 \
dependencies==web \
language==java \
type==gradle-project \
name==SpringBootJetty \
groupId==com.okta.springbootjetty \
artifactId==SpringBootJetty \
packageName==com.okta.springbootjetty -o SpringBootJetty.zip
解压缩下载的文件,然后在您选择的IDE中打开目录。
首先,修改build.gradle
文件,以便项目使用Jetty嵌入式容器,而不是默认的Tomcat。 添加spring-boot-starter-jetty
依赖关系,并排除spring-boot-starter-tomcat
依赖关系。
更改build.gradle
文件以匹配以下内容:
plugins {
id 'org.springframework.boot' version '2.2.0.RELEASE'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
id 'java'
}
group = 'com.okta.springbootjetty'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-jetty'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
useJUnitPlatform()
}
configurations {
compile.exclude module: "spring-boot-starter-tomcat"
}
现在添加一个WebController.java
文件。
src/main/java/com/okta/springbootjetty/WebController.java
package com.okta.springbootjetty;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Controller
public class WebController {
private List<String> hikes = new ArrayList<>(Arrays.asList(
"Wonderland Trail", "South Maroon Peak", "Tour du Mont Blanc",
"Teton Crest Trail", "Everest Base Camp via Cho La Pass", "Kesugi Ridge"
));
@GetMapping("/")
@ResponseBody
public String indexGet() {
return String.join("\n", this.hikes);
}
@PostMapping("/")
@ResponseBody
public String indexPost(@RequestParam String hike, HttpServletResponse response) {
if (hike == null) {
response.setStatus(400);
return "Param 'hike' cannot be null.";
}
else if (this.hikes.contains(hike)) {
response.setStatus(400);
return "The hike '"+hike+"' already exists.";
}
else {
this.hikes.add(hike);
return String.join("\n", this.hikes);
}
}
@DeleteMapping("/")
@ResponseBody
public String indexDelete(@RequestParam String hike, HttpServletResponse response) {
if (hike == null) {
response.setStatus(400);
return "Param 'hike' cannot be null.";
}
else {
this.hikes.remove(hike);
return String.join("\n", this.hikes);
}
}
}
该控制器重新创建在第一个Jetty Hikes ToDo应用程序中发现的相同功能,但现在使用Spring Boot。 您会注意到Spring简化了一些语法。 @ResponseBody
注释告诉Spring Boot控制器正在直接返回响应主体(与返回模板名称相反)。 另外,请注意,代码使用依赖注入来获取HttpServletResponse
以及请求参数。
运行Spring Boot REST服务(确保您的其他服务已停止,否则您将收到端口冲突错误):
gradle bootRun
在第二个Shell窗口中,获取远足列表(注意,下面没有/hikes
路径)。
http :8080
另外,尝试添加和删除新的加息。
发布新的加息:
http -f POST :8080 hike="Pear Lake"
删除远足:
http DELETE :8080 hike=="South Maroon Peak"
部署Spring Boot项目
现在,您有了一个在嵌入式Jetty容器上运行的Spring Boot应用程序。 要将其部署到生产服务器,请使用gradle bootJar
构建可执行的jar文件,将该jar文件复制到服务器,然后使用java -jar <your jar file name>.jar
运行它。 无需单独的Web服务器,因为此jar包含嵌入式Jetty Web服务器。
注意:对于在同一服务器上具有多个单独应用程序的应用程序服务器,如果要进行更老式的部署,则需要构建war文件。 有关如何执行此操作的Spring文档非常有用。 从本质上讲,你需要做两件事情:1)添加
war
插件到项目的依赖,以及2)改变码头或Tomcat依赖于providedRuntime
所以它不是在打包的战争包括在内。 然后,您构建一个war文件并将其部署到服务器上的servlet Web应用程序路径。
将OAuth / OIDC登录名添加到Spring Boot App
您注册了Okta并创建了OIDC应用程序。 现在是时候配置Spring Boot应用程序以使用OAuth / OIDC进行身份验证和授权了。
首先,将您的Issuer URI添加到src/main/resources/application.properties
文件。 您需要用实际的Okta URL替换{yourOktaUrl}
。 如果您访问https://developer.okta.com并导航至API和授权服务器 ,则会看到default
授权服务器的Issuer URI。
![](https://i-blog.csdnimg.cn/blog_migrate/a00f4ce1ec362fd0e5965223ef204bcd.png)
okta.oauth2.issuer=https://{yourOktaUrl}/oauth2/default
在该文件中时,从您先前创建的“ Spring Boot Jetty”应用程序中添加客户端ID和客户端密钥。
okta.oauth2.clientId={clientId}
okta.oauth2.clientSecret={clientSecret}
接下来,您需要在dependencies {}
块中的build.gradle
文件中添加以下依赖。
implementation 'com.okta.spring:okta-spring-boot-starter:1.3.0'
其中包括Okta Spring Boot Starter,这是一个很好的项目,可简化Spring Boot对Okta身份验证和授权的使用。 查看项目页面以获取更多信息 。
您还需要更新SpringBootJettyApplication
类以匹配以下内容:
package com.okta.springbootjetty;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@SpringBootApplication
public class SpringBootJettyApplication extends WebSecurityConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(SpringBootJettyApplication.class, args);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().permitAll()
.and()
.oauth2Login()
.and()
.oauth2ResourceServer().jwt();
}
}
保护您的DELETE和POST端点
configure(HttpSecurity http)
方法将具有OAuth 2.0登录名的Spring Boot应用配置为OAuth 2.0资源服务器,并默认允许所有请求。 您将通过@PreAuthorize
批注使用方法级别的安全性来保护下面的DELETE和POST端点。
最后,将@PreAuthorize("isAuthenticated")
批注添加到WebController
类的indexPost()
和indexDelete()
方法中。
package com.okta.springbootjetty;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Controller
public class WebController {
private List<String> hikes = new ArrayList<>(Arrays.asList(
"Wonderland Trail", "South Maroon Peak", "Tour du Mont Blanc",
"Teton Crest Trail", "Everest Base Camp via Cho La Pass", "Kesugi Ridge"
));
@GetMapping("/")
@ResponseBody
public String indexGet() {
return String.join("\n", this.hikes);
}
@PreAuthorize("isAuthenticated") // <- ***ADDED***
@PostMapping("/")
@ResponseBody
public String indexPost(@RequestParam String hike, HttpServletResponse response) {
if (hike == null) {
response.setStatus(400);
return "Param 'hike' cannot be null.";
}
else if (this.hikes.contains(hike)) {
response.setStatus(400);
return "The hike '"+hike+"' already exists.";
}
else {
this.hikes.add(hike);
return String.join("\n", this.hikes);
}
}
@PreAuthorize("isAuthenticated") // <- ***ADDED***
@DeleteMapping("/")
@ResponseBody
public String indexDelete(@RequestParam String hike, HttpServletResponse response) {
if (hike == null) {
response.setStatus(400);
return "Param 'hike' cannot be null.";
}
else {
this.hikes.remove(hike);
return String.join("\n", this.hikes);
}
}
}
您现在有了受保护的Web服务。 您可以发出GET请求,但无法发布或删除。 重新启动服务器,并使用以下HTTPie命令对此进行验证。
http :8080
HTTP/1.1 200 OK
...
Wonderland Trail
South Maroon Peak
Tour du Mont Blanc
Teton Crest Trail
Everest Base Camp via Cho La Pass
Kesugi Ridge
http -f POST :8080 hike="Pear Lake"
HTTP/1.1 403 Forbidden
...
{
"error": "Forbidden",
"message": "Forbidden",
"path": "/",
"status": 403,
"timestamp": "2019-09-07T16:13:59.474+0000"
}
使用OIDC调试器生成JWT
要访问受保护的端点,您需要生成一个JWT。 为此,您可以使用OIDC调试器 。 您将需要先前创建的OIDC应用程序中的客户端ID,以及基本Okta URI(与Issuer URI中的基本URI相同)。
打开OIDC调试器 。
将授权URI更新为: https://{yourOktaUri}/oauth2/default/v1/authorize
从OIDC应用程序将客户端ID更新为客户端ID。
在“ 状态”字段中放置一些内容。 就本教程而言,这可以是任何东西。 此值用于帮助防止跨站点伪造请求。
![](https://i-blog.csdnimg.cn/blog_migrate/f16c653fb5a73ce7fa35376de1d82f56.png)
向下滚动并单击发送请求 。
将令牌复制到剪贴板,并将其存储在用于发出请求的shell窗口中的shell变量中。
TOKEN=eyJraWQiOiJIb05xb01mNE9jREltWnBGRnBINjZGTkFOM0J...
现在尝试发布新的加息,然后将其删除。
http -f POST :8080 hike="Pear Lake" "Authorization: Bearer $TOKEN"
HTTP/1.1 200 OK
...
Wonderland Trail
South Maroon Peak
Tour du Mont Blanc
Teton Crest Trail
Everest Base Camp via Cho La Pass
Kesugi Ridge
Pear Lake
http DELETE :8080 hike=="South Maroon Peak" "Authorization: Bearer $TOKEN"
HTTP/1.1 200 OK
...
Wonderland Trail
Tour du Mont Blanc
Teton Crest Trail
Everest Base Camp via Cho La Pass
Kesugi Ridge
Pear Lake
您还配置了此应用程序以使用Spring Security的oauth2Login()
。 这意味着您可以转到http://localhost:8080/login
,单击发行者URL,然后也以这种方式登录。
了解有关Java,Spring Boot和Spring Security的更多信息
就是这样。 在本教程中,您了解了如何制作一个简单的Java servlet服务并使用Jetty运行它。 您还了解了如何在Spring Boot中重新创建相同的服务,如何将其配置为使用Jetty,以及简化Java代码。 最后,您了解了如何使用Okta提供的免费开发者帐户向您的Spring Boot应用程序添加OAuth / OIDC安全性。
您可以在oktadeveloper / okta-spring-boot-jetty-example上的GitHub上找到本教程的代码。
以下是一些相关的博客文章:
- Java应用程序的简单令牌认证
- 在15分钟内使用Spring Boot和Spring Security构建一个Web应用程序
- 创建一个安全的Spring REST API
- 使用Spring Boot和Vue.js构建一个简单的CRUD应用
如果您对此帖子有任何疑问,请在下面添加评论。 有关更多精彩内容, 请在Twitter上关注@oktadev , 在Facebook上关注我们,或订阅我们的YouTube频道 。
使用Okta的身份管理平台轻松部署您的应用程序 使用Okta的API在几分钟之内即可对任何应用程序中的用户进行身份验证,管理和保护。 今天尝试Okta。
翻译自: https://www.javacodegeeks.com/2019/12/get-started-with-jetty-java-and-oauth.html