Never Build Auth Again – 喜欢构建用户管理?借助 Okta,您可以在几分钟内为您的应用程序添加社交登录、多重身份验证和 OpenID Connect 支持。 立即创建一个免费的开发者帐户。
在构建 Java 应用程序时,用户管理是一个重要的考虑因素。应用程序和 API 通常会根据分配给用户的角色来划分对应用程序不同部分的访问权限——这就是基于角色的访问控制 (RBAC)。
这就是 Okta 的用武之地——Okta 通过管理组内的角色(用户可以属于一个或多个组)简化了这一点。借助 Okta 的 Spring Security 集成,通过使用将组映射到特定角色并允许或拒绝访问的通用注释,此过程变得自动化。这是使用常见的 Spring Security 注释完成的,我们在下面概述了这些注释。
为了在实践中展示这一点,我在下面整理了一个演示,演示了一个简单但常见的场景。对于我们的示例,我们将查看未受保护的页面、只有经过身份验证的用户才能访问的页面以及要求用户在访问之前具有额外授权级别的页面。
我们在此处添加了我们引用的所有代码。
如果您已经打算深入研究并开始为您的 Java 应用程序使用内置 RBAC,只需在此处将您的 Okta 租户连接到 Spring Boot 应用程序。
如果您有任何疑问,请在此处联系 Okta 的开发支持团队。
第一步是设置您的 Okta 租户。这样,您就可以启动我们创建的示例应用程序并查看实际效果。在此处注册开发者帐户,然后按照以下步骤操作:
admins
组users
组users
组的用户让我们来看看这是什么样子的:
在您的 Okta 管理仪表板菜单中,找到 Users
,然后单击 Groups
。单击 Add Group
并在 admins
字段中输入 Name
并添加组描述,例如:Admins。单击 Add Group
完成此步骤。
按照相同的过程添加一个 users
组。
再次导航到 Okta 管理仪表板中的 Users
,但这次单击 People
。单击Add User
并使用用户信息填写表单。 (对主要或次要电子邮件地址使用真实的电子邮件地址——以及您有权访问的电子邮件地址——以便稍后验证电子邮件。)在 Groups
字段中,将此用户添加到users
您之前创建的组。确保您已单击 Send user activation email now
复选框,然后单击“保存并添加另一个”。
重复我们上面列出的步骤,只是这一次,将第二个用户添加到users
和admins
组。
跳转到您的电子邮件并验证这些电子邮件地址。单击两个用户的链接以激活它们。
现在是时候设置您的身份验证层了。
在 Okta 管理仪表板中,单击菜单中的 Applications
,然后单击 Add Application
。
选择 Web
并单击 Next
。
当系统提示您填写表单时,请使用以下值:
| Field | Value | | ------------------- | ------------------------------ | | Name | Fun with Spring Security Roles | | Base URIs | http://localhost:8080/ | | Login redirect URIs | http://localhost:8080/ | | Group assignments | `admins` and `users` | | Grant type allowed | Check: `Implicit` |
准备就绪后,单击“完成”,您将在下面看到结果页面:
在继续下一步之前,向下滚动并记下 Client ID
。您将需要它来配置 Spring Boot 应用程序。
接下来,在 Okta 管理仪表板菜单中查找 API
,然后单击 Authorization Servers
以启动以下内容:
记下 Issuer URI
。稍后您还需要它来配置 Spring Boot 应用程序。
单击 default
并选择 Claims
选项卡。单击 Add Claim
并填写以下字段:
| Field | Value | | --------------------- | ------------ | | Name | groups | | Include in token type | Access Token | | Value type | Groups | | Filter | Regex .* | | Include in | Any scope |
单击 Create
完成此步骤。
创建此 Claim 可确保组成员身份信息在用户进行身份验证时包含在访问令牌中。这一步对于连接到 Spring Security 的角色和权限机制至关重要。
首先,在此链接克隆。
在您喜欢的 IDE 或编辑器中打开项目。以下屏幕截图来自此处。
复制 application.yml.sample
文件并将其命名为 application.yml
还记得我们之前标记为保存的那些值吗?用这些更新你的信息。这是我们的例子:
| Name | Value | | ----------- | ------------------------------------------------- | | baseUrl | https://dev-237330.oktapreview.com | | issuer | https://dev-237330.oktapreview.com/oauth2/default | | audience | api://default | | clientId | 0oacdldhkydGGruON0h7 | | rolesClaim | groups | | redirectUri | http://localhost:8080/ |
在开始编写代码之前,让我们先看看应用程序的运行情况。
从命令行运行此命令以开始使用:
mvn spring-boot:run
导航到主页并单击 Login
。
要登录,请使用属于您在第一步中创建的 Users
组的用户的凭据。从那里,您会看到该应用程序显示您的用户信息并在其下方显示一行按钮。
这些对应于应用程序内的访问权限。单击 users
时,Users Only
组的成员将能够看到该页面。 admins
组的用户也是如此,当点击 Admins Only
时,他们可以看到该页面。
让我们看看这是如何工作的。
单击 Users Only
。您会看到一个页面,显示您是 users
组的成员。单击 Back
,然后单击 Admins Only
。这次,您将获得一个 403 Unauthorized
页面,因为您不是 admins
组的成员。单击 Logout
并再次登录,但这次是作为属于两个组的用户(您在第一步中创建的第二个用户)。单击 Admins Only
。
这一次,您会看到您同时在 admins
和 users
组中。
非常简单!现在让我们进入代码……
本节概述 Okta 组如何链接到 Spring Security 角色。
demo 应用程序使用以下内容:
我们依赖于 okta-spring-security-starter
(来自 pom.xml)。这就是幕后魔术的发生方式:
... <dependency> <groupId>com.okta.spring</groupId> <artifactId>okta-spring-security-starter</artifactId> <version>0.1.0</version> </dependency> ...
让我们从 Javascript Okta 登录小部件开始,看看它如何将客户端连接到 Spring Boot。
以下是我们如何在 `login.html` Thymeleaf 模板中设置 Okta 登录小部件:
$( document ).ready(function() { var data = { baseUrl: [[${appProperties.baseUrl}]], clientId: [[${appProperties.clientId}]], redirectUri: [[${appProperties.redirectUri}]], authParams: { issuer: [[${appProperties.issuer}]], responseType: ['token'] } }; window.oktaSignIn = new OktaSignIn(data); // Check if we already have an access token var token = oktaSignIn.tokenManager.get('token'); if (token) { window.location.href = "/authenticated"; } else { renderWidget(); } });
在第 3-8 行中,您会看到我们已经嵌入了所有连接到我们的 Okta 租户的设置作为内联 Thymeleaf 模板变量。这些值作为模型的一部分从 Spring Boot 控制器传入。通过这样做,您只需指定一次这些设置——现在服务器端和客户端都可以确保它们。我们将在下面详细说明如何管理这些设置(它们都来自 application.yml
文件)。
在我们配置并实例化 Okta 登录小部件后,下一步是检查用户是否已登录。然后我们执行以下两个操作之一。如果有,我们将它们发送到 /authenticated
页面。如果他们还没有,我们将呈现小部件,让用户有机会登录。
这是 renderWidget
函数:
function renderWidget() { oktaSignIn.renderEl( {el: '#okta-login-container'}, function (response) { // check if success if (response.status === 'SUCCESS') { // for our example we have the id token and the access token oktaSignIn.tokenManager.add('token', response[0]); if (!document.location.protocol.startsWith('https')) { console.log( 'WARNING: You are about to pass a bearer token in a cookie over an insecure\n' + 'connection. This should *NEVER* be done in a production environment per\n' + 'https://tools.ietf.org/html/rfc6750' ); } document.cookie = 'access_token=' + oktaSignIn.tokenManager.get('token').accessToken; document.location.href = "/authenticated"; } }, function (err) { // handle any errors console.log(err); } ); }
一旦小部件呈现在页面上,内部逻辑就会在用户登录时根据您的设置接管。在这种情况下,您使用的是 this 流,并且将只取回指定的访问令牌通过配置的 responseType
参数。
成功登录后,您将使用 response
对象输入回调函数。响应对象具有您的(或在本例中为您的用户的)访问令牌。
第 19 行使用访问令牌设置一个 cookie,第 20 行将(现已通过身份验证的)用户发送到 /authenticated
端点。
此时,Spring Security 可以识别经过身份验证的用户。
在我们查看 Spring Security 角色之前,让我们看看 Spring Security 如何处理访问令牌。
默认情况下,Spring Security OAuth 2.0 插件将 Authorization
标头中的访问令牌处理为不记名令牌。这对于为客户端创建 RESTful 响应的应用程序来说很好,例如 Angular 客户端。
对于这个例子,我将架构和 Javascript 的数量保持在最低限度,所以我想要完整的页面转换。这有点老派,但它使示例代码紧凑且小巧。
为了让 Spring Security 识别用户已经通过身份验证,我们需要它能够处理来自 cookie 的令牌。
幸运的是,Spring Security 通过设置 TokenExtractor
可以很容易地覆盖默认行为。下面是从 OktaSpringSecurityRolesExampleApplication
实现这一点的代码:
@Bean protected ResourceServerConfigurerAdapter resourceServerConfigurerAdapter() { return new ResourceServerConfigurerAdapter() { ... @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.tokenExtractor(new TokenExtractor() { @Override public Authentication extract(HttpServletRequest request) { String tokenValue = findCookie(ACCESS_TOKEN_COOKIE_NAME, request.getCookies()); if (tokenValue == null) { return null; } return new PreAuthenticatedAuthenticationToken(tokenValue, ""); } ... }); } }; }
如果可以的话,这样做是从传入请求的 cookie 列表中提取访问令牌。然后自动完成解析和验证。瞧!
在应用程序设置中,您定义哪些路径是开放的。所有其他路径至少需要经过身份验证的会话。
这是 OktaSpringSecurityRolesExampleApplication
的另一段摘录:
@Bean protected ResourceServerConfigurerAdapter resourceServerConfigurerAdapter() { return new ResourceServerConfigurerAdapter() { @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/", "/login", "/images/**").permitAll() .and() .exceptionHandling().accessDeniedHandler(customAccessDeniedHandler); } ... }; }
在这种情况下,您告诉 Spring Security 让任何未经身份验证的用户访问主页 (/
)、登录页面 (/login
) 以及来自静态的任何内容图片文件夹。这意味着默认情况下会自动限制所有其他路径。
此时,您还定义了一个自定义的拒绝访问处理程序。
@Controller public class SecureController { @Autowired protected AppProperties appProperties; @RequestMapping("/authenticated") public String authenticated(Model model) { model.addAttribute("appProperties", appProperties); return "authenticated"; } @RequestMapping("/users") @PreAuthorize("hasAuthority('users')") public String users() { return "roles"; } @RequestMapping("/admins") @PreAuthorize("hasAuthority('admins')") public String admins() { return "roles"; } @RequestMapping("/403") public String error403() { return "403"; } }
在这个控制器中,我们定义了四个路径,它们都至少需要一个经过身份验证的用户。
真正的价值来自 /users
和 /admins
路径。请注意,它们都有 @PreAuthorize
注释。这意味着必须在进入方法之前满足后面的 表达式。 hasAuthority
函数确认经过身份验证的用户是否属于这些角色。在此示例中,这些自动映射到我们之前创建的 Okta 组,这就是为什么在来自 Okta 的访问令牌中包含 groups
声明很重要。
虽然这涉及一些设置,但现在您只需一行代码就可以进行基于角色的访问控制!
在客户端,我们有一组 HTML 页面,采用应用程序本身提供的 Thymeleaf 模板形式。因此,为客户端和服务器所需的配置值提供单一来源是有意义的。
使用 Spring 的 @Component
和 @ConfigurationProperties
注释很容易做到这一点。
这是 AppProperties
类:
@Component @ConfigurationProperties("okta.oauth") public class AppProperties { private String issuer; private String audience; private String clientId; private String rolesClaim; private String baseUrl; private String redirectUri; ... getters and setters ... }
@ConfigurationProperties
告诉 Spring 从 application.yml
文件中提取属于 okta.oauth
键的所有属性。
@Component
注释使 Spring 实例化此对象并使其可用于其他地方的自动装配。
看一下 HomeController
中的这个片段:
@Controller public class HomeController { @Autowired protected AppProperties appProperties; ... @RequestMapping("/login") public String login(Model model) { model.addAttribute("appProperties", appProperties); return "login"; } }
在命中 login
端点时返回 /login
视图之前,将 AppProperties
对象(在第 4 和 5 行自动装配)添加到模型中.
正如您之前看到的,这就是使它可用于 Thymeleaf 模板的原因:
<script th:inline="javascript"> /*<![CDATA[*/ $( document ).ready(function() { var data = { baseUrl: [[${appProperties.baseUrl}]], clientId: [[${appProperties.clientId}]], redirectUri: [[${appProperties.redirectUri}]], authParams: { issuer: [[${appProperties.issuer}]], responseType: ['token'] } }; window.oktaSignIn = new OktaSignIn(data); ... }); ... /*]]>*/ </script>
就是这样!试一试,让我知道进展如何!我在 Twitter 上。
虽然我已经概述了将 Okta 的组机制与 Spring Security 基于角色的访问控制结合使用的好处,但 Okta 的 Java 开发团队也在努力开发我们的下一代 SDK 和集成。请留意即将发布的新 Okta Java Spring Boot 集成版本,它将支持其他 OIDC 工作流,包括 code
以及托管的、可配置的登录和注册视图。
这篇文章改编自这里。
Never Build Auth Again – 喜欢构建用户管理?借助 Okta,您可以在几分钟内为您的应用程序添加社交登录、多重身份验证和 OpenID Connect 支持。 立即创建一个免费的开发者帐户。
标签2: Java教程地址:https://www.cundage.com/article/jcg-secure-java-app-spring-security-thymeleaf-okta.html