超詳細!一步一步教會你如何使用Java構建單點登錄
在開發(fā)應用程序時,通常只有一臺資源服務器為多個客戶端應用程序提供數(shù)據(jù)。盡管這些應用程序可能具有相似的用戶,但它們可能具有執(zhí)行所需的不同權限。設想一種情況,其中第一個應用程序的一部分用戶應有權訪問第二個應用程序(以管理控制臺應用程序與客戶端或用戶應用程序相對應);您將如何執(zhí)行此操作?
在本文中,我將向您展示如何使用Okta和Spring Boot通過兩個客戶端應用程序和一個資源服務器來實現(xiàn)單點登錄。我還將討論如何使用訪問策略來強制執(zhí)行身份驗證和授權策略,以及如何基于應用程序范圍來限制對資源服務器的訪問。
在進入代碼之前,您需要適當?shù)挠脩羯矸蒡炞C配置。今天,您將使用Okta作為OAuth 2.0和OpenID Connect(OIDC)提供程序。這將使您能夠管理用戶和組,并輕松啟用諸如社交和多因素日志身份驗證之類的選項。
首先,您需要先注冊并創(chuàng)建一個免費的Okta開發(fā)人員帳戶(如果尚未注冊)。您會收到一封電子郵件,其中包含有關如何完成帳戶設置的說明。完成此操作后,導航回到您的Okta帳戶以設置Web應用程序,用戶,資源服務器和授權服務器。首次登錄時,可能需要單擊黃色的管理按鈕才能訪問開發(fā)人員的控制臺。
創(chuàng)建兩個OpenID Connect應用程序
第一步是創(chuàng)建兩個OIDC應用程序。OpenID Connect是建立在OAuth 2.0之上的身份驗證協(xié)議,它是一種授權協(xié)議。每個OIDC應用程序都為每個Web應用程序實例定義一個身份驗證提供程序終結點。
在Okta開發(fā)人員控制臺中,導航到應用程序,然后單擊添加應用程序。選擇Web,然后單擊Next。使用以下值填充字段:
單擊完成。
向下滾動并記下Client ID和Client Secret。您將很快使用這些值。
使用以下值對第二個應用程序重復這些步驟:
單擊完成。
你還需要的Client ID,并Client Secret從該OIDC申請為好。
為您的Java應用程序創(chuàng)建測試用戶
接下來,您需要創(chuàng)建兩個用戶。第一個用戶只能登錄第一個應用程序(OIDC App 1),第二個用戶可以登錄兩個應用程序。
在開發(fā)人員控制臺中,單擊“?用戶”?>“?人員”,然后單擊“?添加人員”。使用下表填寫第一個用戶的信息表。也使用下表對第二個用戶重復此操作。
記下兩個用戶的用戶名和密碼(稍后將與他們一起測試應用程序)。
創(chuàng)建用戶后,您可以單擊用戶名,然后單擊配置文件,然后單擊“?編輯”。在此處,為以下字段的每個用戶添加一些信息:中間名和昵稱。這將使您以后可以從應用程序中查看此信息。
為您的資源服務器創(chuàng)建服務應用程序
現(xiàn)在,您需要為資源服務器創(chuàng)建OIDC應用程序。這將配置對REST API的訪問。
在Okta開發(fā)人員控制臺中,導航到應用程序,然后單擊添加應用程序。選擇服務,然后單擊下一步。使用以下值填充字段:
單擊完成。
向下滾動并復制Client ID和Client Secret。您將很快使用這些值。
創(chuàng)建授權服務器
Okta的最后一步是創(chuàng)建和配置授權服務器。這使您可以配置自定義聲明并設置自定義訪問策略。這確定Okta是否在請求令牌時發(fā)出令牌,該令牌控制用戶訪問客戶端應用程序和資源服務器的能力。
導航對API?>?授權服務器。單擊添加授權服務器,然后按如下所示填寫值:
單擊“完成”,然后單擊“?聲明”選項卡。在“索賠”中,單擊“?添加索賠”,在下面的字段中填寫“?索賠1”的值,然后單擊“?創(chuàng)建”。您可以將以下未提及的任何值保留為默認值。完成后,重復并使用下面的Claim 2下的值創(chuàng)建第二個索賠。
接下來,您將為第一個應用程序添加訪問策略。此應用程序將允許兩個用戶訪問它。單擊訪問策略選項卡,添加新的訪問策略,在這些字段中填寫這些值,然后單擊創(chuàng)建策略。
這會將策略綁定到您的OIDC應用程序。
接下來,點擊添加規(guī)則。設置OIDC App 1的規(guī)則名稱字段。取消選擇除“?授權碼”之外的所有授權類型,然后單擊“?創(chuàng)建規(guī)則”。
這樣可以確保請求必須使用授權代碼流才能使Okta創(chuàng)建令牌。這是所有可用OAuth流中最安全的流。它確保通過對POST請求的響應來傳遞所有敏感信息(如令牌)。
接下來,您將為第二個應用程序添加訪問策略。此應用程序將僅允許第二個用戶Tanya Tester對其進行訪問。在訪問策略標簽中,添加策略,在這些字段中填寫這些值,然后點擊創(chuàng)建策略。
這會將策略綁定到您的OIDC應用程序。
接下來,點擊添加規(guī)則。設置OIDC App 2的規(guī)則名稱字段。取消選擇除“?授權碼”之外的所有授權類型。找到“?用戶”部分,然后選擇標記為“已分配應用程序和下列成員之一”的第二個單選按鈕:在出現(xiàn)的“?用戶”框中,開始鍵入Tanya并Tanya Tester從列表中選擇。這表明只有該用戶可以登錄該OIDC App 2應用程序。
點擊創(chuàng)建規(guī)則。
單擊設置選項卡,然后復制頒發(fā)者URL。您將很快使用此值。
在Okta中完成所有配置工作。上代碼!
創(chuàng)建OAuth 2.0資源應用
您將使用兩個不同的代碼庫。第一個是資源服務器的代碼庫,如果客戶被授權獲取此類信息,它將用于向客戶端應用程序提供其他用戶信息。
首先下載GitHub存儲庫中可用的資源服務器的代碼。
Java
git clone https://github.com/oktadeveloper/okta-java-spring-sso-example.git
cd okta-java-spring-sso-example/oauth2-resource-server
您將需要使用在Okta中創(chuàng)建的“ OIDC資源服務器”應用程序中的值來配置資源應用程序。打開
src/main/resources/application.properties文件。
Java
okta.oauth2.issuer={issuerUri}
okta.oauth2.clientId={clientId}
okta.oauth2.clientSecret={clientSecret}
okta.oauth2.audience=api://oidcauthserver
server.port=8082
將{clientId}和替換為{clientSecret}您為上面的資源服務器寫下的那些。這{issuerUri}是您在上面創(chuàng)建的授權服務器的頒發(fā)者URI。轉到API和授權服務器,然后查看OIDC身份驗證服務器旁邊的表。
要查看資源服務器的功能,請查看DemoResourceServer該類中的代碼。
src/main/java/com/okta/examples/sso/DemoResourceServer.java
Java
package com.okta.examples.sso;
import java.security.Principal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class DemoResourceServer {
public static void main(String[] args) {
SpringApplication.run(DemoResourceServer.class, args);
}
@GetMapping("/welecomeMessage")
@PreAuthorize("hasAuthority('SCOPE_profile')")
public String getWelcomeMessage(Principal principal) {
JwtAuthenticationToken jwtAuth = (JwtAuthenticationToken) principal;
String fullName = jwtAuth.getToken().getClaimAsString("fullName");
return "Welcome " + fullName + "!";
}
@GetMapping("/userEmail")
@PreAuthorize("hasAuthority('SCOPE_email')")
public String getUserEmail(Principal principal) {
JwtAuthenticationToken jwtAuth = (JwtAuthenticationToken) principal;
String email = jwtAuth.getToken().getClaimAsString("userEmail");
return email;
}
}
此代碼同時設置了Spring Boot應用程序和控制器。該@SpringBootApplication注解告訴它應支持自動配置,組件掃描,和豆注冊該應用程序。
該@RestController注解告訴系統(tǒng)這個文件是一個REST API控制器,它只是意味著它包含API端點的集合。該@
EnableGlobalMethodSecurity注解告訴系統(tǒng)端點可能對方法的水平,這兩種方法做安全放。每個get端點都使用@PreAuthorize注釋來告訴系統(tǒng)調用應用程序必須具有指定的特定范圍才能被授權。例如,如果/userEmail端點在沒有email作用域的情況下被調用,它將拋出錯誤。
該getWelcomeMessage方法返回一條歡迎消息,其中包含用戶的全名。該getUserEmail方法將返回用戶的電子郵件。這兩個數(shù)據(jù)都是從您之前在Okta控制臺中設置的令牌聲明中提取的。
打開一個Shell并使用Maven啟動資源服務器的實例。
Shell
./mvnw spring-boot:run
現(xiàn)在它將在port上監(jiān)聽8082。
創(chuàng)建OAuth 2.0客戶端應用
您將使用的第二個代碼庫是兩個不同客戶端應用程序的代碼庫。兩個客戶端應用程序將使用相同的代碼,但是將以不同的配置啟動。
運行客戶端應用程序時,將首先為OIDC App 1(已配置概要文件范圍)運行它。
您還將為OIDC App 2運行它,但是對于此應用程序,將在配置文件和電子郵件范圍都已設置的情況下運行它。
這是這兩個應用程序之間的主要配置差異之一。
對于兩個客戶端應用程序實例,請從oauth2-client示例項目目錄中的代碼開始。
此Web應用程序非常簡單。它導入所需的Okta和Spring依賴項,然后僅定義可以在給定某些參數(shù)的情況下啟動的客戶端應用程序。該應用程序的完整代碼在SingleSignOnApplication類中。
src/main/java/com/okta/examples/sso/SingleSignOnApplication.java
Java
package com.okta.examples.sso;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.servlet.ModelAndView;
@Controller
@SpringBootApplication
public class SingleSignOnApplication {
private WebClient webClient;
@Value("#{ @environment['resourceServer.url'] }"
private String resourceServerUrl;
public static void main(String[] args) {
SpringApplication.run(SingleSignOnApplication.class, args);
}
public SingleSignOnApplication(WebClient webClient) {
this.webClient = webClient;
}
@GetMapping("/")
public ModelAndView home(@AuthenticationPrincipal OidcUser user) {
ModelAndView mav = new ModelAndView();
mav.addObject("user", user.getUserInfo());
Map<String,String> userBasicProfile = new HashMap<String,String>();
userBasicProfile.put("First Name",user.getGivenName());
userBasicProfile.put("Middle Initial",user.getMiddleName());
userBasicProfile.put("Last Name",user.getFamilyName());
userBasicProfile.put("Nick Name",user.getNickName());
String welcomeMessage = this.webClient.get()
.uri(this.resourceServerUrl + "/welecomeMessage").retrieve()
.bodyToMono(String.class).block();
mav.addObject("welcomeMessage",welcomeMessage);
try {
String email = this.webClient.get()
.uri(this.resourceServerUrl + "/userEmail").retrieve()
.bodyToMono(String.class).block();
if (email != null) {
userBasicProfile.put("Email", email);
}
} catch (Exception e) {
mav.addObject("emailError", true);
}
mav.addObject("profile", userBasicProfile);
mav.setViewName("home");
return mav;
}
@Configuration
public static class OktaWebClientConfig {
@Bean
WebClient webClient(ClientRegistrationRepository clientRegistrations,
OAuth2AuthorizedClientRepository authorizedClients) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
clientRegistrations, authorizedClients);
oauth2.setDefaultOAuth2AuthorizedClient(true);
oauth2.setDefaultClientRegistrationId("okta");
return WebClient.builder().apply(oauth2.oauth2Configuration()).build();
}
}
}
該文件同時帶有@Controller和@SpringBootApplication注釋。該@SpringBootApplication注解告訴它應支持自動配置,組件掃描,和豆注冊該應用程序。
該@Controller注解告訴系統(tǒng)這個文件是一個REST API控制器。在這種情況下,只有一個端點處理GET對基本/URL的請求。
該端點調用home方法中的代碼,用最簡單的術語來說,該方法將建立一堆要顯示在頁面上的數(shù)據(jù),并告訴頁面使用哪個模板來顯示此數(shù)據(jù)。
在該home方法內,有兩個對資源服務器的調用。首先,它調用資源服務器以獲取歡迎消息以顯示在頁面上。只要應用程序配置了概要文件作用域集(如我之前提到的那樣),該消息就會成功返回,它將為客戶端應用程序的兩個實例都設置。下一個呼叫將獲取用戶的電子郵件。僅在為應用程序設置了電子郵件范圍的情況下,才成功返回電子郵件。
請記住,只有客戶端應用程序的第二個實例將設置電子郵件范圍,因此對于第一個實例,它將引發(fā)錯誤。這是使用范圍確定授權的授權失敗示例。
如果無法檢索到電子郵件,則設置一個標志,告訴模板(在home.html文件中配置)顯示一條消息,指出該應用程序無權獲取用戶的電子郵件。
使用Spring Run Profiles配置客戶端應用程序
現(xiàn)在,您需要在oauth2-client項目文件夾中配置客戶端應用程序的兩個不同實例。您需要能夠使用兩個不同的配置值來運行客戶端應用程序的兩個不同的實例。為此,您將利用Spring Boot的運行配置文件。這通常用于分隔諸如testand dev和production,但是沒有理由我們不能在這里使用它。
如果查看
oauth2-client/src/main/resources,您將看到三個.properties文件。
application.properties對所有三個配置文件都是通用的
application-client1.properties具有客戶端1?
application-client2.properties的配置值具有客戶端2的配置值
打開
oauth2-client/src/main/resources/application.properties并填寫您在上面創(chuàng)建的資源服務器的頒發(fā)者URI。
要查找頒發(fā)者URI(如果您沒有記下來的話),請轉到API和授權服務器。在頒發(fā)者URI下的OIDC身份驗證服務器旁邊的表中查找。
Java
okta.oauth2.issuer={yourIssuerUri}
resourceServer.url=http://localhost:8082
打開
oauth2-client/src/main/resources/application-client1.properties并填寫第一個OIDC客戶端應用程序的客戶端ID和客戶端密鑰。
如果您需要再次找到這些值,請從Okta Developer的控制臺中,轉到Applications,單擊表中的OIDC應用程序名稱(OIDC App 1),然后單擊General選項卡。客戶端ID和客戶端密鑰在底部。
Java
okta.oauth2.clientId={yourClient1Id}
okta.oauth2.clientSecret={yourClient1Secret}
okta.oauth2.scopes=openid,profile
server.port=8080
打開
oauth2-client/src/main/resources/application-client2.properties并填寫第二個OIDC客戶端應用程序的客戶端ID和客戶端密鑰。
Java
okta.oauth2.clientId={yourClient2Id}
okta.oauth2.clientSecret={yourClient2Secret}
okta.oauth2.scopes=openid,profile,email
server.port=8081
最后一步是運行此客戶端應用程序的兩個實例。在兩個單獨的Shell窗口中運行以下命令。這會為客戶端加載每個運行配置文件。
在http:// localhost:8080上運行客戶端應用程序1:
Shell
./mvnw spring-boot:run -Dspring-boot.run.profiles=client1
在http:// localhost:8081上運行客戶端應用2:
Shell
./mvnw spring-boot:run -Dspring-boot.run.profiles=client2
那是很多事情的配置,所以讓我們快速回顧一下剛剛設置并運行的內容。
- http:// localhost:8082是您的本地資源服務器
- http:// localhost:8080是客戶端應用程序1(任何經(jīng)過身份驗證的用戶都可以訪問)
- http:// localhost:8081是客戶端應用程序2(訪問策略設置為僅允許Tany Tester訪問)
在Okta方面:
- 您為服務器和兩個客戶端應用程序創(chuàng)建了匹配的OIDC應用程序。這將為每個應用程序生成唯一的客戶端ID和客戶端密鑰,這使Okta可以對應用程序進行身份驗證,并允許您使用Okta對其進行配置。
- 您還創(chuàng)建了一個自定義授權服務器。這將管理來自應用程序的所有身份驗證和授權請求。
- 在授權服務器中,您創(chuàng)建了兩個訪問策略,每個客戶端應用程序一個。兩種訪問策略均限制對授權碼流的訪問。第一個客戶端應用程序對任何經(jīng)過身份驗證的用戶(通過Okta的單點登錄進行身份驗證的任何用戶)開放。第二個應用程序僅限于用戶Tanya Tester。
因此,您創(chuàng)建了一個非常典型的生產(chǎn)場景,其中有一個資源服務器為多個客戶端應用程序提供數(shù)據(jù),并且您使用Okta的儀表板提供單點登錄,管理用戶以及設置對客戶端應用程序和資源服務器的訪問策略。
得到它了?是時候嘗試一下了。
測試您的Java單一登錄
在接下來的幾個步驟中,您將在兩個不同的應用程序上登錄和注銷不同的Okta帳戶。使用隱身窗口將避免注銷Okta開發(fā)人員控制臺或單一登錄帳戶。
打開一個新的隱身瀏覽器窗口,然后輸入URL http://localhost:8080。這是第一個應用程序的URL OIDC App 1。
用tanyaTester@mail.com用戶登錄。您應該能夠成功登錄!
接下來,您可以將URL更改為http://localhost:8081。這是第二個應用程序的URL OIDC App 2。您會發(fā)現(xiàn)您不必再次登錄。這是因為您已經(jīng)登錄,OIDC App 1并且是單點登錄!
如果要關閉瀏覽器窗口,打開一個新的隱身瀏覽器,然后OIDC App 2再次登錄,系統(tǒng)將提示您重新登錄,因為它將不再具有您的會話。
測試您的訪問策略
您已經(jīng)看到Tanya Tester可以登錄到兩個應用程序。接下來,您將看到與amandaTester@mail.com用戶一起登錄每個應用程序時會發(fā)生什么。如果尚未關閉,請關閉用于測試的任何隱身瀏覽器窗口。
打開一個新的隱身瀏覽器窗口,然后輸入URL http://localhost:8080。用amandaTester@mail.com用戶登錄。您應該能夠成功登錄!
關閉該瀏覽器窗口,然后打開一個新的隱身瀏覽器窗口,然后輸入URL http://localhost:8081。
用amandaTester@mail.com用戶登錄。您將收到“訪問被拒絕”錯誤。
出現(xiàn)此錯誤的原因是,您設置了訪問策略,因此只能Tanya Tester登錄OIDC App 2。
測試范圍授權
最后,您將測試資源服務器如何處理每個應用程序的授權。
打開一個新的隱身瀏覽器窗口,然后輸入URL http://localhost:8080。用tanyaTester@mail.com用戶登錄。您應該能夠成功登錄。
請注意,您將在頂部看到一條消息,其中包含特定于用戶的歡迎消息。這是因為應用程序正在使用配置文件作用域,因此被允許訪問歡迎消息端點。在其下,您將看到一條消息,指出該應用程序無權訪問電子郵件信息。這是因為該應用程序實例未與電子郵件范圍一起運行。
接下來,您可以將URL更改為http://localhost:8081。請記住,這是第二個應用程序的URL OIDC App 2。當您轉到此頁面時,您會注意到您沒有看到有關無法訪問該電子郵件的消息。相反,您會在配置文件信息中看到該電子郵件。
好啦!今天的分享到這里就結束了,希望大家持續(xù)關注馬哥教育官網(wǎng),每天都會有大量優(yōu)質內容與大家分享!
文章來源于網(wǎng)絡,侵刪!