# LIFF 開發框架 [TOC] ###### tags: `chatgpt` --- ## 聽過 LIFF 嗎 是的,我知道 LIFF。LIFF 全名為 Line Front-end Framework,是 Line 的一個開發框架,用於開發 Line Bot 的前端應用程序。 LIFF 提供了一種輕量級的方式來開發 Line Bot 的前端,開發者可以使用 HTML、CSS 和 JavaScript 等前端技術來設計和實現 Line Bot 的介面和功能,並與 Line Bot 後端進行通信。 ## 由於 LIFF 已處理了前端的認證及授權,當呼叫 REST API 時如何向後端進行認證及授權? 當使用 LIFF 框架開發 Line Bot 前端應用程序時,LIFF 已經處理了前端的認證及授權,因此在呼叫 REST API 時,可以使用 LIFF 提供的 token 來向後端進行認證及授權。 在 LIFF 中,可以使用 `liff.getAccessToken()` 方法獲取當前用戶的 access token,然後將 access token 傳遞到後端 API 中,讓後端 API 對用戶進行驗證和授權。 以下是一個簡單的 JavaScript 代碼示例,展示如何使用 LIFF 的 access token 進行 API 認證及授權: ```javascript // 呼叫 LIFF API 獲取 access token const accessToken = await liff.getAccessToken(); // 發送 API 請求,將 access token 作為請求 header 中的 Authorization 欄位 const response = await fetch('https://your-api.com/your-endpoint', { headers: { 'Authorization': `Bearer ${accessToken}` } }); // 處理 API 响應 const data = await response.json(); ``` 在這個示例中,`fetch` 方法被用來發送 API 請求,並將 access token 作為 Authorization 欄位的值傳遞到 API。注意,這裡的 access token 是一個字串,需要使用 Bearer 方案來進行認證。在實際應用中,你需要將 **`https://your-api.com/your-endpoint`** 替換為實際的 API 端點,並確保你的 API 正確處理了 access token。 ## 後端框架為 Spring Boot,並搭配外部的 Keycloak,該怎麼實作呢 如果你的後端框架是 Spring Boot,並且你使用外部的 Keycloak 來進行身份驗證和授權,你可以使用 Spring Security 框架來實現身份驗證和授權。 以下是一個簡單的 Spring Security 配置,展示如何使用 Keycloak 進行身份驗證和授權: ```java @Configuration @EnableWebSecurity public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter { @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(keycloakAuthenticationProvider()); } @Bean public KeycloakSpringBootConfigResolver keycloakConfigResolver() { return new KeycloakSpringBootConfigResolver(); } @Override protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); } @Override protected void configure(HttpSecurity http) throws Exception { super.configure(http); http.authorizeRequests() .antMatchers("/api/**").authenticated() .anyRequest().permitAll(); } @Bean @Override protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception { KeycloakAuthenticationProcessingFilter filter = new KeycloakAuthenticationProcessingFilter(authenticationManagerBean()); filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy()); return filter; } @Bean public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(KeycloakPreAuthActionsFilter filter) { FilterRegistrationBean registration = new FilterRegistrationBean(filter); registration.setEnabled(false); return registration; } @Bean public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(KeycloakAuthenticationProcessingFilter filter) { FilterRegistrationBean registration = new FilterRegistrationBean(filter); registration.setEnabled(false); return registration; } @Bean public FilterRegistrationBean keycloakAuthenticatedActionsFilterBean(KeycloakAuthenticatedActionsFilter filter) { FilterRegistrationBean registration = new FilterRegistrationBean(filter); registration.setEnabled(false); return registration; } @Bean public FilterRegistrationBean keycloakSecurityContextRequestFilterBean(KeycloakSecurityContextRequestFilter filter) { FilterRegistrationBean registration = new FilterRegistrationBean(filter); registration.setEnabled(false); return registration; } @Bean public FilterRegistrationBean keycloakCorsFilterRegistrationBean(KeycloakCorsFilter filter) { FilterRegistrationBean registration = new FilterRegistrationBean(filter); registration.setEnabled(false); return registration; } } ``` 這個配置中,我們繼承了 `KeycloakWebSecurityConfigurerAdapter` 類,這個類提供了 Keycloak 的 Spring Security 配置。我們使用 `authenticationProvider()` 方法來配置 Keycloak 的身份驗證提供者,使用 `keycloakAuthenticationProcessingFilter()` 方法來配置 Keycloak 的身份驗證過濾器。我們還使用 `configure()` 方法來配置 URL 路徑的授權,這裡的 `/api/**` URL 路徑需要進行身份驗證,其他路徑則不需要。 在上面的代碼中,我們還配置了一些過濾器的 Bean,這些過濾器在 ## 後端框架為 Spring Boot,並搭配外部的 Keycloak,該怎麼實作來自 LIFF 的 access token 呢 如果你的後端框架是 Spring Boot,並且你使用外部的 Keycloak 來進行身份驗證和授權,你可以使用 Spring Security 框架來實現 LIFF 傳遞的 access token 的驗證和授權。 以下是一個簡單的 Spring Security 配置,展示如何使用 Keycloak 進行身份驗證和授權,並從 LIFF 傳遞的 access token 中驗證授權: ```java @Configuration @EnableWebSecurity public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter { @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(keycloakAuthenticationProvider()); } @Bean public KeycloakSpringBootConfigResolver keycloakConfigResolver() { return new KeycloakSpringBootConfigResolver(); } @Override protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); } @Override protected void configure(HttpSecurity http) throws Exception { super.configure(http); http.authorizeRequests() .antMatchers("/api/**").hasAnyRole("user") .anyRequest().permitAll() .and() .oauth2ResourceServer() .jwt() .decoder(jwtDecoder()); } @Bean public JwtDecoder jwtDecoder() { return JwtDecoders.fromIssuerLocation("<ISSUER_URL>"); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider(); keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper()); auth.authenticationProvider(keycloakAuthenticationProvider); } } ``` 在這個配置中,我們使用 oauth2ResourceServer() 方法來配置 Spring Security 能夠接受來自 LIFF 傳遞的 access token,使用 `jwt()` 方法來指定 JWT 設定。並在 `jwtDecoder()` 方法中指定 JWT 的簽發者 URL。這樣 Spring Security 就可以在接收到 LIFF 傳遞的 access token 後,使用指定的 JWT 簽發者 URL 進行驗證。 另外,我們在 `configure()` 方法中使用 `hasAnyRole("user")` 方法來授予 `/api/**` URL 路徑的 `user` 角色權限,這樣只有具有 `user` 角色的用戶才能訪問這些 URL 路徑。 最後,我們在 `configure(AuthenticationManagerBuilder auth)` 方法中設置了 `KeycloakAuthenticationProvider` 的 `GrantedAuthoritiesMapper`,並使用 `authenticationProvider()` 方法來配置 Keycloak 的身份驗證提供者。這樣就能從 LIFF 傳遞的 access token 中解析出用戶的角色,並將其授予 Spring Security 中對應的角色權限。 ## 那外部 Keycloak 該如何設定以配合前述情境 要設置外部的 Keycloak 以配合 Spring Boot 中使用 LIFF 傳遞的 access token,你可以遵循以下步驟: 1. 創建一個新的 Keycloak 客戶端,用於與 LIFF 應用程序集成。在 Keycloak 中,轉到 Clients -> Create,然後輸入客戶端的詳細信息,例如客戶端 ID 和描述。 2. 配置新創建的 Keycloak 客戶端。在 Keycloak 中,轉到 **Clients** -> **ClientID** -> **Settings**,然後執行以下操作: 1. 在 “Client Protocol” 下拉菜單中,選擇 “openid-connect”。 2. 在 “Access Type” 下拉菜單中,選擇 “confidential”。 3. 在 “Valid Redirect URIs” 欄位中,輸入 LIFF 應用程序的 URL。例如,如果 LIFF 應用程序的 URL 為 https://example.com,則輸入 https://example.com/*。 4. 在 “Web Origins” 欄位中,輸入 LIFF 應用程序的 URL。例如,如果 LIFF 應用程序的 URL 為 https://example.com,則輸入 https://example.com。 5. 確定保存更改。 3. 配置 Keycloak 的用戶和角色。在 Keycloak 中,轉到 Users,然後創建新用戶。然後,在 Roles 下創建一些角色,例如 `user`。 4. 為 LIFF 應用程序生成一個專用的 access token。在 Keycloak 中,轉到 **Clients** -> **Client ID** -> **Credentials**,然後點擊 **Regenerate Secret** 生成新的 Client Secret。 5. 最後,將新生成的 Client Secret 設置為 Spring Boot 應用程序的參數,並配置 Spring Security 使用 Keycloak 進行身份驗證和授權,如上所述。 ## 這樣 Keycloak 就能從 LIFF 傳來的 access token 解析出用戶身分並保護 REST API 嗎 是的,這樣配置之後,當 LIFF 應用程序使用 access token 訪問 Spring Boot 的 REST API 時,Keycloak 可以使用 access token 中的信息進行用戶身份驗證,並根據用戶角色或權限保護 REST API 的訪問。 在 Spring Boot 應用程序中,你需要使用 Keycloak Spring Security Adapter 來實現此功能。Keycloak Spring Security Adapter 是一個 Keycloak 提供的用於 Spring Security 應用程序的庫,可用於簡化 Spring Security 的集成和配置。 在 Spring Boot 應用程序中,你可以將 Keycloak Spring Security Adapter 添加到依賴中,然後在 Spring Security 的配置中配置 Keycloak 的身份驗證和授權功能。這樣配置之後,當用戶訪問受保護的 REST API 時,Spring Security 將使用 Keycloak 中的用戶信息進行身份驗證和授權,並根據用戶角色或權限控制訪問權限。 ## 我不太理解 Keycloak 是怎麼解析出用戶身分的,不需要配置 JWKS 等等之類的嗎 當 LIFF 應用程序使用 access token 訪問 Spring Boot 的 REST API 時,Keycloak 將使用 access token 中的 JWT (JSON Web Token) 進行身份驗證。JWT 是一種開放標準,用於在不同應用程序之間傳遞安全信息。JWT 由三部分組成:Header、Payload 和 Signature。 在 JWT 中,Payload 包含有關用戶身份的信息,例如用戶名、電子郵件地址和角色。Keycloak 可以使用 JWT 的內容進行用戶身份驗證和授權。 在 LIFF 應用程序中,當使用者進行登入後,LIFF 會透過 Keycloak 發出 token 的 request,Keycloak 則會進行身份驗證,並在使用者身份驗證成功後返回一個包含 JWT 的 access token 給 LIFF 應用程序。 當 LIFF 應用程序使用 access token 訪問 Spring Boot 的 REST API 時,Keycloak 將從 access token 中解析出 JWT,然後使用 JWT 的內容進行用戶身份驗證和授權。因此,你不需要額外配置 JWKS 或其他類似的設置,只需要配置好 Keycloak 的 client 和角色即可。 ## 這種情況下,當有一個新的用戶產生時 Keycloak 怎麼知道呢 當有一個新的用戶產生時,你需要在 Keycloak 中手動創建用戶。你可以在 Keycloak 管理介面中的 Users 頁面上添加新用戶,或者使用 Keycloak 的 API 創建用戶。 在創建用戶時,你可以為用戶分配角色或權限,並將其關聯到相應的 client 上。在這種情況下,當用戶使用 LIFF 應用程序訪問 Spring Boot 的 REST API 時,Keycloak 將使用 JWT 的內容進行用戶身份驗證和授權,並根據用戶角色或權限控制訪問權限。 如果你希望自動創建 Keycloak 用戶,可以使用 Keycloak 的 API 進行自動化創建。你可以通過調用 Keycloak 的 REST API,使用相應的 API 請求在 Keycloak 中創建新用戶。但是,這需要你進行更多的開發工作,並需要保證安全性和可靠性。 ## 使用 API 自動創建 Keycloak 用戶在 Spring Boot 該怎麼做 要在 Spring Boot 中使用 Keycloak 的 API 創建用戶,你需要使用 Keycloak 的 Java 客戶端庫。以下是一個簡單的例子,展示了如何使用 Java 客戶端庫創建 Keycloak 用戶: 首先,你需要在你的 Spring Boot 項目中添加 Keycloak Java 客戶端庫的依賴項。你可以在 Maven 或 Gradle 配置文件中添加以下依賴項: ```xml <dependency> <groupId>org.keycloak</groupId> <artifactId>keycloak-admin-client</artifactId> <version>16.1.0</version> </dependency> ``` 或 ```xml implementation 'org.keycloak:keycloak-admin-client:16.1.0' ``` 然後,在你的 Spring Boot 項目中,你可以使用以下代碼創建一個 Keycloak 用戶: ```java // 創建 Keycloak 客戶端 Keycloak keycloak = KeycloakBuilder.builder() .serverUrl("http://localhost:8080/auth") .realm("your_realm") .clientId("your_client_id") .clientSecret("your_client_secret") .username("admin") .password("admin_password") .build(); // 創建用戶 UserRepresentation user = new UserRepresentation(); user.setUsername("user123"); user.setEmail("user123@example.com"); user.setEnabled(true); // 添加用戶到 Keycloak 中 Response response = keycloak.realm("your_realm").users().create(user); if (response.getStatus() != 201) { throw new RuntimeException("Failed to create user"); } ``` 這個代碼片段中,我們使用 KeycloakBuilder 創建了一個 Keycloak 客戶端,並使用客戶端創建了一個新用戶。在創建用戶時,我們還可以為用戶指定角色或其他屬性。最後,我們使用 Keycloak 的 REST API 將用戶添加到 Keycloak 中。 注意,你需要使用你自己的 Keycloak 設置替換上面代碼中的 "your_realm"、"your_client_id"、"your_client_secret"、"admin" 和 "admin_password" 等參數。 這樣,你就可以使用 Keycloak 的 Java 客戶端庫創建 Keycloak 用戶了。 ## 前述 Keycloak 參數 "your_realm"、"your_client_id"、"your_client_secret"、"admin" 和 "admin_password" 等能設置在 application.properties 嗎 是的,你可以將 Keycloak 的參數設置在 Spring Boot 的 `application.properties` 或 `application.yml` 文件中,並使用 Spring Boot 的屬性注入來注入這些參數。以下是一個使用 `application.properties` 文件設置 Keycloak 參數的例子: 在 `application.properties` 文件中添加以下配置: ```properties # Keycloak 配置 keycloak.auth-server-url=http://localhost:8080/auth keycloak.realm=your_realm keycloak.resource=your_client_id keycloak.credentials.secret=your_client_secret keycloak.admin.username=admin keycloak.admin.password=admin_password ``` 然後,在你的 Java 代碼中使用 @Value 注解來注入這些參數。例如,你可以創建一個名為 "keycloakConfig" 的類,並使用以下代碼來注入 Keycloak 參數: ```java @Component @ConfigurationProperties(prefix = "keycloak") public class KeycloakConfig { private String authServerUrl; private String realm; private String resource; private String credentialsSecret; private String adminUsername; private String adminPassword; // 省略 getter 和 setter 方法 } ``` 在這個例子中,我們使用 @Component 和 @ConfigurationProperties 注解將這個類聲明為 Spring Bean,並注入了 Keycloak 的各個參數。 然後,在你的代碼中,你可以使用 @Autowired 注解來注入 KeycloakConfig 類,並從這個類中獲取 Keycloak 的各個參數,例如: ```java @Autowired private KeycloakConfig keycloakConfig; public void createKeycloakUser() { // 創建 Keycloak 客戶端 Keycloak keycloak = KeycloakBuilder.builder() .serverUrl(keycloakConfig.getAuthServerUrl()) .realm(keycloakConfig.getRealm()) .clientId(keycloakConfig.getResource()) .clientSecret(keycloakConfig.getCredentialsSecret()) .username(keycloakConfig.getAdminUsername()) .password(keycloakConfig.getAdminPassword()) .build(); // 創建用戶 UserRepresentation user = new UserRepresentation(); user.setUsername("user123"); user.setEmail("user123@example.com"); user.setEnabled(true); // 添加用戶到 Keycloak 中 Response response = keycloak.realm(keycloakConfig.getRealm()).users().create(user); if (response.getStatus() != 201) { throw new RuntimeException("Failed to create user"); } } ``` 這樣,你就可以在 Spring Boot 中使用 application.properties 文件設置 Keycloak 參數並使用 Spring Boot 的屬性注入來注入這些參數了。 ## 前述使用 @Autowired 注解來注入 KeycloakConfig 類,是要在哪個類裡注入 你可以在需要使用 Keycloak 參數的 Spring Bean 中注入 KeycloakConfig 類,以便在這個 Bean 中使用這些參數。例如,如果你需要在一個 UserService 中創建 Keycloak 用戶,你可以在這個 UserService 中注入 KeycloakConfig 類,並使用這個類中的參數創建 Keycloak 客戶端。以下是一個示例: ```java @Service public class UserService { @Autowired private KeycloakConfig keycloakConfig; public void createUser(UserDto userDto) { // 創建 Keycloak 客戶端 Keycloak keycloak = KeycloakBuilder.builder() .serverUrl(keycloakConfig.getAuthServerUrl()) .realm(keycloakConfig.getRealm()) .clientId(keycloakConfig.getResource()) .clientSecret(keycloakConfig.getCredentialsSecret()) .username(keycloakConfig.getAdminUsername()) .password(keycloakConfig.getAdminPassword()) .build(); // 創建用戶 UserRepresentation user = new UserRepresentation(); user.setUsername(userDto.getUsername()); user.setEmail(userDto.getEmail()); user.setEnabled(true); // 添加用戶到 Keycloak 中 Response response = keycloak.realm(keycloakConfig.getRealm()).users().create(user); if (response.getStatus() != 201) { throw new RuntimeException("Failed to create user"); } } } ``` 在這個例子中,我們在 UserService 中使用 @Autowired 注解來注入 KeycloakConfig 類,並使用這個類中的參數創建 Keycloak 客戶端。這樣,當 UserService 的 createUser 方法被調用時,就可以使用這個 Keycloak 客戶端創建用戶了。 ## 若 `response.getStatus() != 201` 除了拋出 `RuntimeException` 還有沒有更好的實踐 當 Keycloak 創建用戶失敗時,拋出 RuntimeException 可以讓呼叫者知道出了什麼問題。不過,更好的實踐是使用自定義的異常類型,以便區分不同類型的錯誤。這樣做可以提高代碼的可讀性和可維護性,也更容易進行錯誤處理和調試。 你可以創建一個自定義的異常類型,例如 UserCreationFailedException,並在 createUser 方法中捕獲 Keycloak 創建用戶時可能拋出的異常,然後拋出 UserCreationFailedException 異常,以便在外層進行處理。以下是一個示例: ```java @Service public class UserService { // ... public void createUser(UserDto userDto) throws UserCreationFailedException { // ... // 添加用戶到 Keycloak 中 Response response = keycloak.realm(keycloakConfig.getRealm()).users().create(user); if (response.getStatus() != 201) { throw new UserCreationFailedException("Failed to create user"); } } } ``` 在這個例子中,我們定義了一個 UserCreationFailedException 異常類型,並在 createUser 方法中拋出這個異常,以便在外層進行處理。這樣,當創建用戶失敗時,就可以捕獲這個異常,並進行相應的處理。 ## 前述的 `KeycloakAuthenticationProvider` 中有個 `JwtDecoder` 的 `@Bean`,它的依存性是什麼 在 `KeycloakAuthenticationProvider` 中使用的 `JwtDecoder` `@Bean` 的依存性是 `org.springframework.security.oauth2.jwt.JwtDecoder`。這個依存性是 Spring Security OAuth2 中的一部分,用於解碼 JWT(JSON Web Tokens)。 在 Spring Security OAuth2 中,JwtDecoder 用於解碼 JWT,然後將其轉換為 OAuth2AuthenticationToken。JwtDecoder 的實現方式可以是對稱加密、非對稱加密、JWK Set 等,因此可以支持多種加密方式。 在 KeycloakAuthenticationProvider 中,JwtDecoder 用於解碼從 Keycloak 返回的 ID Token,並使用其內容創建 Authentication 對象。簡單來說,JwtDecoder 確保 ID Token 的有效性,並使用其內容創建一個 Authentication 對象,以便 Spring Security 進行授權。 為了使用 JwtDecoder,我們需要將 `org.springframework.boot:spring-boot-starter-oauth2-client` 依存性添加到 Maven 或 Gradle 項目中,或者將 spring-security-oauth2-client 依存性添加到舊版的 Spring Security 項目中。這樣,Spring Security 就能夠自動配置 JwtDecoder,以便使用 JWT 進行身份驗證和授權。