当前位置:Java -> 使用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摆脱自己搭建的认证服务器,并简化应用程序。
让我们开始吧!
首先打开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,用以下设置创建一个新项目:
下载项目,将其复制到最终位置并解压缩。
这次你需要将以下依赖项添加到你的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
请注意,这里你正在配置clientId
和clientSecret
,以及您的身份验证服务器的各种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的设置更多信息。
Okta是一个SaaS(软件即服务)身份验证和授权提供商。我们为开发人员提供免费账户,以便他们可以轻松开发无OIDC应用。转到Okta Developer页面,注册一个账户。确认您的电子邮件后,登录并执行以下步骤:
http://localhost:8080/login/oauth2/code/okta
。其余默认值将会起作用。保持页面打开,并注意客户端ID和客户端秘钥。您马上会需要它们。
复制项目并解压到某个位置。
在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 仪表板中注销,以免跳过登录页面,直接被引导到受保护的端点。
登录后,您将会看到包含您的姓名的受保护页面!
所以,就是这样。非常简单。在之前的教程中,您学习了如何使用Spring Boot和Spring Security来实现一个非常基本的身份验证服务器和客户端应用程序。接下来,您使用Okta创建了一个更简单的客户端应用程序,具有完全功能的SSO和OAuth验证。
您可以在 GitHub 上查看此教程的完整 代码。
推荐阅读: FastApi、Flask、Django如何选择
本文链接: 使用Spring Boot和Spring Security构建OAuth 2.0授权服务器