1. 概述
在本教程中,我們將演示如何創建一個將用户身份驗證委託給第三方以及自定義授權服務器的應用程序,使用 Spring Boot 和 Spring Security OAuth。
此外,我們將演示如何使用 Spring 的<em>PrincipalExtractor</em> 和<em>AuthoritiesExtractor</em> 接口提取<em>Principal</em> 和<em>Authorities</em>。
有關 Spring Security OAuth2 的介紹,請參閲這些文章。
2. Maven 依賴項
為了開始,我們需要將 spring-security-oauth2-autoconfigure 依賴項添加到我們的 pom.xml 中:
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.6.8</version>
</dependency>
3. 使用 Github 進行 OAuth 認證
接下來,讓我們為我們的應用程序創建安全配置:
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/login**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.disable()
.oauth2Login();
return http.build();
}
}
簡而言之,我們説的是任何人都可以訪問 /login 端點,並且所有其他端點都需要用户身份驗證。
添加以下屬性(至少一個客户端)將啓用 Oauth2ClientAutoConfiguration 類,該類設置了所有必要的 Bean。
spring.security.oauth2.client.registration.github.client-id=89a7c4facbb3434d599d
spring.security.oauth2.client.registration.github.client-secret=
9b3b08e4a340bd20e866787e4645b54f73d74b6a
spring.security.oauth2.client.registration.github.scope=read:user,user:email
spring.security.oauth2.client.provider.github.token-uri=
https://github.com/login/oauth/access_token
spring.security.oauth2.client.provider.github.authorization-uri=
https://github.com/login/oauth/authorize
spring.security.oauth2.client.provider.github.user-info-uri=https://api.github.com/user
與其處理用户帳户管理,我們正在將其委託給第三方——在本例中為 Github,從而使我們能夠專注於應用程序的邏輯。
4. 從主要實體和權限提取
當作為 OAuth 客户端並通過第三方進行用户身份驗證時,我們需要考慮以下三個步驟:
- 用户身份驗證 – 用户與第三方進行身份驗證
- 用户授權 – 遵循身份驗證,當用户允許我們的應用程序代表其執行某些操作時;這時 權限 發揮作用
- 獲取用户數據 – 使用我們獲得的 OAuth 令牌檢索用户數據
一旦我們檢索了用户數據,Spring 能夠自動創建用户的主要 主要實體 和 權限。
雖然這種情況可能可以接受,但我們通常會發現自己處於想要完全控制它們的情況。
要做到這一點,Spring 提供了兩個接口我們可以用來覆蓋默認行為:
- 主要實體提取器 – 我們可以使用此接口來提供自定義邏輯以提取 主要實體
- 權限提取器 – 類似於 主要實體提取器,但它用於自定義 權限 提取
默認情況下,Spring 提供了兩個組件 – 主要實體提取器 和 權限提取器 – 這些接口實現並具有為我們創建它們的預定義策略。
4.1. 定製 Github 認證
在我們的案例中,我們瞭解 Github 用户數據是什麼樣的 樣子,以及我們可以根據我們的需求如何對其進行定製。
因此,為了覆蓋 Spring 的默認組件,我們只需要創建兩個 Bean,這些 Bean 也需要實現這些接口。
對於我們應用程序的 Principal,我們將簡單地使用用户的 Github 用户名:
public class GithubPrincipalExtractor
implements PrincipalExtractor {
@Override
public Object extractPrincipal(Map<String, Object> map) {
return map.get("login");
}
}
根據我們的用户的 Github 訂閲 – 免費,或者其他 – 我們將授予他們一個 GITHUB_USER_SUBSCRIBED 權限,或者一個 GITHUB_USER_FREE 權限:
public class GithubAuthoritiesExtractor
implements AuthoritiesExtractor {
List<GrantedAuthority> GITHUB_FREE_AUTHORITIES
= AuthorityUtils.commaSeparatedStringToAuthorityList(
"GITHUB_USER,GITHUB_USER_FREE");
List<GrantedAuthority> GITHUB_SUBSCRIBED_AUTHORITIES
= AuthorityUtils.commaSeparatedStringToAuthorityList(
"GITHUB_USER,GITHUB_USER_SUBSCRIBED");
@Override
public List<GrantedAuthority> extractAuthorities
(Map<String, Object> map) {
if (Objects.nonNull(map.get("plan"))) {
if (!((LinkedHashMap) map.get("plan"))
.get("name")
.equals("free")) {
return GITHUB_SUBSCRIBED_AUTHORITIES;
}
}
return GITHUB_FREE_AUTHORITIES;
}
}
然後,我們也需要使用這些類創建 Bean:
@Configuration
public class SecurityConfig {
// ...
@Bean
public PrincipalExtractor githubPrincipalExtractor() {
return new GithubPrincipalExtractor();
}
@Bean
public AuthoritiesExtractor githubAuthoritiesExtractor() {
return new GithubAuthoritiesExtractor();
}
}
4.2. 使用自定義授權服務器
我們也可以為我們的用户使用自己的授權服務器——而不是依賴第三方。
儘管我們決定使用哪個授權服務器,但我們需要自定義的組件,即Principal和Authorities仍然相同:一個PrincipalExtractor和一個AuthoritiesExtractor。
我們只需要注意由user-info-uri端點返回的數據,並根據需要使用它。
讓我們將我們的應用程序配置為使用本文所述的授權服務器來身份驗證我們的用户:
spring.security.oauth2.client.registration.baeldung.client-id=SampleClientId
spring.security.oauth2.client.registration.baeldung.client-secret=secret
spring.security.oauth2.client.provider.baeldung.token-uri=http://localhost:8081/auth/oauth/token
spring.security.oauth2.client.provider.baeldung.authorization-uri=
http://localhost:8081/auth/oauth/authorize
spring.security.oauth2.client.provider.baeldung.user-info-uri=http://localhost:8081/auth/user/me
現在我們指向了我們的授權服務器,我們需要創建這兩個提取器;在本例中,我們的PrincipalExtractor將從Map中提取Principal,使用“name”鍵:
public class BaeldungPrincipalExtractor
implements PrincipalExtractor {
@Override
public Object extractPrincipal(Map<String, Object> map) {
return map.get("name");
}
}
至於權限,我們的授權服務器已經將它們放在其user-info-uri的數據中。
因此,我們將提取並豐富它們:
public class BaeldungAuthoritiesExtractor
implements AuthoritiesExtractor {
@Override
public List<GrantedAuthority> extractAuthorities
(Map<String, Object> map) {
return AuthorityUtils
.commaSeparatedStringToAuthorityList(asAuthorities(map));
}
private String asAuthorities(Map<String, Object> map) {
List<String> authorities = new ArrayList<>();
authorities.add("BAELDUNG_USER");
List<LinkedHashMap<String, String>> authz =
(List<LinkedHashMap<String, String>>) map.get("authorities");
for (LinkedHashMap<String, String> entry : authz) {
authorities.add(entry.get("authority"));
}
return String.join(",", authorities);
}
}
然後我們將這些 Bean 添加到我們的SecurityConfig類中:
@Configuration
public class SecurityConfig {
// ...
@Bean
public PrincipalExtractor baeldungPrincipalExtractor() {
return new BaeldungPrincipalExtractor();
}
@Bean
public AuthoritiesExtractor baeldungAuthoritiesExtractor() {
return new BaeldungAuthoritiesExtractor();
}
}
5. 結論
在本文中,我們已實現一個將用户身份驗證委託給第三方服務,以及自定義授權服務器的應用,並演示瞭如何自定義Principal和Authorities。
在本地運行時,您可以在localhost:8082上運行和測試該應用。