密码初始化
Based on my analysis of the deploy scripts, here is how the user creation and password handling process works:
User and Password Input:
- The main script,
scripts/deploy/secretpad.sh, is responsible for setting up the environment. - Inside this script, the
account_settingsfunction prompts the interactive user to enter a username and password. - It performs basic validation on the username (
check_user_name) and password (check_user_passwd) complexity. - If a password is not provided or is invalid after a few tries, a random one is generated by the
generate_passwordfunction.
- The main script,
Passing Credentials to the Application:
- The shell scripts do not calculate the password hash.
- Instead, the plaintext username and password, stored in the
SECRETPAD_USER_NAMEandSECRETPAD_PASSWORDenvironment variables, are passed directly into thesecretpadDocker container when it’s started. You can see this in thedocker runcommand within thestartfunction ofsecretpad.sh.
Password Hashing (Inside the Java Application):
- The password hashing itself happens within the Java application running inside the container. The application reads these environment variables on startup, calculates the hash, and then stores the result in the
user_accountstable.
- The password hashing itself happens within the Java application running inside the container. The application reads these environment variables on startup, calculates the hash, and then stores the result in the
Here is the breakdown of how, when, and where the password hash is calculated:
When: The process happens when the Secret-Pad application starts up.
Where: The logic is located in the
DbDataInit.javafile atsecretpad-web/src/main/java/org/secretflow/secretpad/web/init/DbDataInit.java.How:
- The
initUserAndPwdmethod inside theDbDataInitclass is triggered on startup. - It reads the username and password from configuration properties (
secretpad.auth.pad_nameandsecretpad.auth.pad_pwd). ==These properties are populated by theSECRETPAD_USER_NAMEandSECRETPAD_PASSWORDenvironment variables you saw in the shell scripts.== - It then calculates the password hash using the
Sha256Utils.hash(password)method. - The resulting SHA-256 hash is stored in the
passwordHashfield of theAccountsDOentity, which is then saved to theuser_accountstable in your database.
In short, the application uses a SHA-256 hashing algorithm to protect the user’s password.
- The
init script
please tell me when these init function is called. And where is this configuration? @secretpad-web/src/main/java/org/secretflow/secretpad/web/init/
The classes you listed implement either ==CommandLineRunner or ApplicationRunner==. In Spring Boot, any bean that implements these interfaces will have its run() method executed after the application context has been loaded, but before the application starts accepting requests.
@Orderannotation: This determines the sequence of execution.-
@Order(Ordered.HIGHEST_PRECEDENCE): Runs first.DbDataInithas this, so it is executed before the others. -
@Order(Ordered.LOWEST_PRECEDENCE): Runs last. Most other initializers have this, meaning they run afterDbDataInitin an otherwise undetermined order.
-
CommandLineRunner the method itself runs every time you launch the program.
The run method is executed once every time the application starts.
Here is the sequence of events:
- You start the Secretpad application (e.g., by running the JAR file).
- Spring Boot initializes and loads all its components.
- As part of this startup process, Spring Boot finds the
DbDataInitbean and executes itsrun()method. - The application is now fully running.
- You stop the application.
— LATER —
You start the Secretpad application again.
Spring Boot initializes again.
It finds the
DbDataInitbean and executes itsrun()method again.So, the method itself runs every time you launch the program.
改密码的逻辑
You are correct. The run method in DbDataInit is executed every time the application starts, but the account is only created once.
This is due to two main reasons:
CommandLineRunnerLifecycle: As aCommandLineRunner, Spring Boot guarantees that therunmethod is executed only one time during the application’s startup sequence. ==It doesn’t run again unless the application is restarted.==Defensive Code Logic: The
initUserAndPwdmethod is written defensively to prevent creating duplicate users. Let’s look at the key lines:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39// 1. Try to find a user with the configured name in the database.
Optional<AccountsDO> accountsDOOptional = userAccountsRepository.findByName(accountsDO.getName());
// 2. Check if the user was found. if (accountsDOOptional.isEmpty()) {
// 3. If the user does NOT exist, save the new account. This only happens on the very first run. userAccountsRepository.save(accountsDO);
} else { // 4. If the user ALREADY exists, just log a message and update the password. log.info("user {} already exists update password", username);
accountsDOOptional.get().setPasswordHash(Sha256Utils.hash(password));
userAccountsRepository.save(accountsDOOptional.get());
} ```
In summary:
* **On the first application startup**: The database is empty, `findByName` returns nothing, the `if` block is executed, and the account is created.
* **On every subsequent startup**: `findByName` finds the account created previously, the `else` block is executed, and the existing account's password is simply updated. No new account is created.
This pattern makes the initialization process **idempotent**—meaning it can run multiple times without changing the result beyond the initial execution.
---
# 第一个api,api/v1alpha1/p2p/project/list
When you call the `api/v1alpha1/p2p/project/list` API, the following functions are called in order:
1. `org.secretflow.secretpad.web.controller.p2p.P2PProjectController.listP2PProject(...)`
2. `org.secretflow.secretpad.service.impl.ProjectServiceImpl.listP2PProject(...)`
This API call **does not modify any tables** in the database. It is a read-only operation that retrieves project information from the following tables:
* `project_inst`
* `project_approval_config`
* `project`
* `vote_request`
* `project_node`
* `project_graph`
* `project_job`
* `inst`
## ApiResource 注解@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiResource {/**
- api code * * @return api resource code {@link org.secretflow.secretpad.common.constant.resource.ApiResourceCodeConstants}
- / String code();
}
* `@Target(ElementType.METHOD)`: This means the annotation can only be applied to methods.
* `@Retention(RetentionPolicy.RUNTIME)`: This means the annotation is available at runtime, which allows for frameworks or custom logic to inspect it.
* `String code();`: This defines a single attribute named `code` that takes a `String` value. **==这里的code 不是函数,见[[Java 中的注解 annotation|Java 中的注解 annotation]]==**
In the `P2PProjectController`, you can see it applied to the API methods:
```java
@ResponseBody
@PostMapping(value = "/list", consumes = "application/json")
@Operation(summary = "list project", description = "list project")
@ApiResource(code = ApiResourceCodeConstants.PRJ_LIST)
public SecretPadResponse<List<ProjectVO>> listP2PProject() {
return SecretPadResponse.success(projectService.listP2PProject());}
In this example, @ApiResource(code = ApiResourceCodeConstants.PRJ_LIST) associates the listP2PProject method with the resource code PRJ_LIST.
The values for the code attribute are defined as constants in the ApiResourceCodeConstants class. For example, PRJ_LIST is defined as:
public static final String PRJ_LIST = "PRJ_LIST";```
By using this annotation, the application can implement cross-cutting concerns like checking if a user has the necessary permissions to access a specific API endpoint based on the resource code.
## 注解 与授权的关系
The `@ApiResource` annotation, in conjunction with `PRJ_LIST`, is directly related to permission control in the application. Here's how it works:
**1. Annotation and Resource Code:**
As we've seen, the `@ApiResource` annotation is used to mark API endpoint methods. It takes a `code` as a parameter, which is a `String` that uniquely identifies that API resource. In the case of the project list API, the code is `PRJ_LIST`.
```java
@ApiResource(code = ApiResourceCodeConstants.PRJ_LIST)
public SecretPadResponse<List<ProjectVO>> listP2PProject() {
// ...}
2. AOP-based Interception:
The application uses Aspect-Oriented Programming (AOP) to intercept calls to methods annotated with @ApiResource. This is done in the InterfaceResourceAspect class.
This aspect defines a “pointcut” that targets any method annotated with @ApiResource. When such a method is called, the check method in the aspect is executed before the actual API method.
3. Permission Checking Logic:
The check method in InterfaceResourceAspect extracts the code from the @ApiResource annotation (e.g., PRJ_LIST) and calls the apiResourceAuth.check(apiResource.code()) method.
The DefaultApiResourceAuth class implements the ApiResourceAuth interface and contains the core permission checking logic. It does the following:
It retrieves the current user’s information from the
UserContext.It checks if the user has the
ALL_INTERFACE_RESOURCEpermission. If so, the check passes.If not, it checks if the user’s
apiResources(a set of resource codes they are allowed to access) contains thecodefrom the annotation (e.g.,PRJ_LIST).*4. UserContext and Permissions:**
The
UserContextholds the currently logged-in user’s details, including a set of strings representing the API resources they are permitted to access. When a user logs in, their permissions are fetched and stored in theUserContext.In summary, the workflow is as follows:
- A user makes a request to an API endpoint (e.g.,
/api/v1alpha1/p2p/project/list). - The
InterfaceResourceAspectintercepts the call to the controller method because it’s annotated with@ApiResource. - The aspect extracts the resource code (
PRJ_LIST) from the annotation. - It calls the
ApiResourceAuthservice to check if the current user has permission for that resource code. - The
DefaultApiResourceAuthservice checks the user’s permissions stored in theUserContext. - If the user has the
PRJ_LISTpermission (or theALL_INTERFACE_RESOURCEpermission), the check passes, and the controller method is executed. - If the user does not have the required permission, an exception is thrown, and the user is denied access.
注解的详细讲解
Of course. Let’s break down what’s happening. It’s a great question because the magic isn’t in the @ApiResource annotation itself, but in how the application uses it.
1. @interface is not a regular interface
First, a key clarification: In Java, @interface is used to declare an annotation, not a standard interface that a class would implement.
An
interfacedefines a contract (a set of methods) that a class must implement.An
@interfacedefines a new type of annotation. The annotation itself is just metadata; it’s a marker that can be placed on code, but it doesn’t contain any logic on its own.So, you won’t find a class that says
class MyClass implements ApiResource. That’s not how annotations work.2. How it’s Used: Aspect-Oriented Programming (AOP)
The “implementation” you’re looking for is not in a class that implements
@ApiResource, but in a class that reads the annotation and acts on it. This is done using a powerful programming paradigm called Aspect-Oriented Programming (AOP).In this project, the class
InterfaceResourceAspect.javais an “aspect”. Think of it as a special module that can intercept and add behavior to existing code without modifying that code directly.Here is the relevant code from
InterfaceResourceAspect.java:@Aspect @Component public class InterfaceResourceAspect { // ... @Autowired private ApiResourceAuth apiResourceAuth; @Pointcut("@annotation(org.secretflow.secretpad.common.annotation.resource.ApiResource)") public void pointCut() { } @Around("pointCut() && args(data) && @annotation(apiResource)") public Object check(ProceedingJoinPoint joinPoint, Object data, ApiResource apiResource) throws Throwable { // ... if (!apiResourceAuth.check(apiResource.code())) { throw SecretpadException.of(AuthErrorCode.AUTH_FAILED, apiResource.code()); } return joinPoint.proceed(); } // ...}3. The Permission Control Workflow
Here is the step-by-step process that connects the annotation to the permission check:
A Request Arrives: A user calls the
/api/v1alpha1/p2p/project/listendpoint.AOP Interception: The Spring Framework, which powers the application, sees that the
listP2PProjectmethod is annotated with@ApiResource. Because of the@Aspectand@Pointcutconfiguration inInterfaceResourceAspect, it intercepts the call before thelistP2PProjectmethod’s code is executed.The
checkMethod Runs: The@Aroundadvice in the aspect is triggered, and itscheckmethod is executed.Metadata is Read: The
checkmethod receives theApiResourceannotation instance as a parameter. It then callsapiResource.code()to get the string value you provided (in this case,PRJ_LIST).Permission Check is Delegated: The aspect then calls
apiResourceAuth.check("PRJ_LIST"). Here,apiResourceAuthis an instance of a class that implements theApiResourceAuthinterface. The actual implementation isDefaultApiResourceAuth.The Real Check Happens: The
DefaultApiResourceAuthclass performs the logic:- It gets the current user’s data from the
UserContext. - It checks if the user’s list of permissions (stored in
UserContext.apiResources) contains the string"PRJ_LIST".
- It gets the current user’s data from the
Execution Proceeds or Fails:
- If the
checkmethod returnstrue, the aspect callsjoinPoint.proceed(), which allows the originallistP2PProjectmethod to finally execute. - If the
checkmethod returnsfalse, the aspect throws anAuthErrorCode.AUTH_FAILEDexception, and the request is denied with a permission error. The controller method is never executed.
So, the
@ApiResourceannotation is not an interface to be implemented. It is a marker whose data is read by an AOP aspect, which then triggers the actual permission-checking logic.- If the