朋友不允许朋友写用户身份验证。 厌倦了管理自己的用户? 立即尝试Okta的API和Java SDK。 在几分钟之内即可对任何应用程序中的用户进行身份验证,管理和保护。
本教程将探讨使用Spring Security在Spring Boot中配置身份验证和授权的两种方法。 一种方法是创建WebSecurityConfigurerAdapter
并使用流畅的API覆盖HttpSecurity
对象上的默认设置。 另一个方法是在控制器方法上使用@PreAuthorize
批注,称为方法级安全性或基于表达式的安全性。 后者将是本教程的重点。 但是,我将通过对比介绍一些HttpSecurity
代码和想法。
第一种身份验证方法是HttpSecurity
,它是全局的,默认情况下应用于所有请求。 但是,可以通过对端点使用模式匹配来进行更细粒度的控制,并且HttpSecurity
公开的流畅API十分强大。 在此处公开了OAuth 2.0,表单登录和HTTP Basic等配置选项。 这是设置全局身份验证策略的好地方。
方法级别的安全性是通过将@PreAuthorize
批注放置在控制器方法上(实际上是一组可用的批注之一,但最常用)来实现。 该批注包含一个Spring Expression Language(SpEL)代码段,该代码段经过评估以确定是否应该对请求进行身份验证。 如果未授予访问权限,则不会执行该方法,并返回HTTP Unauthorized。 实际上,在控制器方法上使用@PreAuthorize
注释与在特定端点上使用HttpSecurity
模式匹配器非常相似。 但是有一些区别。
区分Spring Security的@PreAuthorize和HttpSecurity
第一个区别是微妙的,但值得一提。 在发生控制器映射之前, HttpSecurity
方法会在Web请求过滤器中更早地拒绝该请求。 相反, @PreAuthorize
评估是在控制器方法执行之前@PreAuthorize
进行的。 这意味着在HttpSecurity
之前应用 @PreAuthorize
中的配置。
其次, HttpSecurity
绑定到URL端点,而@PreAuthorize
绑定到控制器方法,并且实际上位于与控制器定义相邻的代码内 。 将所有安全性集中在一处并由Web终结点定义具有一定的整洁性,尤其是在较小的项目中,或者在更全局的设置中; 但是,随着项目的扩大,使授权策略靠近受保护的代码可能更有意义,这就是基于注释的方法所允许的。
另一个优点是@PreAuthorize
呈现在HttpSecurity
就是利用规划环境地政司的。 使用Spring Expression Language,您可以基于复杂的表达式来做出授权决策,这些复杂的表达式可以访问内置的身份验证对象(例如authentication
和principal
),依赖项注入的方法参数和查询参数。 在本教程中,您将主要看两个表达式: hasAuthority()
和hasRole()
。 Spring文档再次是深入研究的好地方。
在深入研究该项目之前,我还要提到Spring还提供了@PostAuthorize
批注。 毫不奇怪,这是在方法执行后评估的方法级授权注释。 我们为什么要这样做? 它允许方法在评估注释之前根据喜欢的控制器逻辑执行自己的授权检查。 缺点是,由于控制器方法是在评估注释之前执行的,因此可能会导致效率低下,具体取决于实现方式。
依存关系
本教程的依赖项非常简单。 您需要:1)已安装Java 8+,以及2)Okta开发人员帐户。
如果没有安装Java,请转到AdoptOpenJDK 。 在* nix系统上,您也可以使用SDKMAN 。
如果您还没有免费的Okta开发者帐户,请访问我们的网站并注册 。
使用Spring Initializr启动一个示例项目
要启动项目,可以使用Spring Initializr 。 但是,尽管值得一游,但您甚至不必费心去那里创建项目。 您可以使用REST API和curl
。
打开终端,然后将cd
转到您希望项目文件.zip结束的任何位置。 运行以下命令,该命令将下载压缩的Spring Boot项目。
curl https://start.spring.io/starter.zip \
-d dependencies=web,security \
-d type=gradle-project \
-d bootVersion=2.1.5.RELEASE \
-d groupId=com.okta.preauthorize \
-d artifactId=application \
-o PreAuthorizeProject.zip
unzip PreAuthorizeProject.zip
除了build.gradle
文件和DemoApplication.java
类文件之外,该项目没有多少其他内容。 但是,已经为您设置了整个项目结构。
对于本示例, build.gradle
文件还具有两个Spring依赖项:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
}
添加一个WebController
当前状态下的示例应用程序并没有做太多事情。 您需要添加一个控制器来定义一些端点和响应。
添加一个新文件src/main/java/com/okta/preauthorize/application/WebController.java
:
package com.okta.preauthorize.application;
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 home() {
return "Welcome home!";
}
@RequestMapping("/restricted")
@ResponseBody
public String restricted() {
return "You found the secret lair!";
}
}
该控制器定义两个端点: /
和/restricted
。 您将稍后向/restricted
端点添加方法级安全性。 但是,目前尚未配置安全性。
继续运行该应用程序。 在根项目目录中,运行:
./gradlew bootRun
Spring Boot完成启动后,导航至http://localhost:8080
。
您会注意到出现了一个登录表单。 哇! 那个是从哪里来的?!
该表单是由Spring Boot自动生成的。 看一下Spring类WebSecurityConfigurerAdapter
和方法configure(HttpSecurity http)
。 这是配置默认身份验证设置的位置。
/**
* Override this method to configure the {@link HttpSecurity}. Typically subclasses
* should not invoke this method by calling super as it may override their * configuration. The default configuration is:
*
*
* http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
*
*
* @param http the {@link HttpSecurity} to modify
* @throws Exception if an error occurs
*/
protected void configure(HttpSecurity http) throws Exception {
...
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
默认情况下,所有请求都需要身份验证。 为所有请求启用基于表单的身份验证和HTTP Basic身份验证。
如果要登录,请在控制台输出中查找类似于以下内容的行,以告诉您所生成的密码:
Using generated security password: 9c299bb9-f561-4c12-9810-c9a2bc1dca08
用户名就是“用户”。 每次运行Spring Boot时都会重新生成此密码。
身份验证与授权
在继续之前,我想快速确保两个术语明确:身份验证和授权。 身份验证回答问题:谁在发出请求? 授权回答了这个问题:他们被允许做什么?
通常,首先进行身份验证,除非为匿名用户设置了特定的权限(在某些方面这是隐式身份验证)。 授权基于经过身份验证的用户的价值。 认证实体可以是人类用户或自动化服务,也可以是代表人类用户运行的服务。
两种常见的授权方案基于组和角色。 这两个术语通常在网络上不太知名的地方被混用并互换使用,但是在官方上有区别。 组定义用户组,并向这些用户组分配权限。 用户可以是多个组的成员。 角色定义可以分配给用户的权限集(允许的操作或资源)。 在实践中,组往往是一种更静态,更不灵活的控制器访问方式,而角色通常是精细粒度的,甚至在会话中也可以是动态的,为特定任务分配角色,并在不再需要它们时将其撤销。 使用Amazon AWS的任何人都已经看到了这种做法,通常会产生令人困惑的效果。
为Spring @PreAuthorize启用方法级安全性
您现在想要做的是配置Spring Boot以允许home端点上的请求,同时将请求限制为/restricted
端点。
您最初可能以为可以将@PreAuthorize("permitAll()")
到本地端点,这将允许所有请求。 但是,如果尝试使用它,您会发现它什么也没做。 这是因为默认的HttpBuilder
实现仍处于活动状态,并且因为它是在Web请求过滤器链中进行评估的,因此它具有优先权。 您还必须显式启用方法级别的安全注释,否则,它们将被忽略。
添加以下SecurityConfig
类,将实现上述两个目标。
src/main/java/com/okta/preauthorize/application/SecurityConfig.java
package com.okta.preauthorize.application;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(final HttpSecurity http) throws Exception {}
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
注释启用了@PreAuthorize
注释。 可以使用@Configuration
批注将其添加到任何类。 在这里,我将不做任何深入的介绍,但是您也可以启用@Secured
,较旧的Spring Security注释和JSR-250注释。
configure(final HttpSecurity http)
方法将覆盖默认的HttpBuilder
配置。 因为它是空的,所以它使应用程序未经授权或认证。
使用./gradlew bootRun
再次运行该应用程序,您会发现两个端点都已完全打开。
实施全球安全政策
应用程序通常必须选择要围绕其构建安全性的全局安全策略:“默认为允许”或“默认为已验证”。 该应用程序默认情况下是否打开? 还是默认情况下受限制? 我通常默认为受限,并明确允许任何公共端点。 对于我所使用的专有Web应用程序类型,这些类型通常不是公开的,或者具有相对较小的公开面Kong,因此该方案有意义。 但是,如果您要进行的工作大部分是公开的,并且要有离散的访问控制支持,例如带有管理面板的网站,那么可能会采用一种更为宽松的方案。 您将在此处查看两者。
由于该应用已经开放,因此我将首先向您展示如何限制特定方法。 在那之后,您将研究几种方法来实施更具全局限制的访问策略。
在WebController
类中,将@PreAuthorize
批注添加到/restricted
端点,如下所示:
@PreAuthorize("isAuthenticated()")
@RequestMapping("/restricted")
@ResponseBody
public String restricted() {
return "You found the secret lair!";
}
运行应用程序( ./gradlew bootRun
)。
这次,您将能够导航到主页,但是转到/restricted
端点将为您提供(非常难看的)whitelabel错误页面。
![](https://i-blog.csdnimg.cn/blog_migrate/f8ab4f3c7c749f6cc4c8cb1978aceb09.png)
在生产应用程序中,您需要重写此代码以返回更好的自定义错误页面,或者以其他方式处理错误(如果要创建自定义错误页面,请查看主题的Spring文档 )。 但是您可以看到该应用返回的是403 /未经授权 ,这就是您想要的。
大。 现在,改为更改您的WebController
类以匹配以下内容:
@Controller
@PreAuthorize("isAuthenticated()")
public class WebController {
@PreAuthorize("permitAll()")
@RequestMapping("/")
@ResponseBody
public String home() {
return "Welcome home!";
}
@RequestMapping("/restricted")
@ResponseBody
public String restricted() {
return "You found the secret lair!";
}
}
在这里,您已使用@PreAuthorize
批注将整个控制器类限制为经过身份验证的用户,并明确允许所有请求(无论身份验证状态如何)到本地端点。
我知道我们一直将其称为“方法级”安全性,但实际上,也可以将这些@PreAuthorize
批注添加到控制器类中,以为整个类设置默认值。 这也是@PreAuthorize("permitAll()")
派上用场的地方,因为它可以覆盖类级别的注释。
如果运行该应用程序( ./gradlew bootRun
)并尝试使用端点,则会发现home端点处于打开状态,而/restricted
端点处于关闭状态。
请注意,如果添加了另一个单独的Web控制器,则默认情况下,其所有方法仍将处于打开状态,并且不需要身份验证。
第三种选择(我最喜欢的是大多数中小型应用程序)是使用HttpBuilder
在默认情况下要求对所有请求进行身份验证并显式允许任何公共端点。 这使您可以使用@PreAuthorize
来基于用户或角色或组来优化对特定方法的访问控制,但要明确指出, 除非明确允许 , 否则所有路径都将应用一些基本安全性。 这也意味着公共路径定义在中心位置。 同样,这适用于某种类型的项目,但可能不是所有项目的最佳结构。
要实现此目的,请将WebController
类更改为此(删除所有@PreAuthorize
批注):
@Controller
public class WebController {
@RequestMapping("/")
@ResponseBody
public String home() {
return "Welcome home!";
}
@RequestMapping("/restricted")
@ResponseBody
public String restricted() {
return "You found the secret lair!";
}
}
并将WebSecurity
类更改为:
Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(final HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and().formLogin();
}
}
您要做的是使用SecurityConfig
类显式允许本地终结点计算机上的所有请求,同时要求在所有其他终结点计算机上进行身份验证。 这为您的应用设置了全面的最低身份验证要求。 您还重新启用了基于表单的身份验证。
尝试一下!
使用以下./gradlew bootRun
运行应用程序: ./gradlew bootRun
。
导航到打开的home端点: http://localhost:8080
。
以及需要认证的受限端点: http://localhost:8080/restricted
。
当Spring的登录表单出现时,别忘了您可以使用默认凭据。 用户是“用户”,并且在控制台输出中找到了密码(查找Using generated security password:
。
前进到OAuth 2.0登录
这些天,基于表单的身份验证感觉非常破旧。 用户越来越希望能够使用第三方站点登录,由于安全威胁的增加,在您自己的服务器上管理用户凭据的动机也越来越少。 Okta是一家提供服务的软件即服务身份和访问管理公司。 在本部分中,您将使用它们来使用OAuth 2.0和OIDC(OpenID Connect)快速实现登录表单。
非常非常简短:OAuth 2.0是一种行业标准的授权协议,而OIDC是OAuth之上的另一个开放标准,它添加了一个身份层(身份验证)。 它们共同为程序提供了一种结构化的方式来管理身份验证和授权,以及跨网络和Internet进行通信。 但是,OAuth和OIDC均未提供实现。 它们只是规格或协议。 这就是Okta的用处。Okta实现了OAuth 2.0和OIDC规范的实现,该规范允许程序使用其服务来快速提供登录,注册和单点登录(或社交登录)服务。 在本教程中,您将仅实现登录功能,但是在本教程的最后,您可以找到指向其他资源的链接,以向您展示如何实现社交登录和注册。
首先,注册一个免费的Okta Developer帐户: https : //developer.okta.com/signup/ 。
如果这是您首次登录或刚刚注册,则可能需要单击Admin按钮才能进入开发人员控制台。
接下来,您需要配置OIDC应用程序。
在Okta开发人员仪表板的顶部菜单中,单击应用程序 。
![](https://i-blog.csdnimg.cn/blog_migrate/b345d01f74929281c5caa5bee726f4be.png)
- 单击绿色的“ 添加应用程序”按钮
- 单击“ Web应用程序类型”,然后单击“下一步”。
- 为应用命名。 任何名字。
- 将登录重定向URI设置为
http://localhost:8080/login/oauth2/code/okta
- 单击完成 。
![](https://i-blog.csdnimg.cn/blog_migrate/250c714e85e9da33a907258e1f7245eb.png)
记下页面底部的客户端ID和客户端密钥 。 在下一部分中,您将需要这些。
在Okta方面就是这样。
现在,您需要配置Spring Boot以将Okta用作OAuth 2.0提供程序。
为OAuth 2.0配置Spring Boot应用
使用OAuth 2.0和Okta使Spring Boot变得非常容易。 第一步是添加Okta Spring Boot Starter依赖项。 无需使用我们的启动程序就可以完全使用Okta OAuth 2.0 / OIDC; 但是,启动器简化了配置。 它还处理从JSON Web令牌中提取组声明,并将其转换为Spring Security授权(稍后会介绍)。 查看Okta Spring Boot Starter GitHub页面以获取更多信息。
更新build.gradle
文件的依赖项部分:
dependencies {
implementation 'com.okta.spring:okta-spring-boot-starter:1.2.1' // <-- ADDED
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'
}
在src/main/resources
目录中,有一个application.properties
文件。 将其重命名为application.yml
。 添加以下内容:
okta:
oauth2:
issuer: https://{yourOktaDomain}/oauth2/default
client-id: {yourClientId}
client-secret: {yourClientSecret}
不要忘记更新client-id , client-secret和issuer值以匹配Okta开发人员帐户和OIDC应用程序中的值。 Okta发行者的外观应类似于https://dev-123456.okta.com/oauth2/default
。
最后,更新SecurityConfiguration.java
文件:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(final HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and().oauth2Login(); // <-- THIS WAS CHANGED
}
}
请注意,您在这里真正进行的更改只是从formLogin()
到oauth2Login()
。
运行应用程序: ./gradlew bootRun
(您可能需要退出Okta开发人员仪表板,或使用隐身窗口查看登录屏幕)。
/
端点仍然打开,但是当您访问受限端点时: http://localhost:8080/restricted
。
您将看到Okta登录屏幕。
![](https://i-blog.csdnimg.cn/blog_migrate/8b1ce3242bc3ae6500ec2bb71fe8b5b7.png)
使用您的Okta凭据登录,即可通过身份验证!
检查OAuth 2.0用户属性
开发OAuth应用程序时,我发现能够检查Spring Boot关于客户端和已认证用户的信息会很有帮助。 为此,添加一个名为UserInfoController.java
的新控制器。
src/main/java/com/okta/preauthorize/application/UserInfoController.java
package com.okta.preauthorize.application;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Map;
@RequestMapping("/user")
@Controller
public class UserInfoController {
@RequestMapping("/oauthinfo")
@ResponseBody
public String oauthUserInfo(@RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient,
@AuthenticationPrincipal OAuth2User oauth2User) {
return
"User Name: " + oauth2User.getName() + "<br/>" +
"User Authorities: " + oauth2User.getAuthorities() + "<br/>" +
"Client Name: " + authorizedClient.getClientRegistration().getClientName() + "<br/>" +
this.prettyPrintAttributes(oauth2User.getAttributes());
}
private String prettyPrintAttributes(Map<String, Object> attributes) {
String acc = "User Attributes: <br/><div style='padding-left:20px'>";
for (String key : attributes.keySet()){
Object value = attributes.get(key);
acc += "<div>"+key + ": " + value.toString() + "</div>";
}
return acc + "</div>";
}
}
请注意,该类定义了全类的请求映射路径/user
,从而形成了实际的oauthUserInfo()
端点/user/oauthinfo
。
第二种方法prettyPrintAttributes()
只是一些用来格式化用户属性的糖,使它们更具可读性。
运行应用程序: ./gradlew bootRun
。
导航到http://localhost:8080/user/oauthinfo
。
您会看到以下内容:
User Name: 00ab834zk7eJ18e8Y0h7
User Authorities: [ROLE_USER, SCOPE_email, SCOPE_openid, SCOPE_profile]
Client Name: Okta
User Attributes:
at_hash: 1yq0lbHDupcb8AhBNShkeQ
sub: 00ue9mlzk7eW24e8Y0h7
zoneinfo: America/Los_Angeles
ver: 1
email_verified: true
amr: ["pwd"]
iss: https://dev-123456.oktapreview.com/oauth2/default
preferred_username: andrew.hughes@mail.com
locale: en-US
given_name: Andrew
aud: [0oakz4teswoV7sDZI0h7]
updated_at: 1558380884
idp: 00oe9mlzh0xuqOT5z0h7
auth_time: 1558454889
name: Andrew Hughes
exp: 2019-05-21T17:46:28Z
family_name: Hughes
iat: 2019-05-21T16:46:28Z
email: andrew.hughes@mail.com
jti: ID.CnwVJ_h1Dq5unqkwherWyf8ZFTETX_X4TP39ythQ-ZE
我想指出几件事。 首先,请注意, 用户名实际上是Okta的OIDC应用程序的客户端ID。 这是因为,从Spring Boot OAuth的角度来看,客户端应用程序是“用户”。 要找到实际的用户名和信息,您必须在User Attributes下查看 。 请记住,这些属性的实际内容在OAuth提供程序之间会有所不同,因此,例如,如果您支持Okta,GitHub和Twitter,那么您将需要检查每个OAuth提供程序的这些属性以查看它们返回的内容。
另一个要点是用户权限 。 如Spring在这里所使用的,授权机构是授权信息的元术语。 它们只是字符串。 它们的值是从OAuth / OIDC信息中提取的。 客户端应用程序可以正确使用它们。 它们可能是角色,作用域,组等。就OAuth / OIDC而言,它们的用法基本上是任意的。
要查看其工作原理,在接下来的几节中,您将在Okta中添加一个Admin组,将用户分配给该组,然后使用@PreAuthorize
批注将方法限制为Admin组。
ROLE_USER :您会注意到,Spring已为所有经过身份验证的用户分配了ROLE_USER
。 这是自动分配的默认最低级别角色。
SCOPE_ :还应注意,OAuth范围已映射到Spring授权,并且可以用于授权,例如,在@PreAuthorize
和@PostAuthorize
批注中,正如您将在@PostAuthorize
秒钟后看到的那样。
激活对Okta的团体声明
Okta默认情况下不将组声明包括在JSON Web令牌(JWT)中。 JWT是Okta用于将身份验证和授权信息传达到客户端应用程序的工具。 在本文末尾链接的其他一些博客文章中,可以找到更深入的了解。
要将Okta配置为添加组声明,请转到Okta开发人员仪表板。
在顶部菜单中,转到API并选择授权服务器 。
选择默认授权服务器。
单击“ 索赔”选项卡。
![](https://i-blog.csdnimg.cn/blog_migrate/f4e2f3afebb8b80b1067fd9ec7f236a7.png)
您将创建两个索赔映射。 您本身并不是在创建两个声明,而是指示Okta将组声明添加到访问令牌和ID令牌中。 您需要执行此操作,因为根据OAuth流的不同,可能会从任一组中提取组声明。 在我们的案例中,对于OIDC流程而言,实际上实际上是ID令牌,但是最好将它们同时添加到两者中,以免日后沮丧。 资源服务器流要求组声明位于访问令牌中。
首先,为令牌类型Access Token添加一个声明映射。
点击添加声明 。
更新以下值(其他默认值也可以):
- 名称:团体
- 包含在令牌类型中 :访问令牌
- 值类型:组
- 过滤器:匹配正则表达式
.*
![](https://i-blog.csdnimg.cn/blog_migrate/e6670a993e678685ce5b8c912a79e606.png)
其次,为令牌类型ID Token添加第二个声明映射。
点击添加声明 。
更新以下值(除了令牌类型外,与上面的值相同):
- 名称:团体
- 包含在令牌类型中 :ID令牌
- 值类型:组
- 过滤器:匹配正则表达式
.*
大! 因此,现在Okta会将其所有组映射到有关访问令牌和ID令牌的groups
声明。
在Spring,这些团体声称发生的事情不一定是显而易见的,也不是自动的。 Spring Boot启动程序的好处之一是,它会自动从JWT中提取组声明并将其映射到Spring授权机构。 否则,您将需要实现自己的GrantedAuthoritiesExtractor
。
仅供参考:可以使用application.yml
文件中的okta.oauth2.groupsClaim
字段配置组声明的名称。 它默认为groups
。
使用组检查用户属性
运行应用程序: ./gradlew bootRun
。
导航到http://localhost:8080/user/oauthinfo
。
您将看到类似这样的内容(为清楚起见,省略了许多冗余行):
User Name: 00ab834zk7eJ18e8Y0h7
User Authorities: [Everyone, ROLE_USER, SCOPE_email, SCOPE_openid, SCOPE_profile]
Client Name: Okta
User Attributes:
...
groups: ["Everyone"]
...
注意一个新的组用户属性(映射的组声明)。 值Everyone
是映射到Everyone
的默认组。 这被映射到用户权限Everyone
。
这是基本思想。 添加管理员组后,下一步将更加令人兴奋。
在Okta中创建一个管理员组
现在,您想在Okta上添加一个管理员组。 登录到Okta开发人员仪表板。
在顶部菜单中,转到“ 用户”,然后选择“ 组” 。
单击添加组 。
在弹出窗口中:
- 将组命名为“ Admin”。
- 描述可以是您喜欢的任何内容。
- 单击添加组 。
至此,您已经创建了Admin组, 但实际上尚未分配任何人!
使用方法级授权来限制端点
在WebController
类中,更新/restricted
终结点方法。 您将在方法中添加以下注释:
@PreAuthorize("hasAuthority('Admin')")
像这样:
@PreAuthorize("hasAuthority('Admin')")
@RequestMapping("/restricted")
@ResponseBody
public String restricted() {
return "You found the secret lair!";
}
这告诉Spring检查经过身份验证的用户是否具有Admin
权限,如果没有,则拒绝该请求。
运行应用程序: ./gradlew bootRun
。
导航到http://localhost:8080/restricted
。
您将收到403 /未经授权的白页错误。
将您的用户添加到管理员组
现在,您需要将Okta用户添加到Admin组。 从顶部菜单中,选择“ 用户” ,然后单击“ 组” 。 单击管理员组。 单击添加成员 。 在弹出窗口中搜索您的用户,然后单击添加 。
测试管理员组成员身份
做完了! 让我们看看这样做了。 再次运行Spring Boot应用程序: ./gradlew bootRun
。
导航到http://localhost/user/oauthinfo
。
这次,您将看到Admin
组和权限。 (您可能需要一个新的浏览器会话才能看到更改,或使用隐身模式。)
User Name: 00ab834zk7eJ18e8Y0h7
User Authorities: [Admin, Everyone, ROLE_USER, SCOPE_email, SCOPE_openid, SCOPE_profile]
Client Name: Okta
User Attributes:
...
groups: ["Everyone", "Admin"]
...
并且,如果您导航到http://localhost:8080/restricted
,则将被允许访问。
比较Spring Security角色和权限
最初让我感到困惑的一件事是hasRole()
与hasAuthority()
。
Spring中的角色是具有ROLE_
前缀的授权机构(就像Spring中的所有其他事物一样,该前缀是可配置的)。 考虑这种情况的一种方法是,角色应用于大量权限,而权限可用于更精细的控制。 但是,这只是一种可能的用法。 实际的实现取决于开发人员。 在本教程中,您实际上是在使用权限映射到授权组。
要记住的重要一点是,如果要使用hasRole()
,则需要声明中的授权名称以ROLE_
。 例如,如果您添加了一个ROLE_ADMIN
组,并将您的用户添加到该组,并将该组添加到OIDC应用程序,则可以使用hasRole('ADMIN')
。
基于OAuth 2.0范围的授权和Spring PreAuthorize
您还可以使用@PreAuthorize
批注来限制基于OAuth范围的访问。 从OAuth 2.0范围文档中 :
范围是OAuth 2.0中的一种机制,用于限制应用程序对用户帐户的访问。 一个应用程序可以请求一个或多个范围,然后在同意屏幕中将此信息呈现给用户,并且颁发给该应用程序的访问令牌将限于所授予的范围。
如果查看从/user/oauthinfo
端点返回的经过检查的User Authorities
,则会看到三个以SCOPE_
开头的SCOPE_
:
- SCOPE_email
- SCOPE_openid
- SCOPE_profile
这些对应于电子邮件,openid和配置文件范围。 要将方法限制为具有特定范围的用户,应使用诸如: @PreAuthorize("hasAuthority('SCOPE_email')")
的注释。
我还将指出,您可以使用SecurityConfig
类中的HttpSecurity
完成完全一样的事情(或多或少完全一样):
protected void configure(final HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/restricted").hasAuthority("SCOPE_custom") // <- LOOK AT ME!
.anyRequest().authenticated()
.and().oauth2Login();
}
您可以通过将scopes
属性添加到application.yml
文件来定制客户端应用程序从Okta授权服务器请求的scopes
。 例如,在下面,我将application.yml
文件设置为仅请求openid
范围,这对于OAuth是必需的。
okta:
oauth2:
...
scopes: openid
...
如果在/user/oauthinfo
端点上运行此请求,则会得到以下信息:
User Name: 00ab834zk7eJ18e8Y0h7
User Authorities: [Admin, Everyone, ROLE_USER, SCOPE_openid]
Client Name: Okta
...
请注意,只有一个范围已分配给用户权限。
尝试添加自定义范围。 更改application.yml
文件中的okta.oauth2.scopes
属性以使其匹配:
okta:
oauth2:
...
scopes: openid email profile custom
...
在运行应用程序并尝试之前,您需要将自定义范围添加到Okta授权服务器(如果现在运行它,将会出现错误)。
打开您的Okta开发人员仪表板。
在顶部菜单中,转到API并选择授权服务器 。
选择默认授权服务器。
单击“ 作用域”选项卡。
![](https://i-blog.csdnimg.cn/blog_migrate/79a7159667f1283c58192b04c4f0bb29.png)
单击添加范围按钮。
- 名称 :
custom
- 描述 :
Custom test scope
点击创建 。
您刚刚向默认的Okta授权服务器添加了一个自定义范围(俗称为custom
)。
最后一次,运行Spring Boot应用程序: ./gradlew bootRun
。
导航到http://localhost:8080/user/oauthinfo
。
User Name: 00ab834zk7eJ18e8Y0h7
User Authorities: [Admin, Everyone, ROLE_USER, SCOPE_custom, SCOPE_email, SCOPE_openid, SCOPE_profile]
Client Name: Okta
...
成功! 您应该在用户权限中看到新的SCOPE_custom
。
Spring Boot中的Spring PreAuthorize,HttpSecurity和Security
您覆盖了一大堆土地! 您使用@PreAuthorize
很好地了解了Spring方法级别的安全性,并了解了它与HttpSecurity
。 您使用了一些基本的SpEL(Spring表达式语言)语句来配置授权。 您检查了授权和身份验证之间的区别。 您将Spring Boot配置为使用Okta作为OAuth 2.0 / OIDC单一登录提供程序,并向身份验证服务器和客户端应用添加了组声明。 您甚至还创建了一个新的Admin组,并了解了如何使用映射到Spring授权的组声明来限制访问。 最后,您了解了OAuth 2.0范围如何用于定义授权方案并在应用程序中实现它们。
下一站:火箭科学!
如果您想查看这个完整的项目,可以在GithHub上找到该仓库 。
如果您想了解有关Spring Boot,Spring Security或安全用户管理的更多信息,请查看以下任何出色的教程:
- Spring Boot,OAuth 2.0和Okta入门
- 15分钟内将单一登录添加到您的Spring Boot Web App
- 使用多重身份验证保护您的Spring Boot应用程序安全
- 使用Spring Boot和GraphQL构建安全的API
如果您想更深入地学习,请查看Okta Spring Boot Starter GitHub Project 。
如果您对此帖子有任何疑问,请在下面添加评论。 有关更多精彩内容, 请在Twitter上关注@oktadev , 在Facebook上关注我们,或订阅我们的YouTube频道 。
“带有PreAuthorize的Spring方法安全性”最初于2019年6月20日发布在Okta Developer博客上。
朋友不允许朋友写用户身份验证。 厌倦了管理自己的用户? 立即尝试Okta的API和Java SDK。 在几分钟之内即可对任何应用程序中的用户进行身份验证,管理和保护。
翻译自: https://www.javacodegeeks.com/2019/09/spring-method-security-preauthorize.html