mirror of
https://github.com/vran-dev/databasir.git
synced 2025-08-08 17:32:14 +08:00
feat: init api (#2)
This commit is contained in:
10
api/Dockerfile
Normal file
10
api/Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
||||
FROM openjdk:11.0.13-jre
|
||||
ARG service_name_folder
|
||||
WORKDIR /app
|
||||
ADD databasir.jar /app
|
||||
EXPOSE 8080
|
||||
|
||||
#-Ddatabasir.datasource.username=${databasir.datasource.username}
|
||||
#-Ddatabasir.datasource.password=${databasir.datasource.password}
|
||||
#-Ddatabasir.datasource.url=${databasir.datasource.url}
|
||||
ENTRYPOINT ["sh", "-c","java ${JAVA_OPTS} -jar /app/databasir.jar"]
|
41
api/build.gradle
Normal file
41
api/build.gradle
Normal file
@@ -0,0 +1,41 @@
|
||||
plugins {
|
||||
id 'io.spring.dependency-management'
|
||||
id 'org.springframework.boot' apply false
|
||||
}
|
||||
|
||||
bootJar {
|
||||
archiveBaseName = 'databasir'
|
||||
archiveVersion = ''
|
||||
enabled = true
|
||||
}
|
||||
|
||||
bootBuildImage {
|
||||
imageName = "${project.group}/databasir:${project.version}"
|
||||
publish = false
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(":common")
|
||||
implementation project(":plugin")
|
||||
implementation project(":core")
|
||||
implementation project(":dao")
|
||||
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-aop'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'org.flywaydb:flyway-core'
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Docker
|
||||
*/
|
||||
task copyDockerfile(type: Copy) {
|
||||
from("Dockerfile")
|
||||
into("build/libs")
|
||||
}
|
||||
|
||||
bootJar.finalizedBy copyDockerfile
|
||||
assemble.finalizedBy copyDockerfile
|
13
api/src/main/java/com/databasir/DatabasirApplication.java
Normal file
13
api/src/main/java/com/databasir/DatabasirApplication.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.databasir;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
|
||||
|
||||
@SpringBootApplication(exclude = {R2dbcAutoConfiguration.class})
|
||||
public class DatabasirApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DatabasirApplication.class, args);
|
||||
}
|
||||
}
|
44
api/src/main/java/com/databasir/api/DocumentController.java
Normal file
44
api/src/main/java/com/databasir/api/DocumentController.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package com.databasir.api;
|
||||
|
||||
import com.databasir.common.JsonData;
|
||||
import com.databasir.core.domain.document.data.DatabaseDocumentResponse;
|
||||
import com.databasir.core.domain.document.data.DatabaseDocumentVersionResponse;
|
||||
import com.databasir.core.domain.document.service.DocumentService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Validated
|
||||
public class DocumentController {
|
||||
|
||||
private final DocumentService documentService;
|
||||
|
||||
@PostMapping(Routes.Document.SYNC_ONE)
|
||||
public JsonData<Void> sync(@PathVariable Integer projectId) {
|
||||
documentService.syncByProjectId(projectId);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@GetMapping(Routes.Document.GET_ONE)
|
||||
public JsonData<DatabaseDocumentResponse> getByProjectId(@PathVariable Integer projectId,
|
||||
@RequestParam(required = false) Long version) {
|
||||
return documentService.getOneByProjectId(projectId, version)
|
||||
.map(JsonData::ok)
|
||||
.orElseGet(JsonData::ok);
|
||||
}
|
||||
|
||||
@GetMapping(Routes.Document.LIST_VERSIONS)
|
||||
public JsonData<Page<DatabaseDocumentVersionResponse>> getVersionsByProjectId(@PathVariable Integer projectId,
|
||||
@PageableDefault(sort = "id", direction = Sort.Direction.DESC)
|
||||
Pageable page) {
|
||||
return JsonData.ok(documentService.getVersionsBySchemaSourceId(projectId, page));
|
||||
}
|
||||
|
||||
}
|
106
api/src/main/java/com/databasir/api/GroupController.java
Normal file
106
api/src/main/java/com/databasir/api/GroupController.java
Normal file
@@ -0,0 +1,106 @@
|
||||
package com.databasir.api;
|
||||
|
||||
import com.databasir.api.validator.UserOperationValidator;
|
||||
import com.databasir.common.JsonData;
|
||||
import com.databasir.core.domain.group.data.*;
|
||||
import com.databasir.core.domain.group.service.GroupService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Validated
|
||||
public class GroupController {
|
||||
|
||||
private final GroupService groupService;
|
||||
|
||||
private final UserOperationValidator userOperationValidator;
|
||||
|
||||
@PostMapping(Routes.Group.CREATE)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
|
||||
public JsonData<Void> create(@RequestBody @Valid GroupCreateRequest request) {
|
||||
groupService.create(request);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@PatchMapping(Routes.Group.UPDATE)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER'.concat('?groupId='.concat(#request.id)))")
|
||||
public JsonData<Void> update(@RequestBody @Valid GroupUpdateRequest request) {
|
||||
groupService.update(request);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@GetMapping(Routes.Group.LIST)
|
||||
public JsonData<Page<GroupPageResponse>> list(@PageableDefault(sort = "id", direction = Sort.Direction.DESC)
|
||||
Pageable page,
|
||||
GroupPageCondition condition) {
|
||||
return JsonData.ok(groupService.list(page, condition));
|
||||
}
|
||||
|
||||
@DeleteMapping(Routes.Group.DELETE)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER'.concat('?groupId='.concat(#groupId)))")
|
||||
public JsonData<Void> deleteById(@PathVariable Integer groupId) {
|
||||
groupService.delete(groupId);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@GetMapping(Routes.Group.GET_ONE)
|
||||
public JsonData<GroupResponse> getOne(@PathVariable Integer groupId) {
|
||||
return JsonData.ok(groupService.get(groupId));
|
||||
}
|
||||
|
||||
@GetMapping(Routes.Group.MEMBERS)
|
||||
public JsonData<Page<GroupMemberPageResponse>> listGroupMembers(@PathVariable Integer groupId,
|
||||
@PageableDefault(sort = "user_role.create_at", direction = Sort.Direction.DESC)
|
||||
Pageable pageable,
|
||||
GroupMemberPageCondition condition) {
|
||||
return JsonData.ok(groupService.listGroupMembers(groupId, pageable, condition));
|
||||
}
|
||||
|
||||
@PostMapping(Routes.Group.ADD_MEMBER)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER'.concat('?groupId='.concat(#groupId)))")
|
||||
public JsonData<Void> addGroupMember(@PathVariable Integer groupId,
|
||||
@RequestBody @Valid GroupMemberCreateRequest request) {
|
||||
userOperationValidator.forbiddenIfUpdateSelfRole(request.getUserId());
|
||||
List<String> groupRoles = Arrays.asList("GROUP_OWNER", "GROUP_MEMBER");
|
||||
if (!groupRoles.contains(request.getRole())) {
|
||||
throw new IllegalArgumentException("role should be GROUP_OWNER or GROUP_MEMBER");
|
||||
}
|
||||
groupService.addMember(groupId, request);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping(Routes.Group.DELETE_MEMBER)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER'.concat('?groupId='.concat(#groupId)))")
|
||||
public JsonData<Void> removeGroupMember(@PathVariable Integer groupId,
|
||||
@PathVariable Integer userId) {
|
||||
userOperationValidator.forbiddenIfUpdateSelfRole(userId);
|
||||
groupService.removeMember(groupId, userId);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@PatchMapping(Routes.Group.UPDATE_MEMBER)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER'.concat('?groupId='.concat(#groupId)))")
|
||||
public JsonData<Void> updateGroupMemberRole(@PathVariable Integer groupId,
|
||||
@PathVariable Integer userId,
|
||||
@RequestBody GroupMemberRoleUpdateRequest request) {
|
||||
userOperationValidator.forbiddenIfUpdateSelfRole(userId);
|
||||
List<String> groupRoles = Arrays.asList("GROUP_OWNER", "GROUP_MEMBER");
|
||||
if (!groupRoles.contains(request.getRole())) {
|
||||
throw new IllegalArgumentException("role should be GROUP_OWNER or GROUP_MEMBER");
|
||||
}
|
||||
groupService.changeMemberRole(groupId, userId, request.getRole());
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
}
|
55
api/src/main/java/com/databasir/api/LoginController.java
Normal file
55
api/src/main/java/com/databasir/api/LoginController.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package com.databasir.api;
|
||||
|
||||
import com.databasir.common.DatabasirException;
|
||||
import com.databasir.common.JsonData;
|
||||
import com.databasir.common.exception.InvalidTokenException;
|
||||
import com.databasir.core.domain.DomainErrors;
|
||||
import com.databasir.core.domain.login.data.AccessTokenRefreshRequest;
|
||||
import com.databasir.core.domain.login.data.AccessTokenRefreshResponse;
|
||||
import com.databasir.core.domain.login.service.LoginService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
import java.util.Objects;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class LoginController {
|
||||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
|
||||
private final LoginService loginService;
|
||||
|
||||
@GetMapping(Routes.Login.LOGOUT)
|
||||
public JsonData<Void> logout() {
|
||||
SecurityContextHolder.clearContext();
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@PostMapping(Routes.Login.REFRESH_ACCESS_TOKEN)
|
||||
public JsonData<AccessTokenRefreshResponse> refreshAccessTokens(@RequestBody @Valid AccessTokenRefreshRequest request,
|
||||
HttpServletResponse response) {
|
||||
try {
|
||||
return JsonData.ok(loginService.refreshAccessTokens(request));
|
||||
} catch (DatabasirException e) {
|
||||
if (Objects.equals(e.getErrCode(), DomainErrors.ACCESS_TOKEN_REFRESH_INVALID.getErrCode())) {
|
||||
throw new InvalidTokenException(DomainErrors.ACCESS_TOKEN_REFRESH_INVALID);
|
||||
}
|
||||
if (Objects.equals(e.getErrCode(), DomainErrors.REFRESH_TOKEN_EXPIRED.getErrCode())) {
|
||||
throw new InvalidTokenException(DomainErrors.REFRESH_TOKEN_EXPIRED);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
58
api/src/main/java/com/databasir/api/ProjectController.java
Normal file
58
api/src/main/java/com/databasir/api/ProjectController.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package com.databasir.api;
|
||||
|
||||
import com.databasir.common.JsonData;
|
||||
import com.databasir.core.domain.project.data.*;
|
||||
import com.databasir.core.domain.project.service.ProjectService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Validated
|
||||
public class ProjectController {
|
||||
|
||||
private final ProjectService projectService;
|
||||
|
||||
@PostMapping(Routes.GroupProject.CREATE)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#request.groupId, 'GROUP_MEMBER?groupId='+#request.groupId)")
|
||||
public JsonData<Void> create(@RequestBody @Valid ProjectCreateRequest request) {
|
||||
projectService.create(request);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@PatchMapping(Routes.GroupProject.UPDATE)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#groupId, 'GROUP_MEMBER?groupId='+#groupId)")
|
||||
public JsonData<Void> update(@RequestBody @Valid ProjectUpdateRequest request,
|
||||
@PathVariable Integer groupId) {
|
||||
projectService.update(groupId, request);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping(Routes.GroupProject.DELETE)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#groupId, 'GROUP_MEMBER?groupId='+#groupId)")
|
||||
public JsonData<Void> delete(@PathVariable Integer groupId,
|
||||
@PathVariable Integer projectId) {
|
||||
projectService.delete(projectId);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@GetMapping(Routes.GroupProject.GET_ONE)
|
||||
public JsonData<ProjectDetailResponse> getOne(@PathVariable Integer projectId) {
|
||||
return JsonData.ok(projectService.getOne(projectId));
|
||||
}
|
||||
|
||||
@GetMapping(Routes.GroupProject.LIST)
|
||||
public JsonData<Page<ProjectSimpleResponse>> list(@PageableDefault(sort = "id", direction = Sort.Direction.DESC)
|
||||
Pageable page,
|
||||
ProjectListCondition condition) {
|
||||
return JsonData.ok(projectService.list(page, condition));
|
||||
}
|
||||
}
|
84
api/src/main/java/com/databasir/api/Routes.java
Normal file
84
api/src/main/java/com/databasir/api/Routes.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package com.databasir.api;
|
||||
|
||||
public interface Routes {
|
||||
|
||||
String BASE = "/api/v1.0";
|
||||
|
||||
interface User {
|
||||
|
||||
String LIST = BASE + "/users";
|
||||
|
||||
String GET_ONE = BASE + "/users/{userId}";
|
||||
|
||||
String ENABLE = BASE + "/users/{userId}/enable";
|
||||
|
||||
String DISABLE = BASE + "/users/{userId}/disable";
|
||||
|
||||
String CREATE = BASE + "/users";
|
||||
|
||||
String UPDATE_PASSWORD = BASE + "/users/{userId}/password";
|
||||
|
||||
String UPDATE_NICKNAME = BASE + "/users/{userId}/nickname";
|
||||
|
||||
String RENEW_PASSWORD = BASE + "/users/{userId}/renew_password";
|
||||
|
||||
String ADD_OR_REMOVE_SYS_OWNER = BASE + "/users/{userId}/sys_owners";
|
||||
}
|
||||
|
||||
interface Group {
|
||||
|
||||
String LIST = BASE + "/groups";
|
||||
|
||||
String GET_ONE = BASE + "/groups/{groupId}";
|
||||
|
||||
String CREATE = BASE + "/groups";
|
||||
|
||||
String UPDATE = BASE + "/groups";
|
||||
|
||||
String DELETE = BASE + "/groups/{groupId}";
|
||||
|
||||
String MEMBERS = GET_ONE + "/members";
|
||||
|
||||
String DELETE_MEMBER = GET_ONE + "/members/{userId}";
|
||||
|
||||
String ADD_MEMBER = GET_ONE + "/members";
|
||||
|
||||
String UPDATE_MEMBER = GET_ONE + "/members/{userId}";
|
||||
}
|
||||
|
||||
interface GroupProject {
|
||||
|
||||
String LIST = BASE + "/projects";
|
||||
|
||||
String GET_ONE = BASE + "/projects/{projectId}";
|
||||
|
||||
String CREATE = BASE + "/projects";
|
||||
|
||||
String UPDATE = BASE + "/groups/{groupId}/projects";
|
||||
|
||||
String DELETE = BASE + "/groups/{groupId}/projects/{projectId}";
|
||||
}
|
||||
|
||||
interface Document {
|
||||
|
||||
String GET_ONE = BASE + "/projects/{projectId}/documents";
|
||||
|
||||
String SYNC_ONE = BASE + "/projects/{projectId}/documents";
|
||||
|
||||
String LIST_VERSIONS = BASE + "/projects/{projectId}/document_versions";
|
||||
}
|
||||
|
||||
interface Setting {
|
||||
|
||||
String GET_SYS_EMAIL = BASE + "/settings/sys_email";
|
||||
|
||||
String UPDATE_SYS_EMAIL = BASE + "/settings/sys_email";
|
||||
}
|
||||
|
||||
interface Login {
|
||||
|
||||
String LOGOUT = "/logout";
|
||||
|
||||
String REFRESH_ACCESS_TOKEN = "/access_tokens";
|
||||
}
|
||||
}
|
35
api/src/main/java/com/databasir/api/SettingController.java
Normal file
35
api/src/main/java/com/databasir/api/SettingController.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package com.databasir.api;
|
||||
|
||||
|
||||
import com.databasir.common.JsonData;
|
||||
import com.databasir.core.domain.system.data.SystemEmailResponse;
|
||||
import com.databasir.core.domain.system.data.SystemEmailUpdateRequest;
|
||||
import com.databasir.core.domain.system.service.SystemService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Validated
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
|
||||
public class SettingController {
|
||||
|
||||
private final SystemService systemService;
|
||||
|
||||
@GetMapping(Routes.Setting.GET_SYS_EMAIL)
|
||||
public JsonData<SystemEmailResponse> getSystemEmailSetting() {
|
||||
return systemService.getEmailSetting()
|
||||
.map(JsonData::ok)
|
||||
.orElseGet(JsonData::ok);
|
||||
}
|
||||
|
||||
@PostMapping(Routes.Setting.UPDATE_SYS_EMAIL)
|
||||
public JsonData<Void> updateSystemEmailSetting(@RequestBody @Valid SystemEmailUpdateRequest request) {
|
||||
systemService.updateEmailSetting(request);
|
||||
return JsonData.ok();
|
||||
}
|
||||
}
|
105
api/src/main/java/com/databasir/api/UserController.java
Normal file
105
api/src/main/java/com/databasir/api/UserController.java
Normal file
@@ -0,0 +1,105 @@
|
||||
package com.databasir.api;
|
||||
|
||||
import com.databasir.api.validator.UserOperationValidator;
|
||||
import com.databasir.common.JsonData;
|
||||
import com.databasir.common.exception.Forbidden;
|
||||
import com.databasir.core.domain.user.data.*;
|
||||
import com.databasir.core.domain.user.service.UserService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Validated
|
||||
public class UserController {
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
private final UserOperationValidator userOperationValidator;
|
||||
|
||||
@GetMapping(Routes.User.LIST)
|
||||
public JsonData<Page<UserPageResponse>> list(@PageableDefault(sort = "id", direction = Sort.Direction.DESC)
|
||||
Pageable pageable,
|
||||
UserPageCondition condition) {
|
||||
return JsonData.ok(userService.list(pageable, condition));
|
||||
}
|
||||
|
||||
@PostMapping(Routes.User.DISABLE)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
|
||||
public JsonData<Void> disableUser(@PathVariable Integer userId) {
|
||||
userService.switchEnableStatus(userId, false);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@PostMapping(Routes.User.ENABLE)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
|
||||
public JsonData<Void> enableUser(@PathVariable Integer userId) {
|
||||
userService.switchEnableStatus(userId, true);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@PostMapping(Routes.User.CREATE)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
|
||||
public JsonData<Void> create(@RequestBody @Valid UserCreateRequest request) {
|
||||
userService.create(request);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@GetMapping(Routes.User.GET_ONE)
|
||||
public JsonData<UserDetailResponse> getOne(@PathVariable Integer userId) {
|
||||
return JsonData.ok(userService.get(userId));
|
||||
}
|
||||
|
||||
@PostMapping(Routes.User.RENEW_PASSWORD)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
|
||||
public JsonData<Void> renewPassword(@PathVariable Integer userId) {
|
||||
userService.renewPassword(userId);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@PostMapping(Routes.User.ADD_OR_REMOVE_SYS_OWNER)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
|
||||
public JsonData<Void> addSysOwner(@PathVariable Integer userId) {
|
||||
userOperationValidator.forbiddenIfUpdateSelfRole(userId);
|
||||
userService.addSysOwnerTo(userId);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping(Routes.User.ADD_OR_REMOVE_SYS_OWNER)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
|
||||
public JsonData<Void> removeSysOwner(@PathVariable Integer userId) {
|
||||
userOperationValidator.forbiddenIfUpdateSelfRole(userId);
|
||||
userService.removeSysOwnerFrom(userId);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@PostMapping(Routes.User.UPDATE_PASSWORD)
|
||||
public JsonData<Void> updatePassword(@PathVariable Integer userId,
|
||||
@RequestBody @Valid UserPasswordUpdateRequest request) {
|
||||
if (userOperationValidator.isMyself(userId)) {
|
||||
userService.updatePassword(userId, request);
|
||||
return JsonData.ok();
|
||||
} else {
|
||||
throw new Forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping(Routes.User.UPDATE_NICKNAME)
|
||||
public JsonData<Void> updateNickname(@PathVariable Integer userId,
|
||||
@RequestBody @Valid UserNicknameUpdateRequest request) {
|
||||
if (userOperationValidator.isMyself(userId)) {
|
||||
userService.updateNickname(userId, request);
|
||||
return JsonData.ok();
|
||||
} else {
|
||||
throw new Forbidden();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,234 @@
|
||||
package com.databasir.api.advice;
|
||||
|
||||
|
||||
import com.databasir.common.DatabasirException;
|
||||
import com.databasir.common.JsonData;
|
||||
import com.databasir.common.SystemException;
|
||||
import com.databasir.common.exception.Forbidden;
|
||||
import com.databasir.common.exception.InvalidTokenException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.TypeMismatchException;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.validation.ObjectError;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
import org.springframework.web.bind.ServletRequestBindingException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.multipart.support.MissingServletRequestPartException;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
public class DatabasirExceptionAdvice extends ResponseEntityExceptionHandler {
|
||||
|
||||
@ExceptionHandler({ConstraintViolationException.class})
|
||||
public ResponseEntity<Object> handleConstraintViolationException(
|
||||
ConstraintViolationException constraintViolationException, WebRequest request) {
|
||||
|
||||
String errorMsg = "";
|
||||
String path = getPath(request);
|
||||
Set<ConstraintViolation<?>> violations = constraintViolationException.getConstraintViolations();
|
||||
for (ConstraintViolation<?> item : violations) {
|
||||
errorMsg = item.getMessage();
|
||||
log.warn("ConstraintViolationException, request: {}, exception: {}, invalid value: {}",
|
||||
path, errorMsg, item.getInvalidValue());
|
||||
break;
|
||||
}
|
||||
return handleNon200Response(errorMsg, HttpStatus.BAD_REQUEST, path);
|
||||
}
|
||||
|
||||
@ExceptionHandler({InvalidTokenException.class})
|
||||
protected ResponseEntity<Object> handleInvalidTokenException(InvalidTokenException ex, WebRequest request) {
|
||||
String path = getPath(request);
|
||||
log.warn("handle InvalidTokenException " + path + ", " + ex);
|
||||
JsonData<Object> data = JsonData.error(ex.getErrCode(), ex.getErrMessage());
|
||||
return handleNon200Response(ex.getMessage(), HttpStatus.UNAUTHORIZED, path, data);
|
||||
}
|
||||
|
||||
@ExceptionHandler({IllegalArgumentException.class})
|
||||
protected ResponseEntity<Object> handleIllegalArgument(IllegalArgumentException ex, WebRequest request) {
|
||||
String path = getPath(request);
|
||||
log.warn("handle illegalArgument " + path, ex);
|
||||
return handleNon200Response(ex.getMessage(), HttpStatus.BAD_REQUEST, path);
|
||||
}
|
||||
|
||||
@ExceptionHandler({AccessDeniedException.class})
|
||||
protected ResponseEntity<Object> handleAccessDeniedException(AccessDeniedException ex, WebRequest request) {
|
||||
String path = getPath(request);
|
||||
log.warn("AccessDeniedException, request: {}, exception: {}", path, ex.getMessage());
|
||||
return handleNon200Response(ex.getMessage(), HttpStatus.FORBIDDEN, path);
|
||||
}
|
||||
|
||||
@ExceptionHandler({Forbidden.class})
|
||||
protected ResponseEntity<Object> handleForbiddenException(Forbidden ex, WebRequest request) {
|
||||
String path = getPath(request);
|
||||
log.warn("Forbidden, request: {}, exception: {}", path, ex.getMessage());
|
||||
return handleNon200Response(ex.getMessage(), HttpStatus.FORBIDDEN, path);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = DatabasirException.class)
|
||||
public ResponseEntity<Object> handleBusinessException(
|
||||
DatabasirException databasirException, WebRequest request) {
|
||||
|
||||
String path = getPath(request);
|
||||
JsonData<Void> body = JsonData.error(databasirException.getErrCode(), databasirException.getErrMessage());
|
||||
if (databasirException.getCause() == null) {
|
||||
log.warn("BusinessException, request: {}, exception: {}", path, databasirException.getErrMessage());
|
||||
} else {
|
||||
log.warn("BusinessException, request: " + path, databasirException);
|
||||
}
|
||||
return ResponseEntity.ok().body(body);
|
||||
}
|
||||
|
||||
@ExceptionHandler({SystemException.class})
|
||||
public ResponseEntity<Object> handleSystemException(SystemException systemException, WebRequest request) {
|
||||
|
||||
String path = getPath(request);
|
||||
if (systemException.getCause() != null) {
|
||||
log.error("SystemException, request: " + path
|
||||
+ ", exception: " + systemException.getMessage() + ", caused by:", systemException.getCause());
|
||||
} else {
|
||||
log.error("SystemException, request: " + path + ", exception: " + systemException.getMessage());
|
||||
}
|
||||
return handleNon200Response(systemException.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR, path);
|
||||
}
|
||||
|
||||
@ExceptionHandler({Exception.class})
|
||||
public ResponseEntity<Object> handleUnspecificException(Exception ex, WebRequest request) {
|
||||
|
||||
String path = getPath(request);
|
||||
String errorMsg = ex.getMessage();
|
||||
log.error("Unspecific exception, request: " + path + ", exception: " + errorMsg + ":", ex);
|
||||
return handleNon200Response(errorMsg, HttpStatus.INTERNAL_SERVER_ERROR, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Object> handleBindException(
|
||||
BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
|
||||
|
||||
String errorMsg = buildMessages(ex.getBindingResult());
|
||||
log.warn("BindException, request: {}, exception: {}", getPath(request), errorMsg);
|
||||
return handleOverriddenException(ex, headers, status, request, errorMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Object> handleMethodArgumentNotValid(
|
||||
MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
|
||||
|
||||
String errorMsg = buildMessages(ex.getBindingResult());
|
||||
log.warn("MethodArgumentNotValidException, request: {}, exception: {}", getPath(request), errorMsg);
|
||||
return handleOverriddenException(ex, headers, status, request, errorMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Object> handleTypeMismatch(
|
||||
TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
|
||||
|
||||
log.warn("TypeMismatchException, request: {}, exception: {}", getPath(request), ex.getMessage());
|
||||
return handleOverriddenException(ex, headers, status, request, ex.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Object> handleMissingServletRequestParameter(
|
||||
MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
|
||||
|
||||
log.warn("MissingServletRequestParameterException, request: {}, exception: {}",
|
||||
getPath(request), ex.getMessage());
|
||||
return handleOverriddenException(ex, headers, status, request, ex.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Object> handleMissingServletRequestPart(
|
||||
MissingServletRequestPartException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
|
||||
|
||||
log.warn("MissingServletRequestPartException, request: {}, exception: {}", getPath(request), ex.getMessage());
|
||||
return handleOverriddenException(ex, headers, status, request, ex.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Object> handleHttpMessageNotReadable(
|
||||
HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
|
||||
|
||||
String errorMsg = ex.getMostSpecificCause().getMessage();
|
||||
|
||||
log.warn("HttpMessageNotReadableException, request: {}, exception: {}", getPath(request), errorMsg);
|
||||
return handleOverriddenException(ex, headers, status, request, errorMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Object> handleServletRequestBindingException(
|
||||
ServletRequestBindingException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
|
||||
|
||||
log.warn("ServletRequestBindingException, request: {}, exception: {}", getPath(request), ex.getMessage());
|
||||
return handleOverriddenException(ex, headers, status, request, ex.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Object> handleHttpRequestMethodNotSupported(
|
||||
HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
|
||||
|
||||
String errorMsg = ex.getMessage();
|
||||
log.warn("HttpRequestMethodNotSupportedException, request: {}, exception: {}", getPath(request), errorMsg);
|
||||
return handleOverriddenException(ex, headers, status, request, ex.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler({BadCredentialsException.class})
|
||||
protected ResponseEntity<Object> handleBadCredentialsException(BadCredentialsException ex, WebRequest request) {
|
||||
String path = getPath(request);
|
||||
JsonData<Void> body = JsonData.error("-1", "用户名或密码错误");
|
||||
log.warn("BadCredentialsException, request: {}, exception: {}", path, ex.getMessage());
|
||||
return ResponseEntity.ok().body(body);
|
||||
}
|
||||
|
||||
private String buildMessages(BindingResult result) {
|
||||
|
||||
StringBuilder resultBuilder = new StringBuilder();
|
||||
List<ObjectError> errors = result.getAllErrors();
|
||||
for (ObjectError error : errors) {
|
||||
if (error instanceof FieldError) {
|
||||
FieldError fieldError = (FieldError) error;
|
||||
String fieldName = fieldError.getField();
|
||||
String fieldErrMsg = fieldError.getDefaultMessage();
|
||||
resultBuilder.append(fieldName).append(" ").append(fieldErrMsg);
|
||||
}
|
||||
}
|
||||
return resultBuilder.toString();
|
||||
}
|
||||
|
||||
private ResponseEntity<Object> handleNon200Response(String errorMsg, HttpStatus httpStatus, String path) {
|
||||
return ResponseEntity.status(httpStatus).body(null);
|
||||
}
|
||||
|
||||
private ResponseEntity<Object> handleNon200Response(String errorMsg,
|
||||
HttpStatus httpStatus,
|
||||
String path,
|
||||
Object body) {
|
||||
return ResponseEntity.status(httpStatus).body(body);
|
||||
}
|
||||
|
||||
private ResponseEntity<Object> handleOverriddenException(
|
||||
Exception ex, HttpHeaders headers, HttpStatus status, WebRequest request, String errorMsg) {
|
||||
return handleExceptionInternal(ex, null, headers, status, request);
|
||||
}
|
||||
|
||||
private String getPath(WebRequest request) {
|
||||
String description = request.getDescription(false);
|
||||
return description.startsWith("uri=") ? description.substring(4) : description;
|
||||
}
|
||||
}
|
@@ -0,0 +1,75 @@
|
||||
package com.databasir.api.config;
|
||||
|
||||
import com.databasir.api.Routes;
|
||||
import com.databasir.api.config.security.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
private final DatabasirUserDetailService databasirUserDetailService;
|
||||
|
||||
private final DatabasirAuthenticationEntryPoint databasirAuthenticationEntryPoint;
|
||||
|
||||
private final DatabasirJwtTokenFilter databasirJwtTokenFilter;
|
||||
|
||||
private final DatabasirAuthenticationFailureHandler databasirAuthenticationFailureHandler;
|
||||
|
||||
private final DatabasirAuthenticationSuccessHandler databasirAuthenticationSuccessHandler;
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.headers().frameOptions().disable();
|
||||
http.csrf().disable();
|
||||
http.cors();
|
||||
|
||||
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
.and()
|
||||
.formLogin()
|
||||
.loginProcessingUrl("/login")
|
||||
.failureHandler(databasirAuthenticationFailureHandler)
|
||||
.successHandler(databasirAuthenticationSuccessHandler)
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/login", Routes.Login.REFRESH_ACCESS_TOKEN).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.exceptionHandling().authenticationEntryPoint(databasirAuthenticationEntryPoint);
|
||||
|
||||
http.addFilterBefore(
|
||||
databasirJwtTokenFilter,
|
||||
UsernamePasswordAuthenticationFilter.class
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.userDetailsService(databasirUserDetailService)
|
||||
.passwordEncoder(bCryptPasswordEncoder());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public AuthenticationManager authenticationManagerBean() throws Exception {
|
||||
return super.authenticationManagerBean();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder bCryptPasswordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
}
|
18
api/src/main/java/com/databasir/api/config/WebConfig.java
Normal file
18
api/src/main/java/com/databasir/api/config/WebConfig.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package com.databasir.api.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.web.config.EnableSpringDataWebSupport;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
|
||||
@Configuration
|
||||
@EnableSpringDataWebSupport
|
||||
public class WebConfig extends WebMvcConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**")
|
||||
.allowedOrigins("*")
|
||||
.allowedMethods("GET", "POST", "DELETE", "PATCH", "PUT");
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package com.databasir.api.config.security;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class DatabasirAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
|
||||
@Override
|
||||
public void commence(javax.servlet.http.HttpServletRequest request,
|
||||
javax.servlet.http.HttpServletResponse response,
|
||||
AuthenticationException authException) throws IOException, ServletException {
|
||||
log.warn("验证未通过. 提示信息 - {}", authException.getMessage());
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
package com.databasir.api.config.security;
|
||||
|
||||
import com.databasir.common.JsonData;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.DisabledException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DatabasirAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailure(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
AuthenticationException exception) throws IOException, ServletException {
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
if (exception instanceof BadCredentialsException) {
|
||||
JsonData<Void> data = JsonData.error("-1", "用户名或密码错误");
|
||||
String jsonString = objectMapper.writeValueAsString(data);
|
||||
response.setStatus(HttpStatus.OK.value());
|
||||
response.getOutputStream().write(jsonString.getBytes(StandardCharsets.UTF_8));
|
||||
} else if (exception instanceof DisabledException) {
|
||||
JsonData<Void> data = JsonData.error("-1", "用户已禁用");
|
||||
String jsonString = objectMapper.writeValueAsString(data);
|
||||
response.setStatus(HttpStatus.OK.value());
|
||||
response.getOutputStream().write(jsonString.getBytes(StandardCharsets.UTF_8));
|
||||
} else {
|
||||
JsonData<Void> data = JsonData.error("-1", "未登录或未授权用户");
|
||||
String jsonString = objectMapper.writeValueAsString(data);
|
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||
response.getOutputStream().write(jsonString.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
package com.databasir.api.config.security;
|
||||
|
||||
import com.databasir.common.JsonData;
|
||||
import com.databasir.core.domain.login.data.LoginKeyResponse;
|
||||
import com.databasir.core.domain.login.service.LoginService;
|
||||
import com.databasir.core.domain.user.data.UserLoginResponse;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DatabasirAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final LoginService loginService;
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSuccess(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Authentication authentication) throws IOException, ServletException {
|
||||
DatabasirUserDetails user = (DatabasirUserDetails) authentication.getPrincipal();
|
||||
List<UserLoginResponse.RoleResponse> roles = user.getRoles()
|
||||
.stream()
|
||||
.map(ur -> {
|
||||
UserLoginResponse.RoleResponse data = new UserLoginResponse.RoleResponse();
|
||||
data.setRole(ur.getRole());
|
||||
data.setGroupId(ur.getGroupId());
|
||||
return data;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
UserLoginResponse data = new UserLoginResponse();
|
||||
data.setId(user.getUserPojo().getId());
|
||||
data.setNickname(user.getUserPojo().getNickname());
|
||||
data.setEmail(user.getUserPojo().getEmail());
|
||||
data.setUsername(user.getUsername());
|
||||
|
||||
LoginKeyResponse loginKey = loginService.generate(user.getUserPojo().getId());
|
||||
data.setAccessToken(loginKey.getAccessToken());
|
||||
long expireAt = loginKey.getAccessTokenExpireAt().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
data.setAccessTokenExpireAt(expireAt);
|
||||
data.setRefreshToken(loginKey.getRefreshToken());
|
||||
data.setRoles(roles);
|
||||
objectMapper.writeValue(response.getWriter(), JsonData.ok(data));
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
package com.databasir.api.config.security;
|
||||
|
||||
import com.databasir.core.infrastructure.jwt.JwtTokens;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DatabasirJwtTokenFilter extends OncePerRequestFilter {
|
||||
|
||||
private static final String SPACE = " ";
|
||||
|
||||
private final JwtTokens jwtTokens;
|
||||
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
|
||||
final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
if (!StringUtils.hasText(header) || !header.startsWith(JwtTokens.TOKEN_PREFIX)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
final String token = header.split(SPACE)[1].trim();
|
||||
if (!jwtTokens.verify(token)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
String username = jwtTokens.getUsername(token);
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
package com.databasir.api.config.security;
|
||||
|
||||
import com.databasir.dao.impl.LoginDao;
|
||||
import com.databasir.dao.impl.UserDao;
|
||||
import com.databasir.dao.impl.UserRoleDao;
|
||||
import com.databasir.dao.tables.pojos.UserPojo;
|
||||
import com.databasir.dao.tables.pojos.UserRolePojo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DatabasirUserDetailService implements UserDetailsService {
|
||||
|
||||
private final UserDao userDao;
|
||||
|
||||
private final UserRoleDao userRoleDao;
|
||||
|
||||
private final LoginDao loginDao;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
UserPojo user = userDao.selectByEmailOrUsername(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("用户名或密码错误"));
|
||||
List<UserRolePojo> roles = userRoleDao.selectByUserIds(Collections.singletonList(user.getId()));
|
||||
return new DatabasirUserDetails(user, roles);
|
||||
}
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
package com.databasir.api.config.security;
|
||||
|
||||
import com.databasir.dao.tables.pojos.UserPojo;
|
||||
import com.databasir.dao.tables.pojos.UserRolePojo;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class DatabasirUserDetails implements UserDetails {
|
||||
|
||||
@Getter
|
||||
private final UserPojo userPojo;
|
||||
|
||||
@Getter
|
||||
private final List<UserRolePojo> roles;
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return roles.stream()
|
||||
.map(role -> {
|
||||
String expression = role.getRole();
|
||||
if (role.getGroupId() != null) {
|
||||
expression += "?groupId=" + role.getGroupId();
|
||||
}
|
||||
return new SimpleGrantedAuthority(expression);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return userPojo.getPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return userPojo.getEmail();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return userPojo.getEnabled();
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
package com.databasir.api.validator;
|
||||
|
||||
import com.databasir.api.config.security.DatabasirUserDetails;
|
||||
import com.databasir.core.domain.DomainErrors;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class UserOperationValidator {
|
||||
|
||||
public void forbiddenIfUpdateSelfRole(Integer userId) {
|
||||
DatabasirUserDetails principal = (DatabasirUserDetails) SecurityContextHolder.getContext()
|
||||
.getAuthentication()
|
||||
.getPrincipal();
|
||||
if (principal.getUserPojo().getId().equals(userId)) {
|
||||
throw DomainErrors.CANNOT_UPDATE_SELF_ROLE.exception();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMyself(Integer userId) {
|
||||
DatabasirUserDetails principal = (DatabasirUserDetails) SecurityContextHolder.getContext()
|
||||
.getAuthentication()
|
||||
.getPrincipal();
|
||||
return principal.getUserPojo().getId().equals(userId);
|
||||
}
|
||||
|
||||
}
|
11
api/src/main/resources/application-local.properties
Normal file
11
api/src/main/resources/application-local.properties
Normal file
@@ -0,0 +1,11 @@
|
||||
server.port=8080
|
||||
logging.level.org.jooq=DEBUG
|
||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=123456
|
||||
spring.datasource.url=jdbc:mysql://localhost:3306/databasir
|
||||
spring.jooq.sql-dialect=mysql
|
||||
|
||||
spring.flyway.enabled=true
|
||||
spring.flyway.baseline-on-migrate=true
|
||||
spring.flyway.locations=classpath:db/migration
|
12
api/src/main/resources/application.properties
Normal file
12
api/src/main/resources/application.properties
Normal file
@@ -0,0 +1,12 @@
|
||||
server.port=8080
|
||||
# datasource
|
||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||
spring.datasource.username=${databasir.datasource.username}
|
||||
spring.datasource.password=${databasir.datasource.password}
|
||||
spring.datasource.url=jdbc:mysql://${databasir.datasource.url}/${databasir.datasource.database-name:databasir}
|
||||
# jooq
|
||||
spring.jooq.sql-dialect=mysql
|
||||
# flyway
|
||||
spring.flyway.enabled=true
|
||||
spring.flyway.baseline-on-migrate=true
|
||||
spring.flyway.locations=classpath:db/migration
|
Reference in New Issue
Block a user