“我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证。
开发人员知道保护Web应用程序安全可能会很麻烦。 正确地做是很难的。 最糟糕的是,“正确”是一个移动的目标。 安全协议会更改。 在依赖项中发现漏洞,并发布了补丁程序。 必须生成大量通常很复杂的样板代码。 在过去的十年中,软件即服务的范式已经泛滥成灾,尽管我喜欢和下一个开发人员一样重新发明轮子(因为显然,我会比他们雇用的yahoo更好地编写它),但是安全是一个领域在这里,我很乐意将这项工作分担给专家。 输入Okta。
在本教程中,您将使用Spring Boot来构建带有用户注册系统和登录系统的简单Web应用程序。 它具有以下功能:
- 登录和注册页面
- 密码重置工作流程
- 根据组成员身份限制访问
下载Spring Boot Web App示例项目
您首先需要的是免费的Okta帐户。 如果您还没有,请前往developer.okta.com并注册!
下一步是从GitHub下载本教程的示例项目。
git clone https://github.com/oktadeveloper/okta-spring-simple-app-example.git spring-app
该项目使用Gradle作为构建工具以及Thymeleaf模板系统。
运行初始Web应用
从GitHub存储库下载示例代码后,使用以下git命令git checkout tags/Start
Start
标记: git checkout tags/Start
。
目前,该应用程序尚未受到任何保护。 没有启用任何授权或身份验证(即使build.gradle
文件中包含必需的依赖build.gradle
)。 继续并运行示例,方法是打开一个终端,然后从项目根目录运行命令./gradlew bootRun
( bootRun
命令是Gradle Spring Boot插件提供的任务,已添加到build.gradle
中的build.gradle文件中。部分)。
在您喜欢的浏览器中导航到http://localhost:8080
,您应该看到以下内容:
如果您点击“受限”按钮:
为您的Spring Boot + Spring Security Web App添加项目依赖项
项目依赖关系在build.gradle
文件中定义(请参见下文)。 这个文件中有很多事情要做,本教程不会尝试向您解释Gradle构建系统。 随时检查他们的文档 。 我只想指出几件事。
首先,请注意,我们包括了okta-spring-boot-starter
。 该项目大大简化了Okta与Spring Boot应用程序的集成。 如果没有此启动程序,完全可以使用Okta和Spring Boot。 实际上,直到引入“组”和“角色”为止,两者之间的差异很小(主要涉及application.yml
更改)。 但是,一旦开始尝试集成组和角色,Okta Spring Boot Starter会节省大量代码。 如果您想更深入一点,请看一下Okta Spring Boot Starter GitHub项目 。
其余的依赖关系涉及Spring和Spring Boot。 您会注意到org.springframework.boot
依赖项都没有版本号。 这是因为Spring io.spring.dependency-management
Gradle插件完成了一些幕后魔术。 Spring Boot版本由build.gradle
文件顶部附近的构建脚本属性springBootVersion
设置。 基于此版本号,Spring依赖性管理插件决定要包括的依赖性版本。
我们还引入了org.springframework.boot
Gradle插件,该插件添加了我们将用于运行应用程序的bootRun
任务。
-
spring-boot-starter-security
和spring-boot-starter-web
是Spring Boot的核心依赖项。 - 必须使用
spring-security-oauth2-autoconfigure
才能使用@EnableOAuth2Sso
批注,该批注用于将OAuth和Single Sign-On挂接到我们的应用程序中。 -
spring-boot-starter-thymeleaf
和thymeleaf-extras-springsecurity4
引入了Thymeleaf模板系统,并将其与Spring Security集成。
buildscript { ext { springBootVersion = '2.0.5.RELEASE' } repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") }
} apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management' group = 'com.okta.springboot'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8 repositories { mavenCentral()
} dependencies { compile('com.okta.spring:okta-spring-boot-starter:0.6.0') compile('org.springframework.boot:spring-boot-starter-security') compile('org.springframework.boot:spring-boot-starter-web') compile('org.springframework.boot:spring-boot-starter-thymeleaf') compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity4') compile('org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.0.5.RELEASE') testCompile('org.springframework.boot:spring-boot-starter-test') "testCompile('org.springframework.security:spring-security-test')
} /* This is required to resolve a logging dependency conflict between the okta-spring-boot-starter and the various spring dependencies. */
configurations.all { exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' exclude group: 'org.springframework.boot', module: 'logback-classic'
}
了解您的Spring Boot应用程序
Java Web应用程序只有三个类文件和几个模板。 显然,Spring Boot在后台进行了大量繁重的工作,但是我们的类文件中发生了什么?
应用程序入口点在SpringSimpleApplication
类中:
@SpringBootApplication
public class SpringSimpleApplication { public static void main(String[] args) { SpringApplication.run(SpringSimpleApplication.class, args); }
}
这里发生了两个重要的事情,这些事情使事情发生了变化:1)我们使用@SpringBootApplication
批注,以及2)我们的main
方法调用SpringApplication.run()
方法。 这是整个Spring / Spring Boot系统的入口。
SpringSecurityWebAppConfig
类是一种使用Java代码配置Spring Boot处理Web应用程序安全性的方式。 在这里,我们使用HttpSecurity
对象从所有端点中删除授权。 默认情况下,Spring Boot的行为是相反的:所有端点都需要授权。
@Configuration
public class SpringSecurityWebAppConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().permitAll(); }
}
@Configuration
注释告诉Spring我们正在使用该类作为编程配置的源,从而允许我们重写configure()
方法。
最后一个Java类SimpleAppController
是我们唯一的控制器对象。 Spring Boot Web应用程序中的控制器是URL请求映射到Java代码的地方。 @Controller
注释告诉Spring此类是一个控制器。
@Controller
class SimpleAppController { @RequestMapping("/") String home() { return "home"; } @RequestMapping("/restricted") String restricted() { return "restricted"; } }
使用@RequestMapping
批注在类方法和URL之间建立连接。
我们有两个映射:
- “家庭”映射
- “受限”映射
请记住,一开始实际上没有任何“限制”,所以不要为此感到困惑。 您将锁定该映射一点。
还要注意,这些类返回一个简单的文本字符串,但这会自动变成完整的html文件。 这是build.gradle
文件中包含的Thymeleaf依赖项的一部分。 这些字符串被假定为模板文件名,默认情况下是类路径上templates
目录中的路径。
因此,“ home”被映射到src/main/resources/templates/home.html
模板文件。 当将Web应用程序打包到最终的jar中时,会将整个资源文件夹复制到类路径中,以便可以在运行时访问templates
目录。
为OAuth 2.0单一登录设置Okta
现在,您将为我们的应用设置授权。 Okta使这个超级容易。 您应该已经注册了一个免费的developer.okta.com帐户。 现在,您将创建一个OpenID Connect(OIDC)应用程序,以与OAuth 2.0单一登录(SSO)一起使用。
如果您还不熟悉它们的话,可能会有很多术语和缩写。 很简单, OAuth 2.0是授权的行业标准-一种标准化且经过测试的方法,授权服务器和应用程序可以通过该方法进行通信以促进用户授权。 OpenID Connect是OAuth 2.0之上的一层,用于标准化和简化授权过程以及提供用户验证。 它们一起为应用程序与提供身份验证和授权服务的远程服务器(例如Okta)进行交互提供了一种行之有效的方法。
要创建OIDC应用,请打开Okta开发人员信息中心。 单击“ 应用程序”顶部菜单项,然后单击“ 添加应用程序” 。
您应该看到以下屏幕。 单击“ Web”选项的图标。 单击下一步 。
您需要更新一些初始配置选项。 首先将名称更改为更具描述性的名称。 我使用了“ Okta Spring Boot简单Web应用程序”。 接下来,将登录重定向URI更新为http://localhost:8080/login
。 单击完成 。
这将带您进入新应用程序的常规配置选项卡。 向下滚动并记下客户端ID和客户端密码。 稍后您将需要这些。
这就是为Okat设置Okta所需要做的一切! 现在,让我们返回Spring Boot应用程序,并将新的OIDC应用程序挂接到Spring Boot应用程序中。
为单点登录(SSO)配置Spring Boot应用
现在,您需要配置Spring Boot应用程序以与Okta服务器进行交互。 这非常容易。 我们需要做两件事:
- 添加
@EnableOAuth2Sso
批注 - 更新
application.yml
配置
首先将@EnableOAuth2Sso
批注添加到SpringSecurityWebAppConfig
类。
@EnableOAuth2Sso
@Configuration
public class WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().anyRequest().permitAll(); }
}
@EnableOAuth2Sso
批注会做很多事情。 值得深入了解正在发生的事情。 您可以查看有关注解本身的Spring文档 ,以及它们的Spring Boot和OAuth2教程 。
我想指出的一件事(公元前这使我烦恼了一阵子,而我只是想出了这一点)是,您可以将此注释放在项目中的其他类上。 但是,如果这样做,请注意Spring将创建一个WebSecurityConfigurerAdapter并将其添加到安全链中。 由于我们还将创建一个WebSecurityConfigurerAdapter,因此将有两个,并且您将收到有关链订单冲突的错误。 这是因为两个WebSecurityConfigurerAdapters在默认情况下将使用相同的链顺序。 您可以通过在自定义类中添加@Order(101)
批注来解决此错误。 但是,更好的方法是将@EnableOAuth2Sso
批注添加到我们的WebSecurityConfigurerAdapter类WebSecurityConfigurerAdapter
,Spring将使用该类而不是创建重复的类。
您需要进行的第二个更改是更新src/main/resources/application.yml
文件,为来自Okta OIDC应用程序的OAuth SSO值填写一些Okta特定的配置选项。
您需要从上面创建的应用程序中填写您的客户ID和客户机密。 您还需要更改发行者URL,以使其反映您的Okta预览URL,例如dev-123456.oktapreview.com
。
server: port: 8080 spring: resources: static-locations: "classpath:/static/" okta: oauth2: issuer: https://{yourOktaDomain}/oauth2/default clientId: {yourClientId} clientSecret: {yourClientSecret}rolesClaim: groups
完善我们的权限
现在,您将要更新SpringSecurityWebAppConfig
类,以便拥有一个公共主页和一个受限制的“受限制”页面。 我们通过对HttpSecurity对象使用Spring的流畅API来实现此目的。
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@EnableOAuth2Sso
@Configuration
public class SpringSecurityWebAppConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/").permitAll() // allow all at home page.antMatchers("/img/**").permitAll() // allow all to access static images.anyRequest().authenticated(); // authenticate everything else!}
}
重新启动您的应用程序,现在您应该能够:
- 无需认证即可查看主页
- 未经身份验证就无法看到
/restricted
页面 - 能够使用Okta单一登录进行身份验证
本教程中的这一点与GitHub存储库中的OktaOAuthSSO
标签相对应。
看看Thymeleaf模板
总体而言,Thymeleaf模板是很容易解释的,但是我确实想指出几件事。 Thymeleaf模板是完全有效HTML5,这很好。 如果您想深入了解,可以访问他们的网站和他们的文档 。
我想指出的是模板如何引入身份验证信息。 为此,我们使用了thymeleaf-extras-springsecurity
插件。 这是包含在build.gradle
文件中的以下行:
compile ("org.thymeleaf.extras:thymeleaf-extras-springsecurity4")
并且作为主<html>
标记上的XML名称空间属性包含在模板文件中。
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"
这个插件使我们可以检查是否使用带有自定义SPEL表达式(Spring表达式语言)的th:if
属性对用户进行了身份验证。 它还允许我们插入身份验证属性。 在下面,您将看到一个跨度<span th:text="${#authentication.name}"></span>
,该<span th:text="${#authentication.name}"></span>
用于插入已验证用户的名称。
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head> <!--/*/ <th:block th:include="fragments/head :: head"/> /*/-->
</head>
<body>
<div class="container-fluid"> <div class="row"> <div class="box col-md-6 col-md-offset-3"> <div class="okta-header"> <img src="img/logo.png"/> </div> <!--/* displayed if account IS NOT null, indicating that the user IS logged in */--> <div th:if="${#authorization.expression('isAuthenticated()')}"> <h1 th:inline="text">Hello, <span th:text="${#authentication.name}"></span>!</h1> <a href="/restricted" class="btn btn-primary">Restricted</a> </div> <!--/* displayed if account IS null, indicating that the user IS NOT logged in */--> <div th:unless="${#authorization.expression('isAuthenticated()')}"> <h1>Who are you?</h1> <a href="/restricted" class="btn btn-primary">Restricted</a> </div> </div> </div>
</div>
</body>
</html>
thymeleaf-extras-springsecurity
插件还具有其他一些不错的功能。 如果您想更深入一点,请查看GitHub上的项目存储库 。
通过组成员身份安全访问
本教程的下一步是使用将在Okta上创建和定义的用户组添加基于组的身份验证。 一个非常常见的示例是让网站的“管理”部分和网站的“用户”部分,以及可能向所有人开放的公共主页。 在此示例中,“管理员”和“用户”将对应于两个不同的组,其中经过身份验证的用户可以是其成员。 我们要做的是能够基于用户组成员身份限制对URL端点的访问,并能够将用户分配给这些组。
旁注:小组与角色。 有什么不同?
- “组”是用户的集合,并且权限已分配给该组。 一般来说,至少在整个会话期间,组成员资格是相对静态的。
- “角色”是用户在该角色下执行操作时可以继承的一组权限。 角色本质上通常更具动态性。 用户可以具有许多角色。 经常根据复杂的标准激活或停用角色,并且在整个用户会话中经常可能会更改角色。
实际上,对于简单的授权系统,它们非常相似。 主要区别在于,组基于个人身份进行分类,而角色则基于允许的活动进行分类。 您可能会在狂野的互联网上看到无视这种差异的应用程序和教程,因为它们在功能上有些微妙。 (但是现在您知道了。您可以进入所涉及的教程的评论主题,并撰写评论以纠正作者。)
在Okta中配置授权组
转到您的developer.okta.com仪表板。 从顶部菜单中,转到“ 用户” ,然后单击“ 组” 。
单击添加组按钮。
将组命名为“ Admin”并对其进行描述(我放了“ Administrators”,无论您在这里实际输入什么,都无所谓,只是描述性的)。
单击组名称以打开组,然后单击添加成员按钮。 将您的用户添加到Admin组。
接下来,添加一个不是管理员的新用户。
- 从顶部菜单转到“ 用户 ”,然后单击“ 人员” 。
- 单击添加人 。
- 填写弹出表单:
- 名:不
接下来您需要做的是向默认授权服务器添加一个“组”声明。
- 从顶部菜单转到API ,然后单击“ 授权服务器 ”
- 单击默认授权服务器。
- 单击“ 索赔”选项卡。
- 点击添加声明按钮。
- 更新弹出表单以匹配下面的图像
- 名称:团体
您在这里所做的就是告诉Okta在发送给您的应用程序的访问令牌中包含“组”声明。 这是Okta的OAuth方法,用于向您的应用程序告知您已通过身份验证的用户所属的组。 令人困惑的是,这些在Spring应用程序端被称为“权威”,这是OAuth服务器与应用程序通信的组/角色/特权的抽象术语。
现在我们有两个用户。 您的主要用户(已添加到Admin组中)和一个新用户(不在admin组中)。 我们还配置了Okta,将群组声明添加到访问令牌中。 现在,我们要做的就是对应用程序代码进行一些更改!
更新您的Spring Boot + Spring Security App以使用基于组的授权
Okta Spring Boot Starter真正开始发挥作用。 通常,如果您想将我们发送令牌的安全组和组声明映射到应用程序中的组,则必须编写一两个提取程序类来处理提取操作,也许还要编写一个组类。 Okta Spring Boot Starter可以为您完成所有这些工作!
您要做的第一件事是在SpringSecurityWebAppConfig
类中添加以下注释。
@EnableGlobalMethodSecurity(prePostEnabled = true)
像这样:
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;@EnableOAuth2Sso
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityWebAppConfig extends WebSecurityConfigurerAdapter { /* class contents omitted for brevity */
}
此注释启用了我们将要使用的下一个注释@PreAuthorize
注释。 该注释使我们可以使用Spring Expression Language(SpEL)谓词来确定控制器方法是否得到授权。 谓词表达式会在应用程序甚至进入控制器方法之前执行(因此需要“预先”授权)。
在SimpleAppController
类中,添加一个名为admin
的新方法,如下所示:
import org.springframework.security.access.prepost.PreAuthorize;@Controller
class SimpleAppController { /* other controllers omitted for clarity */ @RequestMapping("/admin") @PreAuthorize("hasAuthority('Admin')") String admin() { return "admin"; } }
概括一下,此方法执行以下操作:
- 为
/admin
url端点创建一个映射; - 为
/admin
端点分配基于SpEL的授权方案; - 并简单地返回Thymeleaf模板的名称,假设该
/templates
位于/templates
目录中(我们将在下一个目录中创建)。
创建新的管理模板页面。 在src/main/resources/templates
目录中,创建一个名为admin.html
的新文件,其内容如下:
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head> <!--/*/ <th:block th:include="fragments/head :: head"/> /*/-->
</head>
<body>
<div class="container-fluid"> <div class="row"> <div class="box col-md-6 col-md-offset-3"> <div class="okta-header"> <img src="img/logo.png"/> </div> <h1>Welcome to the admin page!</h1> <a href="/" class="btn btn-primary">Go Home</a> </div> </div>
</div>
</body>
</html>
您可能会问自己@PreAuthorize
批注中使用的SpEL表达式是什么意思。 为什么SpEL表达式具有hasAuthority
而不具有hasGroup
? 正确的答案有些复杂,这与Spring在不同上下文中调用权限特权和权限有关,可以在应用程序中将其映射到组和角色。 当使用Spring Boot和OAuth时,“权限”通常等同于“角色”,这很好。 但是您说我们使用的是群组,而不是角色? 对。 实际上,在这种情况下,这没关系,因为Okta知道我们在谈论群组,而应用程序知道我们在谈论群组,并且在中间,我们仅使用group声明和Authority字段来传达文本代表用户所属组的字符串。
有用的提示:
如果要检查Spring Boot App正在接收的身份验证信息,可以在return语句之前的控制器方法之一中添加以下行。
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
确实在此行上或其后设置一个断点,然后使用允许您检查身份验证对象的调试器运行该应用程序。 这是学习和调试问题的好方法。
试用您的新Spring Boot + Spring Security Web App!
就是这样。 您应该能够重新启动应用程序并以两个不同的用户身份登录。 只有添加到“管理员”组的用户才能访问“管理员”页面。 您必须直接导航到http:// localhost:8080 / admin(因为我们没有添加链接或按钮)。 如果您尝试与其他用户一起导航到管理页面,则会看到漂亮的whitelabel错误页面,其中显示403 /未经授权的错误。
请记住,在用户之间切换时,您必须停止应用程序,注销您的developer.okta.com帐户,然后重新启动应用程序。 您也可以在浏览器中使用隐身窗口。
本教程的这一部分与GroupsAuth
标记相对应,您可以使用以下命令git checkout tags/GroupsAuth
。
了解有关Spring Boot,Spring Security和安全用户管理的更多信息
您在这里取得了一些实际进展。 您了解了如何创建一个简单的Spring Boot应用程序以及如何使用Thymeleaf模板。 您已经了解到Okta使OAuth 2.0 Single Sign-On集成到您的应用中有多么容易。 您已经了解了如何使用WebSecurityConfigurerAdapter
子类和http.authorizeRequests()
API来限制对控制器端点的访问。
最后,您已经了解了如何在Okta上创建组和用户,如何将它们绑定到Spring Boot应用程序中,以及如何使用@PreAuthorize
批注根据组成员身份配置授权。
如果您想查看这个完整的项目,可以在Github上找到该仓库: https : //github.com/moksamedia/okta-spring-simple-app 。
如果您想了解有关Spring Boot,Spring Security或Okta的更多信息,请查看以下任何出色的教程:
- Spring Boot,OAuth 2.0和Okta入门
- 15分钟内将单一登录添加到您的Spring Boot Web App
- 使用多重身份验证保护您的Spring Boot应用程序安全
- 使用Spring Boot和GraphQL构建安全的API
如果您想深入研究,请查看Okta Spring Boot Starter GitHub页面 。
如果您对此帖子有任何疑问,请在下面添加评论。 有关更多精彩内容, 请在Twitter上关注@oktadev , 在Facebook上关注我们,或订阅我们的YouTube频道 。
“我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证。
“使用Spring Boot和Spring Security在15分钟内构建Web应用程序”最初于2018年9月26日在Okta开发人员博客上发布。
翻译自: https://www.javacodegeeks.com/2019/02/build-web-app-spring-boot-spring-security.html