使用Spring Boot 2 + OAuth2自动配置来实现超简化的OAuth2客户端
本次我们将解释使用Spring Boot 2和OAuth2自动配置的方式来创建一个简单的OAuth2客户端(前端)应用程序的方法。
使用OAuth2客户端实现以下要求:
-
- OAuth2認可サーバを利用したシングルサインオン(SSO)
- SSOで取得したアクセストークンを利用したOAuth2リソースサーバ(API)へのアクセス
作为一个提供者,我们将使用Github API。
本次使用的库
-
- Spring Boot 2 (Spring & Spring Security 5)
spring-boot-starter-web 2.0.1.RELEASE
spring-boot-starter-security 2.0.1.RELEASE
OAuth2 Autoconfig (Spring Security OAuth2)
spring-security-oauth2-autoconfigure 2.0.0.RELEASE
Thymeleaf 3
spring-boot-starter-thymeleaf 2.0.1.RELEASE
thymeleaf-extras-springsecurity4 3.0.2.RELEASE
Coding Support
lombok 1.16.20
为了简化Spring Security OAuth2的配置,关键是使用spring-security-oauth2-autoconfigure。
通过Spring Security进行常规登录
首先,我们将使用Spring Boot 2(Spring和Spring Security 5)来验证常规表单登录。
设置依赖库
- pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot 2 (Spring & Spring Security 5) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Thymeleaf 3 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
<!-- Coding Support -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
thymeleaf-extras-springsecurity4用于在登录后的页面上显示认证信息。由于尚未发布适用于Spring Security 5的模块,所以我们使用适用于Spring Security 4的模块作为替代品。
应用程序的入口点
- Application.java
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
只需将Spring Boot依赖项spring-boot-starter-security添加到类路径中,即可自动启用Spring Security,并将默认的isAuthenticated()授权应用于所有路径,即使没有在入口点上使用@EnableWebSecurity注解。
主屏幕
- Controller.java
@Controller
public class Controller {
@GetMapping("/")
public String home() {
return "home";
}
}
- home.html
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="utf-8">
<title>Home</title>
</head>
<body>
<h1>Welcome to Home <span sec:authentication="name"></span></h1>
</body>
</html>
当您执行应用程序并访问上下文根时,将显示Spring Security的默认登录页面,通过表单登录功能。登录后,将显示“欢迎来到首页,用户”并确认登录成功。
在Spring Security的默认设置中,您可以使用用户名为user和启动时输出的随机生成密码来进行访问。
使用OAuth2实现单点登录(SSO)
一旦确认了常规的Spring Security登录,接下来将切换至基于OAuth2的SSO登录。
在依存库中添加spring-security-oauth2-autoconfigure。
- pom.xml
<dependencies>
<!-- OAuth2 Autoconfig (Spring Security OAuth2) -->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
</dependencies>
在入口点上添加@EnableOAuth2Sso
- Application.java
@SpringBootApplication
@EnableWebSecurity
@EnableOAuth2Sso
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
通过添加@EnableOAuth2Sso,可以钩取Spring Security的认证过程,实现基于OAuth2的认证。
这里的要点是除了使用@EnableOAuth2Sso外,还需要添加@EnableWebSecurity。
使用@EnableOAuth2Sso将启用OAuth2认证,但是它不会传递给Spring Security的认证信息(即不会登录),因此每次请求都需要进行认证,这样就完全没有实用性。如果要进行SSO,则必须添加@EnableWebSecurity。
在Github上注册应用程序。
请参考Github API的文档,将应用程序注册到Github,并获取client-id和client-secret。
从Github用户页面的用户图标处,选择“设置>开发者设置>OAuth应用程序>创建新的OAuth应用程序”,按照指示进行注册。
在注册应用程序的时候需要提供Authorization callback URL(用于认证时的回调URL),因此需要说明如何设置应用程序的端口号和上下文根URL。
- application.yml
server:
port: 8080
servlet:
context-path: /demo
将OAuth2的客户端认证信息注册
- application.yml
security:
oauth2:
client:
client-id: xxxx # (1)
client-secret: xxxx
access-token-uri: https://github.com/login/oauth/access_token # (2)
user-authorization-uri: https://github.com/login/oauth/authorize
client-authentication-scheme: form # (3)
resource:
user-info-uri: https://api.github.com/user # (4)
prefer-token-info: false
sso:
login-path: /login # (5)
(1) 注册刚刚获取的client-id和client-secret。
(2) 参考Github API参考文档,注册授权URL(user-authorization-uri)和获取访问令牌的URL(user-authorization-uri)。
(3) 由于我们要在请求正文中附加认证信息,所以将client-authentication-scheme设置为form。
(4) 如果只用作OAuth2客户端,以上设置就足够了,但是为了实现单点登录,我们还需要注册一个用于获取用户信息的URL(user-info-uri)。另外,由于我们要从user-info-uri获取用户信息而不是token-info-uri,所以将prefer-token-info设置为false以禁用后者。
(5) 如果想要更改SSO登录URL(用于在未进行认证时进行重定向的URL),可以更改login-path。本次使用默认值(/login)就可以,所以无需进行设置,但是当存在多个提供者或需要通过授权控制来排除登录URL时,需要进行设置。
当您在此时运行应用并访问上下文根时,将显示GitHub登录界面。
在GitHub上授权应用后,将跳转到主页,并显示“Welcome to Home yoshikawaa”,您可以确认通过SSO登录并将GitHub的认证信息连接到Spring Security的认证信息中。
由于OAuth2Authentication替代了认证信息,因此您可以在页面上通过sec:authentication=”oAuth2Request.clientId”来确认client-id。
使用OAuth2访问资源服务器
在确认通过SSO登录后,我们要使用获取的访问令牌来验证能否访问资源服务器(例如Github API)。
这次,我将参考Github API参考文档,来获取登录用户的库存列表并显示在页面上。
如果尝试在浏览器中访问URL https://api.github.com/user/repos,会显示需要身份验证,说明需要进行认证。
在入口点添加OAuth2RestTemplate的Bean定义。
- Application.java
@Bean
public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext oauth2ClientContext,
OAuth2ProtectedResourceDetails details) {
return new OAuth2RestTemplate(details, oauth2ClientContext);
}
在类上添加@EnableOAuth2Client注解将自动配置OAuth2ClientContext和OAuth2ProtectedResourceDetails,但是由于@EnableOAuth2Sso内部已经包含@EnableOAuth2Client,所以这里不需要添加注解。
储存获取的代码库信息的Bean
- Repository.java
@Getter
@Setter
public class Repository {
private String name;
@JsonProperty("html_url")
private String htmlUrl;
}
这是我第一次使用Lombok在这里。
显示登录用户的存储库列表界面
- Controller.java
@Autowired
private OAuth2RestTemplate auth2RestTemplate;
@GetMapping("/repos")
public String repositories(Model model) {
URI uri = UriComponentsBuilder.fromUriString("https://api.github.com/user/repos").build().toUri();
model.addAttribute("repos", auth2RestTemplate.getForEntity(uri, Repository[].class).getBody());
return "repos";
}
- repos.html
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="utf-8">
<title>Repos</title>
</head>
<body>
<h1>Listing Repositories owned <span sec:authentication="name"></span></h1>
<ul>
<li th:each="repo : ${repos}" th:object="${repo}">
<a th:text="*{name}" th:href="*{htmlUrl}"></a>
</li>
</ul>
</body>
</html>
在此时执行应用程序并访问上下文根路径,通过SSO进行登录。然后,访问路径/repos,可以确认使用获得的访问令牌可以从Github API获取存储库列表。浏览器的网络历史记录中可以看到,在访问路径repos时,可以确认没有重新登录等操作。
顺便提一下,如果访问令牌在中途失效,会向授权服务器查询并可能导致当前访问的URL发生GET重定向。在这种情况下,由于之前使用POST发送的请求参数会丢失,因此通常应通过GET访问资源服务器的URL,或者即使请求参数丢失,重新输入也是可以的,这样做比较稳妥。
总结
Spring Security OAuth2的设置非常复杂,实现起来非常困难,但是使用OAuth2 Autoconfig,几乎没有繁琐的设置,非常简单地实现了单点登录和OAuth2客户端。
然而,关于如何与Spring Security集成的方法在文档中并没有提到,一开始我遇到了很多问题,所以希望将来能够得到改进。
请参考
-
- (Getting Started) Building an Application with Spring Boot
-
- (Tutorial) Spring Boot and OAuth2
-
- OAuth2 Autoconfig
-
- Github API v3
- Building OAuth Apps