使用 Spring Security、Thymeleaf 和 Okta 保护您的 Java 应用

位置:首页>文章>详情   分类: Java教程 > 编程技术   阅读(136)   2024-03-28 06:21:39

Never Build Auth Again – 喜欢构建用户管理?借助 Okta,您可以在几分钟内为您的应用程序添加社交登录、多重身份验证和 OpenID Connect 支持。 立即创建一个免费的开发者帐户。

在构建 Java 应用程序时,用户管理是一个重要的考虑因素。应用程序和 API 通常会根据分配给用户的角色来划分对应用程序不同部分的访问权限——这就是基于角色的访问控制 (RBAC)。

这就是 Okta 的用武之地——Okta 通过管理组内的角色(用户可以属于一个或多个组)简化了这一点。借助 Okta 的 Spring Security 集成,通过使用将组映射到特定角色并允许或拒绝访问的通用注释,此过程变得自动化。这是使用常见的 Spring Security 注释完成的,我们在下面概述了这些注释。

为了在实践中展示这一点,我在下面整理了一个演示,演示了一个简单但常见的场景。对于我们的示例,我们将查看未受保护的页面、只有经过身份验证的用户才能访问的页面以及要求用户在访问之前具有额外授权级别的页面。

我们在此处添加了我们引用的所有代码。

如果您已经打算深入研究并开始为您的 Java 应用程序使用内置 RBAC,只需在此处将您的 Okta 租户连接到 Spring Boot 应用程序。

如果您有任何疑问,请在此处联系 Okta 的开发支持团队。

第一步:配置您的 Okta 帐户

第一步是设置您的 Okta 租户。这样,您就可以启动我们创建的示例应用程序并查看实际效果。在此处注册开发者帐户,然后按照以下步骤操作:

  1. 创建一个admins
  2. 创建一个users
  3. 创建一个属于users组的用户
  4. 创建属于两个组的第二个用户
  5. 创建 OpenID Connect (OIDC) 应用程序
  6. 将这两个组添加到应用程序中
  7. 配置默认授权服务器以在访问令牌中包含组成员资格

让我们来看看这是什么样子的:

设置群组

在您的 Okta 管理仪表板菜单中,找到 Users,然后单击 Groups。单击 Add Group 并在 admins 字段中输入 Name 并添加组描述,例如:Admins。单击 Add Group 完成此步骤。

按照相同的过程添加一个 users 组。

设置用户

再次导航到 Okta 管理仪表板中的 Users,但这次单击 People。单击Add User 并使用用户信息填写表单。 (对主要或次要电子邮件地址使用真实的电子邮件地址——以及您有权访问的电子邮件地址——以便稍后验证电子邮件。)在 Groups 字段中,将此用户添加到users 您之前创建的组。确保您已单击 Send user activation email now 复选框,然后单击“保存并添加另一个”。

重复我们上面列出的步骤,只是这一次,将第二个用户添加到usersadmins 组。

跳转到您的电子邮件并验证这些电子邮件地址。单击两个用户的链接以激活它们。

创建 OIDC 应用程序

现在是时候设置您的身份验证层了。

在 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`              |

准备就绪后,单击“完成”,您将在下面看到结果页面:

提示 指定的 URI 是 Spring Boot 默认值。您可以稍后轻松更改这些。

在继续下一步之前,向下滚动并记下 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 的角色和权限机制至关重要。

第二步:配置您的 Spring Boot 应用程序

首先,在此链接克隆。

在您喜欢的 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

这一次,您会看到您同时在 adminsusers 组中。

非常简单!现在让我们进入代码……

第三步:Spring Security Code Review

本节概述 Okta 组如何链接到 Spring Security 角色。

demo 应用程序使用以下内容:

  1. Spring启动
  2. Spring安全
  3. Spring Security OAuth2
  4. Okta Spring 安全入门
  5. Thymeleaf 模板
  6. 用于 Spring Security 4 的 Thymeleaf Extras
  7. Okta 登录小部件

我们依赖于 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。

设置 Okta 登录小部件

以下是我们如何在 `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 令牌提取器

默认情况下,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

相关阅读

Java HashSet 教程展示了如何使用 Java HashSet 集合。 Java哈希集 HashSet 是一个不包含重复元素的集合。此类为基本操作(添加、删除、包含和大小)提供恒定时间性...
SpringApplicationBuilder 教程展示了如何使用 SpringApplicationBuilder 创建一个简单的 Spring Boot 应用程序。 春天 是用于创建企业应...
通道是继 buffers 之后 java.nio 的第二个主要新增内容,我们在之前的教程中已经详细了解了这一点。通道提供与 I/O 服务的直接连接。 通道是一种在字节缓冲区和通道另一端的实体(通...
课程大纲 Elasticsearch 是一个基于 Lucene 的搜索引擎。它提供了一个分布式的、支持多租户的全文搜索引擎,带有 HTTP Web 界面和无模式的 JSON 文档。 Elasti...
解析器是强大的工具,使用 ANTLR 可以编写可用于多种不同语言的各种解析器。 在这个完整的教程中,我们将: 解释基础:什么是解析器,它可以用来做什么 查看如何设置 ANTLR 以便在 Java...
Java 是用于开发各种桌面应用程序、Web 应用程序和移动应用程序的最流行的编程语言之一。以下文章将帮助您快速熟悉 Java 语言,并迈向 API 和云开发等更复杂的概念。 1. Java语言...
Java中的继承是指子类继承或获取父类的所有非私有属性和行为的能力。继承是面向对象编程的四大支柱之一,用于提高层次结构中类之间的代码可重用性。 在本教程中,我们将了解 Java 支持的继承类型,...
Java Message Service 是一种支持正式通信的 API,称为 网络上计算机之间的消息传递。 JMS 为支持 Java 程序的标准消息协议和消息服务提供了一个通用接口。 JMS 提...
Java 项目中的一项常见任务是将日期格式化或解析为字符串,反之亦然。解析日期意味着你有一个代表日期的字符串,例如“2017-08-3”,你想把它转换成一个代表 Java 中日期的对象,例如Ja...
之前,我介绍了spring 3 + hibernate 集成 示例和struts 2 hello world 示例。在本教程中,我将讨论在将 spring 框架与 struts 与 hibern...