当前位置:Java -> 使用Spring Boot和Spring Security构建OAuth 2.0授权服务器

使用Spring Boot和Spring Security构建OAuth 2.0授权服务器

大多数Web应用都需要用户管理,但构建用户管理系统并不总是一件容易的任务。许多开发人员为了确保应用程序的安全性而不断努力,寻找并修补个别的漏洞。幸运的是,你可以通过使用Spring Security和Spring Boot实现OAuth 2.0来提高自己的效率。通过与Okta集成,整个过程变得更加简单。

在这个教程中,你将首先使用Spring Boot和Spring Security构建一个OAuth 2.0 Web应用和认证服务器。

OAuth 2.0是一种开放标准的授权协议,可以安全地委托访问Web上的资源。它允许用户对其资源(例如个人资料或数据)授予有限访问权限给第三方应用程序,而不分享他们的凭据。OAuth 2.0被广泛应用于现代Web和移动应用程序的认证和授权。

Spring Boot是一个基于Java的开源框架,简化了构建、部署和管理可投入生产的应用程序的过程。它采用约定大于配置的方式,最大程度地减少了样板代码和配置的需求,使开发人员能够专注于编写业务逻辑。Spring Boot具有内嵌服务器和各种内置工具,可以轻松创建独立的、生产级别的基于Spring的应用程序。之前,DZone已经介绍了如何使用Keycloak保护Spring Boot应用程序

Spring Security是一个强大且可定制的Java应用程序认证和访问控制框架,特别适用于基于Spring框架构建的应用程序。它为基于Java EE的企业软件应用程序提供了全面的安全服务。使用Spring Security,开发人员可以轻松地将认证和授权机制集成到他们的应用程序中,保护资源并确保安全交互。该框架支持各种身份验证提供程序,包括LDAP、OAuth和自定义实现,使其适应各种安全需求。

一旦我们使用Spring Boot和Spring Security保护了我们的应用程序,我们将使用Okta摆脱自己搭建的认证服务器,并简化应用程序。

让我们开始吧!

创建OAuth 2.0服务器

首先打开Spring Initializr,用以下设置创建一个新项目:

  • 将项目类型从Maven更改为Gradle
  • 将Group更改为com.okta.spring
  • 将Artifact更改为AuthorizationServerApplication
  • 添加一个依赖项:Web

Spring Initializr

下载并将项目复制到硬盘的合适位置。在这个教程中,你将创建三个不同的项目,所以最好在硬盘上创建一个父目录,比如SpringBootOAuth

你需要在build.gradle文件中添加一个依赖项:

implementation 'org.springframework.security.oauth:spring-security-oauth2:2.3.3.RELEASE'


这样就添加了Spring的OAuth功能。

更新src/main/resources/application.properties如下:

server.port=8081
server.servlet.context-path=/auth
user.oauth.clientId=R2dpxQ3vPrtfgF72
user.oauth.clientSecret=fDw7Mpkk5czHNuSRtmhGmAGL42CaxQB9
user.oauth.redirectUris=http://localhost:8082/login/oauth2/code/
user.oauth.user.username=Andrew
user.oauth.user.password=abcd


这里设置了服务器端口、servlet上下文路径,以及对Web客户端返回的内存中自动生成的令牌和用户的用户名和密码的一些默认值。在实际的生产环境中,你需要更为复杂的后端来构建一个真正的认证服务器,而不是使用硬编码的重定向URI、用户名和密码。

更新AuthorizationServerApplication类,添加@EnableResourceServer

package com.okta.spring.AuthorizationServerApplication;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;

@SpringBootApplication
@EnableResourceServer
public class AuthorizationServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(AuthorizationServerApplication.class, args);
    }
}


src/main/java中同一包下创建一个名为AuthServerConfig的类,就在你的应用程序类com.okta.spring.AuthorizationServerApplication下面(从现在开始,请在src/main/java/com/okta/spring/AuthorizationServerApplication下创建Java类)。这个Spring配置类启用并配置了一个OAuth授权服务器。

package com.okta.spring.AuthorizationServerApplication;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;

