距離上次寫分享已有半年沒有更新了,由於工作上的新項目、生活等方面的原因,遲遲未整理Spring Security Oauth2這塊的內容。最近全國各地都在抗疫中,大家工作生活都受到影響,基本不能出門,儘量在家辦公。當然我也不例外,利用比平時上班省下的些許通勤時間整理這篇分享。
上篇文章分享瞭如何創建Oauth2
授權服務器及最常用的授權碼模式的使用過程,那麼在實際項目中如何對接授權服務器實現對某些受保護資源的訪問?相信大家都使用過各種平臺的登錄,比如CSDN
註冊/登錄,除了服務本身提供的賬號方式,還可以通過社交賬號的如qq
/github
/baidu
等方式登錄,這種登錄方式就是CSDN
利用這些社交服務商提供的Oauth2
授權服務來獲取當前服務的信息,從而完成CSDN
註冊/綁定。在這個過程中,CSDN
扮演的則是Oauth2
客戶端或第三方應用,本文將創建一個demo項目,分享實現Oauth2
客戶端獲取授權的基本過程。
Oauth2客戶端實現方式
Oauth2
客戶端對接實現一般有兩種方式:
-
調用授權服務器提供的授權碼接口、獲取
token
接口等來對接(交互過程可參考Spring Security Oauth2實踐(1) - 授權碼模式),屬於最常用的方式,筆者在實際開發小應用項目過程中也用到這種方式,理解起來也很方便,缺點是需要自定義大量編碼、封裝拓展性依賴開發人員水平、url 302轉發、登錄狀態維護等需要額外開發控制、多個Oauth2 Provider
時可能需要重新編碼對接,關於這塊過程可參考Spring Boot+OAuth2使用GitHub登錄自己的服務.這裏不再提供實現。 -
基於
Spring Security Oauth2
生態提供的方式對接,框架本身將大量的調用交互細節封裝,並且維護用戶認證登錄狀態,提供了很多拓展接口/類方便自定義業務邏輯,開發往往只需要實現特定的接口並注入到相應的業務中就可以完成。缺點在於門檻較高、理解起來需要一點時間、涉及到Spring Security
認證狀態維護、過濾器等細節往往需要閱讀源碼才理解怎麼使用。一旦理解原理、配置使用等,則後續拓展將會非常方便,本文將採用這種方式實踐。
Spring Security Oauth2
客戶端實踐
值得注意的是,Spring Security OAuth2提供了一套客戶端實現(本文采用的框架),Spring Boot也有它自己的方式,這種方式可參考Spring Boot官方指南中,實現、配置等與前者有區別。
Oauth2
授權服務器使用上篇分享搭建的環境,SpringBoot採用2.x。
Maven依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.oauth.client</groupId>
<artifactId>oauth-client-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>oauth-client-demo</name>
<description>oauth-client-demo</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.11.RELEASE</version>
<relativePath />
</parent>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
項目配置文件
application.yml配置文件,其中${oauth2.client}
及${oauth2.resource}
配置是Oauth2
授權服務器發放的客戶端應用信息和要訪問的被保護資源。
server:
port: 9201
servlet:
context-path: /oauth-client1
session:
#自定義Session名稱,以區分瀏覽器其它的登錄Session
cookie:
name: APPSESSION
oauth2:
client:
clientId: B5CDC04D8D8D419DA406364168F276A2
clientSecret: E2DA5B2DEDD548AEB7ABA689436C2E2C
accessTokenUri: http://localhost:9011/auth/oauth/token
userAuthorizationUri: http://localhost:9011/auth/oauth/authorize
clientAuthenticationScheme: form
tokenName: access_token
scope: userProfile
resource:
userInfoUri: http://localhost:9011/auth/userInfo
logging:
# level下配置鍵值對
level:
root: debug
通過@EnableOAuth2Client配置Spring Security Oauth2
@EnableOAuth2Client
配置表示在Spring Security
應用創建一個Oauth2
客戶端,它可以對接一個或多個授權服務器獲取授權碼認證。該配置需要創建一個OAuth2ClientContextFilter
過濾器,並需要注入OAuth2ClientContext
即Oauth2Client
上下文,這樣就可以使用OAuth2RestTemplate
來調用授權服務器的一系列認證服務。具體過程如下
@Configuration
@EnableOAuth2Client
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
OAuth2ClientContext oauth2ClientContext;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/login**", "/error**").permitAll()
.anyRequest().authenticated()
.and()
.logout().logoutUrl("/logout").logoutSuccessUrl("/")
.and()
.addFilterBefore(oauth2ClientFilter(), BasicAuthenticationFilter.class);
}
@Bean
@ConfigurationProperties("oauth2.client")
public AuthorizationCodeResourceDetails githubClient() {
return new AuthorizationCodeResourceDetails();
}
@Bean
@ConfigurationProperties("oauth2.resource")
public ResourceServerProperties githubResource() {
return new ResourceServerProperties();
}
// 過濾器註冊bean
@Bean
public FilterRegistrationBean<OAuth2ClientContextFilter> oauth2ClientFilterRegistration(
OAuth2ClientContextFilter filter) {
FilterRegistrationBean<OAuth2ClientContextFilter> registration = new FilterRegistrationBean<OAuth2ClientContextFilter>();
registration.setFilter(filter);
registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
return registration;
}
private Filter oauth2ClientFilter() {
//配置在Oauth2授權服務器上註冊的redirect_url,可理解爲認證完成後的回調/過程接口
OAuth2ClientAuthenticationProcessingFilter oauth2ClientFilter = new OAuth2ClientAuthenticationProcessingFilter(
"/login/demo");
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(githubClient(), oauth2ClientContext);
oauth2ClientFilter.setRestTemplate(restTemplate);
UserInfoTokenServices tokenServices = new UserInfoTokenServices(githubResource().getUserInfoUri(),
githubClient().getClientId());
// 可配置用戶授權後的回調業務,比如將用戶的信息保存在服務中
//tokenServices.setPrincipalExtractor();
tokenServices.setRestTemplate(restTemplate);
oauth2ClientFilter.setTokenServices(tokenServices);
return oauth2ClientFilter;
}
}
服務Controller接口
服務接口,爲前端提供服務。未認證前訪問這些接口則會報403(禁止訪問),前端接收到狀態後可跳轉到上文中的/login/demo
到授權服務器登錄獲取權限。
@Controller
public class AppController {
// 主頁
@RequestMapping("/")
public void indexHome(Principal principal, HttpServletResponse response) throws IOException {
if (null == principal) {
// 未登錄時則跳轉,實際過程一般是由前端控制,後臺返回 401即可,前端處理跳轉到index,用戶點擊通過其它應用授權
response.sendRedirect("login/demo");
return;
}
response.getWriter().write("welcome " + principal.getName() + " !!!");
}
// 測試接口
@RequestMapping("loginUser")
@ResponseBody
public Object getUser(Principal principal) {
return principal;
}
}
測試
整體交互流程
具體登錄過程
-
訪問
http://localhost:9201/oauth-client1/
-
跳轉到
http://localhost:9011/auth/login
認證服務服務器登錄頁面
-
輸入賬密登錄成功,跳轉到
http://localhost:9011/auth/oauth/authorize?client_id=B5CDC04D8D8D419DA406364168F276A2&redirect_uri=http://localhost:9201/oauth-client1/login/demo&response_type=code&scope=userProfile&state=3CWGVv
請求授權; -
確認授權頁面是否授權
-
獲取到授權碼
-
登錄成功後最終302跳轉到主頁
- 測試其它接口也可訪問
總結
文章提供了一個基於Spring Security
框架的Oauth2客戶端demo,由於框架封裝,搭建成功後整體看起來非常簡潔,從最終的交互過程來看框架對於Oauth2
授權碼模式標準也基本全部實現。如果需要了解框架是如何工作的,則需要深入源碼,比如訪問授權接口過程、維護認證狀態等過程源碼,由於時間資源有限,筆者搭建Demo、調試過程中研究了部分源碼,但未整理成文,有機會再分享。
在實際生產落地實踐則需要考慮更多,例如如何保存從資源服務器獲取的用戶信息與平臺綁定、url接口權限、應用登錄狀態維護、前端分離交互等。
參考
https://www.baeldung.com/sso-spring-security-oauth2
https://www.concretepage.com/spring-boot/spring-boot-enableoauth2client
https://www.cnblogs.com/cjsblog/p/9241217.html
筆者寫本文時,抗疫已取得重大進展,天佑中華,希望能儘快取得最終勝利,加油!!!