# 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 進行身份驗證和授權。