@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {

    @Value("${user.oauth.clientId}")
    private String ClientID;
    @Value("${user.oauth.clientSecret}")
    private String ClientSecret;
    @Value("${user.oauth.redirectUris}")
    private String RedirectURLs;

   private final PasswordEncoder passwordEncoder;

    public AuthServerConfig(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public void configure(
        AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.tokenKeyAccess("permitAll()")
            .checkTokenAccess("isAuthenticated()");
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
            .withClient(ClientID)
            .secret(passwordEncoder.encode(ClientSecret))
            .authorizedGrantTypes("authorization_code")
            .scopes("user_info")
            .autoApprove(true)
            .redirectUris(RedirectURLs);
    }
}


AuthServerConfig类是一个在客户端正确验证时创建和返回JSON Web令牌的类。

创建一个名为SecurityConfiguration的Java类:

package com.okta.spring.AuthorizationServerApplication;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@Order(1)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Value("${user.oauth.user.username}")
    private String username;
    @Value("${user.oauth.user.password}")
    private String password;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requestMatchers()
            .antMatchers("/login", "/oauth/authorize")
            .and()
            .authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin().permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser(username)
            .password(passwordEncoder().encode(password))
            .roles("USER");
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}


SecurityConfiguration类是真正验证请求到你的授权服务器的类。请注意,它是从application.properties文件获取用户名和密码的。

最后,创建一个名为UserController的Java类:

package com.okta.spring.AuthorizationServerApplication;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;

@RestController
public class UserController {

    @GetMapping("/user/me")
    public Principal user(Principal principal) {
        return principal;
    }
}


这个文件允许客户端应用程序更多地了解使用服务器进行身份验证的用户。

这就是你的授权服务器!操作不算太复杂。Spring Boot使它变得相当容易。只需四个文件和一些属性。稍后,你将使用Okta进一步简化操作,但此刻,继续创建一个客户端应用程序来测试授权服务器。

启动授权服务器:

./gradlew bootRun


等待一会儿,直到运行结束。终端应该会显示类似于下面的内容:

...
2019-02-23 19:06:49.122  INFO 54333 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8081 (http) with context path '/auth  '
2019-02-23 19:06:49.128  INFO 54333 --- [           main] c.o.s.A.AuthorizationServerApplication   : Started AuthorizationServerApplication in 3.502 seconds (JVM running for 3.945)


注意:如果你遇到有关JAXB(java.lang.ClassNotFoundException: javax.xml.bind.JAXBException)的错误,那是因为你在使用Java 11。要解决这个问题,将JAXB添加到你的build.gradle中。

implementation 'org.glassfish.jaxb:jaxb-runtime'


构建你的客户端应用程序

回到Spring Initializr,用以下设置创建一个新项目:

  • 项目类型应该选择Gradle(而不是Maven)。
  • Group:com.okta.spring
  • Artifact:SpringBootOAuthClient
  • 添加三个依赖项:Web, ThymeleafOAuth2 Client

Create Client App

下载项目,将其复制到最终位置并解压缩。

这次你需要将以下依赖项添加到你的build.gradle文件中:

implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.0.4.RELEASE'


src/main/resources/application.properties重命名为application.yml并更新以匹配下面的YAML:

server:
  port: 8082
  session:
    cookie:
      name: UISESSION
spring:
  thymeleaf:
    cache: false
  security:
    oauth2:
      client:
        registration:
          custom-client:
            client-id: R2dpxQ3vPrtfgF72
            client-secret: fDw7Mpkk5czHNuSRtmhGmAGL42CaxQB9
            client-name: Auth Server
            scope: user_info
            provider: custom-provider
            redirect-uri-template: http://localhost:8082/login/oauth2/code/
            client-authentication-method: basic
            authorization-grant-type: authorization_code
        provider:
          custom-provider:
            token-uri: http://localhost:8081/auth/oauth/token
            authorization-uri: http://localhost:8081/auth/oauth/authorize
            user-info-uri: http://localhost:8081/auth/user/me
            user-name-attribute: name


请注意,这里你正在配置clientIdclientSecret,以及您的身份验证服务器的各种URI。这些需要与其他项目中的值匹配。

更新SpringBootOAuthClientApplication类以匹配:

package com.okta.spring.SpringBootOAuthClient;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootOAuthClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootOAuthClientApplication.class, args);
    }
}


创建一个名为WebController的新Java类:

package com.okta.spring.SpringBootOAuthClient;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.security.Principal;

@Controller
public class WebController {

    @RequestMapping("/securedPage")
    public String securedPage(Model model, Principal principal) {
        return "securedPage";
    }

    @RequestMapping("/")
    public String index(Model model, Principal principal) {
        return "index";
    }
}


这是将传入请求映射到您的Thymeleaf模板文件的控制器(稍后将创建)。

创建另一个名为SecurityConfiguration的Java类:

package com.okta.spring.SpringBootOAuthClient;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/**").authorizeRequests()
            .antMatchers("/", "/login**").permitAll()
            .anyRequest().authenticated()
            .and()
            .oauth2Login();
    }
}


这个类为您的应用定义了Spring Security配置:允许主页路径上的所有请求,并要求所有其他路由进行身份验证。它还设置了Spring Boot OAuth登录流程。

您需要添加的最后两个文件是两个Thymeleaf模板文件。完整的Thymeleaf模板的使用远超出了本教程的范围,但您可以查看它们的网站获取更多信息。

这些模板放在src/main/resources/templates目录中。您会注意到在上面的控制器中,它们只是简单地返回路径的字符串。当构建中包含Thymeleaf依赖项时,Spring Boot会自动假定您从控制器中返回的是模板文件的名称,因此应用程序会在src/main/resources/templates中查找一个文件名为返回的字符串加上.html的文件。

创建主页模板:src/main/resources/templates/index.html

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>Home</title>  
</head>  
<body>  
    <h1>Spring Security SSO</h1>  
    <a href="securedPage">Login</a>  
</body>  
</html>


和受保护的模板:src/main/resources/templates/securedPage.html

<!DOCTYPE html>  
<html xmlns:th="http://www.thymeleaf.org">  
<head>  
    <meta charset="UTF-8">  
    <title>Secured Page</title>  
</head>  
<body>  
    <h1>Secured Page</h1>  
    <span th:text="${#authentication.name}"></span>  
</body>  
</html>


我只想指出这一行:

<span th:text="${#authentication.name}"></span>  


这是将插入经过身份验证的用户的名称的行。这一行是为什么您需要在build.gradle文件中添加org.thymeleaf.extras:thymeleaf-extras-springsecurity5依赖项的原因。

启动客户端应用程序:

./gradlew bootRun


等待片刻以完成。终端应该以类似于以下内容结束:

...
2019-02-23 19:29:04.448  INFO 54893 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8082 (http) with context path ''
2019-02-23 19:29:04.453  INFO 54893 --- [           main] c.o.s.S.SpringBootOAuthClientApplication : Started SpringBootOAuthClientApplication in 3.911 seconds (JVM running for 4.403)


测试资源服务器

使用您选择的浏览器导航到您的客户端应用程序,地址为http://localhost:8082/

点击登录链接。

您将被重定向到登录页面:

登录表单

输入用户名Andrew和密码abcd(来自身份验证服务器的application.properties文件)。

点击登录,您将进入非常漂亮的securedPage.html模板,应该显示“Secured Page”和“Andrew”。

太棒了! 它起作用了。现在你要让它变得更简单。

您可以停止服务器和客户端的Spring Boot应用程序。了解有关如何使用Spring Boot应用程序与PostgreSQL的设置更多信息。

创建一个 OpenID Connect 应用

Okta是一个SaaS(软件即服务)身份验证和授权提供商。我们为开发人员提供免费账户,以便他们可以轻松开发无OIDC应用。转到Okta Developer页面,注册一个账户。确认您的电子邮件后,登录并执行以下步骤:

  • 转到应用 > 添加应用
  • 选择应用程序类型为Web,然后点击下一步
  • 为应用程序命名。我命名为“Spring Boot OAuth”。
  • 登录重定向 URI中更改值为http://localhost:8080/login/oauth2/code/okta。其余默认值将会起作用。
  • 点击完成

保持页面打开,并注意客户端ID客户端秘钥。您马上会需要它们。

创建一个新的 Spring Boot 应用

  • 将项目类型从Maven更改为Gradle
  • 将组更改为com.okta.spring
  • 将项目名更改为OktaOAuthClient
  • 添加三个依赖项:WebThymeleafOkta
  • 点击生成项目

创建Okta OAuth应用

复制项目并解压到某个位置。

build.gradle文件中,添加以下依赖项:

implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.0.4.RELEASE'  


另外,当您在这里时,请注意依赖项 com.okta.spring:okta-spring-boot-starter:1.1.0。这是Okta Spring Boot Starter。这是一个方便的项目,使得将Okta与Spring Boot集成变得简单而容易。要了解更多信息,请查看 项目的 GitHub

src/main/resources/application.properties 改为 application.yml 并添加以下内容:

server:
  port: 8080
okta:
  oauth2:
    issuer: https://okta.okta.com/oauth2/default
    client-id: {yourClientId}
    client-secret: {yourClientSecret}
spring:
  thymeleaf:
    cache: false


还记得我之前说你需要准备好客户端ID客户端密钥吗?现在是时候了。你需要将它们填写在文件中,还有您的Okta签发者URL。它看起来会是这样的:dev-123456.okta.com。您可以在API > 授权服务器下找到它。

您还需要在 src/main/resources/templates 目录中准备两个类似的模板文件。 index.html 模板文件与之前完全相同,如果愿意可以复制过来。由于从Okta返回的身份验证信息与您之前构建的简单身份验证服务器有所不同,securedPage.html 模板文件略有不同。

创建主页模板:src/main/resources/templates/index.html

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>Home</title>  
</head>  
<body>  
    <h1>Spring Security SSO</h1>  
    <a href="securedPage">Login</a>  
</body>  
</html>


以及受保护的模板:src/main/resources/templates/securedPage.html

<!DOCTYPE html>  
<html xmlns:th="http://www.thymeleaf.org">  
<head>  
    <meta charset="UTF-8">  
    <title>Secured Page</title>  
</head>  
<body>  
    <h1>Secured Page</h1>  
    <span th:text="${#authentication.principal.attributes.name}">Joe Coder</span>  
</body>  
</html>


com.okta.spring.SpringBootOAuth 包中创建一个名为 WebController 的Java类:

package com.okta.spring.OktaOAuthClient;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.security.Principal;

@Controller
public class WebController {

    @RequestMapping("/securedPage")
    public String securedPage(Model model, Principal principal) {
        return "securedPage";
    }

    @RequestMapping("/")
    public String index(Model model, Principal principal) {
        return "index";
    }
}


这个类简单地创建了两个路由,一个用于主页路由,另一个用于受保护路由。同样,Spring Boot 和 Thymeleaf 已经自动将它们关联到 src/main/resources/templates 中的两个模板文件。

最后,在创建另一个名为 SecurityConfiguration 的Java类:

package com.okta.spring.OktaOAuthClient;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/**").authorizeRequests()
            .antMatchers("/").permitAll()
            .anyRequest().authenticated()
            .and()
            .oauth2Login();
    }
}


就是这样了!完成了!

运行基于Okta-OAuth的客户端:

./gradlew bootRun


您应该会看到一大堆输出,最后以此结尾:

...
2019-02-23 20:09:03.465  INFO 55890 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-02-23 20:09:03.470  INFO 55890 --- [           main] c.o.s.O.OktaOAuthClientApplication       : Started OktaOAuthClientApplication in 3.285 seconds (JVM running for 3.744)


导航到 http://localhost:8080。

点击 登录 按钮。

这次,您将会被引导到Okta登录页面。您可能需要使用隐身模式浏览器,或者在 developer.okta.com 仪表板中注销,以免跳过登录页面,直接被引导到受保护的端点。

Okta登录表单

登录后,您将会看到包含您的姓名的受保护页面!

了解更多关于Spring Boot、Spring Security和OAuth 2.0的信息

所以,就是这样。非常简单。在之前的教程中,您学习了如何使用Spring Boot和Spring Security来实现一个非常基本的身份验证服务器和客户端应用程序。接下来,您使用Okta创建了一个更简单的客户端应用程序,具有完全功能的SSO和OAuth验证。

您可以在 GitHub 上查看此教程的完整 代码

推荐阅读: FastApi、Flask、Django如何选择

本文链接: 使用Spring Boot和Spring Security构建OAuth 2.0授权服务器