You've already forked DataMate
feat(auth): 完善API网关JWT认证和权限控制功能
- 实现网关侧JWT工具类和权限规则匹配器 - 集成JWT认证流程,支持Bearer Token验证 - 添加基于路径和HTTP方法的权限控制机制 - 配置白名单路由规则,优化认证性能 - 更新前端受保护路由组件,实现权限验证 - 添加403禁止访问页面和权限检查逻辑 - 重构登录页面,集成实际认证API调用 - 实现用户信息获取和权限加载功能 - 优化全局异常处理器中的认证错误状态码 - 集成FastJSON2和JJWT依赖库支持
This commit is contained in:
@@ -17,6 +17,11 @@
|
||||
<description>DDD领域通用组件</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.datamate</groupId>
|
||||
<artifactId>security-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
package com.datamate.common.auth.application;
|
||||
|
||||
import com.datamate.common.auth.domain.model.AuthPermissionInfo;
|
||||
import com.datamate.common.auth.domain.model.AuthRoleInfo;
|
||||
import com.datamate.common.auth.domain.model.AuthUserAccount;
|
||||
import com.datamate.common.auth.domain.model.AuthUserSummary;
|
||||
import com.datamate.common.auth.infrastructure.exception.AuthErrorCode;
|
||||
import com.datamate.common.auth.infrastructure.persistence.mapper.AuthMapper;
|
||||
import com.datamate.common.auth.interfaces.rest.dto.AuthCurrentUserResponse;
|
||||
import com.datamate.common.auth.interfaces.rest.dto.AuthLoginResponse;
|
||||
import com.datamate.common.auth.interfaces.rest.dto.AuthUserView;
|
||||
import com.datamate.common.auth.interfaces.rest.dto.AuthUserWithRolesResponse;
|
||||
import com.datamate.common.infrastructure.exception.BusinessAssert;
|
||||
import com.datamate.common.security.JwtUtils;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 认证授权应用服务
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AuthApplicationService {
|
||||
private static final String TOKEN_TYPE = "Bearer";
|
||||
|
||||
private final AuthMapper authMapper;
|
||||
private final JwtUtils jwtUtils;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
public AuthLoginResponse login(String username, String password) {
|
||||
AuthUserAccount user = authMapper.findUserByUsername(username);
|
||||
BusinessAssert.notNull(user, AuthErrorCode.INVALID_CREDENTIALS);
|
||||
BusinessAssert.isTrue(Boolean.TRUE.equals(user.getEnabled()), AuthErrorCode.ACCOUNT_DISABLED);
|
||||
BusinessAssert.isTrue(passwordEncoder.matches(password, user.getPasswordHash()), AuthErrorCode.INVALID_CREDENTIALS);
|
||||
|
||||
AuthBundle authBundle = loadAuthBundle(user.getId());
|
||||
String token = buildToken(authBundle);
|
||||
authMapper.updateLastLoginAt(user.getId());
|
||||
|
||||
return new AuthLoginResponse(
|
||||
token,
|
||||
TOKEN_TYPE,
|
||||
computeExpiresInSeconds(token),
|
||||
toUserView(authBundle.user()),
|
||||
authBundle.roleCodes(),
|
||||
authBundle.permissionCodes()
|
||||
);
|
||||
}
|
||||
|
||||
public AuthCurrentUserResponse getCurrentUser(String token) {
|
||||
Claims claims = parseClaims(token);
|
||||
Long userId = parseUserId(claims);
|
||||
AuthBundle authBundle = loadAuthBundle(userId);
|
||||
return new AuthCurrentUserResponse(
|
||||
toUserView(authBundle.user()),
|
||||
authBundle.roleCodes(),
|
||||
authBundle.permissionCodes()
|
||||
);
|
||||
}
|
||||
|
||||
public AuthLoginResponse refreshToken(String token) {
|
||||
Claims claims = parseClaims(token);
|
||||
Long userId = parseUserId(claims);
|
||||
AuthBundle authBundle = loadAuthBundle(userId);
|
||||
String refreshedToken = buildToken(authBundle);
|
||||
return new AuthLoginResponse(
|
||||
refreshedToken,
|
||||
TOKEN_TYPE,
|
||||
computeExpiresInSeconds(refreshedToken),
|
||||
toUserView(authBundle.user()),
|
||||
authBundle.roleCodes(),
|
||||
authBundle.permissionCodes()
|
||||
);
|
||||
}
|
||||
|
||||
public List<AuthUserWithRolesResponse> listUsersWithRoles() {
|
||||
List<AuthUserSummary> users = authMapper.listUsers();
|
||||
List<AuthUserWithRolesResponse> responses = new ArrayList<>(users.size());
|
||||
for (AuthUserSummary user : users) {
|
||||
List<String> roleCodes = authMapper.findRolesByUserId(user.getId())
|
||||
.stream()
|
||||
.map(AuthRoleInfo::getRoleCode)
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
responses.add(new AuthUserWithRolesResponse(
|
||||
user.getId(),
|
||||
user.getUsername(),
|
||||
user.getFullName(),
|
||||
user.getEmail(),
|
||||
user.getEnabled(),
|
||||
roleCodes
|
||||
));
|
||||
}
|
||||
return responses;
|
||||
}
|
||||
|
||||
public List<AuthRoleInfo> listRoles() {
|
||||
return authMapper.listRoles();
|
||||
}
|
||||
|
||||
public List<AuthPermissionInfo> listPermissions() {
|
||||
return authMapper.listPermissions();
|
||||
}
|
||||
|
||||
public void assignUserRoles(Long userId, List<String> roleIds) {
|
||||
AuthUserAccount user = authMapper.findUserById(userId);
|
||||
BusinessAssert.notNull(user, AuthErrorCode.USER_NOT_FOUND);
|
||||
|
||||
Set<String> distinctRoleIds = new LinkedHashSet<>(roleIds);
|
||||
BusinessAssert.notEmpty(distinctRoleIds, AuthErrorCode.ROLE_NOT_FOUND);
|
||||
|
||||
int existingRoleCount = authMapper.countRolesByIds(new ArrayList<>(distinctRoleIds));
|
||||
BusinessAssert.isTrue(existingRoleCount == distinctRoleIds.size(), AuthErrorCode.ROLE_NOT_FOUND);
|
||||
|
||||
authMapper.deleteUserRoles(userId);
|
||||
authMapper.insertUserRoles(userId, new ArrayList<>(distinctRoleIds));
|
||||
}
|
||||
|
||||
private String buildToken(AuthBundle authBundle) {
|
||||
Map<String, Object> claims = Map.of(
|
||||
"userId", authBundle.user().getId(),
|
||||
"roles", authBundle.roleCodes(),
|
||||
"permissions", authBundle.permissionCodes()
|
||||
);
|
||||
return jwtUtils.generateToken(authBundle.user().getUsername(), claims);
|
||||
}
|
||||
|
||||
private AuthBundle loadAuthBundle(Long userId) {
|
||||
AuthUserAccount user = authMapper.findUserById(userId);
|
||||
BusinessAssert.notNull(user, AuthErrorCode.USER_NOT_FOUND);
|
||||
BusinessAssert.isTrue(Boolean.TRUE.equals(user.getEnabled()), AuthErrorCode.ACCOUNT_DISABLED);
|
||||
|
||||
List<String> roleCodes = authMapper.findRolesByUserId(userId).stream()
|
||||
.map(AuthRoleInfo::getRoleCode)
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
List<String> permissionCodes = authMapper.findPermissionCodesByUserId(userId).stream()
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
return new AuthBundle(user, roleCodes, permissionCodes);
|
||||
}
|
||||
|
||||
private Claims parseClaims(String token) {
|
||||
try {
|
||||
return jwtUtils.getClaimsFromToken(token);
|
||||
} catch (JwtException | IllegalArgumentException e) {
|
||||
throw com.datamate.common.infrastructure.exception.BusinessException.of(AuthErrorCode.TOKEN_INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
private Long parseUserId(Claims claims) {
|
||||
Object userIdObject = claims.get("userId");
|
||||
if (userIdObject instanceof Number number) {
|
||||
return number.longValue();
|
||||
}
|
||||
if (userIdObject instanceof String str) {
|
||||
try {
|
||||
return Long.parseLong(str);
|
||||
} catch (NumberFormatException e) {
|
||||
throw com.datamate.common.infrastructure.exception.BusinessException.of(AuthErrorCode.TOKEN_INVALID);
|
||||
}
|
||||
}
|
||||
throw com.datamate.common.infrastructure.exception.BusinessException.of(AuthErrorCode.TOKEN_INVALID);
|
||||
}
|
||||
|
||||
private long computeExpiresInSeconds(String token) {
|
||||
Date expirationDate = jwtUtils.getExpirationDateFromToken(token);
|
||||
long seconds = Duration.between(new Date().toInstant(), expirationDate.toInstant()).toSeconds();
|
||||
return Math.max(seconds, 0L);
|
||||
}
|
||||
|
||||
private AuthUserView toUserView(AuthUserAccount user) {
|
||||
return new AuthUserView(
|
||||
user.getId(),
|
||||
user.getUsername(),
|
||||
user.getFullName(),
|
||||
user.getEmail(),
|
||||
user.getAvatarUrl(),
|
||||
user.getOrganization()
|
||||
);
|
||||
}
|
||||
|
||||
private record AuthBundle(
|
||||
AuthUserAccount user,
|
||||
List<String> roleCodes,
|
||||
List<String> permissionCodes
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.datamate.common.auth.domain.model;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 权限信息
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class AuthPermissionInfo {
|
||||
private String id;
|
||||
private String permissionCode;
|
||||
private String permissionName;
|
||||
private String module;
|
||||
private String action;
|
||||
private String pathPattern;
|
||||
private String method;
|
||||
private Boolean enabled;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.datamate.common.auth.domain.model;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 角色信息
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class AuthRoleInfo {
|
||||
private String id;
|
||||
private String roleCode;
|
||||
private String roleName;
|
||||
private String description;
|
||||
private Boolean enabled;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.datamate.common.auth.domain.model;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 认证用户账户
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class AuthUserAccount {
|
||||
private Long id;
|
||||
private String username;
|
||||
private String email;
|
||||
private String passwordHash;
|
||||
private String fullName;
|
||||
private String avatarUrl;
|
||||
private String organization;
|
||||
private Boolean enabled;
|
||||
private LocalDateTime lastLoginAt;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.datamate.common.auth.domain.model;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 用户摘要
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class AuthUserSummary {
|
||||
private Long id;
|
||||
private String username;
|
||||
private String email;
|
||||
private String fullName;
|
||||
private Boolean enabled;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.datamate.common.auth.infrastructure.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
/**
|
||||
* 认证模块配置
|
||||
*/
|
||||
@Configuration
|
||||
public class AuthConfiguration {
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.datamate.common.auth.infrastructure.exception;
|
||||
|
||||
import com.datamate.common.infrastructure.exception.ErrorCode;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 认证授权错误码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum AuthErrorCode implements ErrorCode {
|
||||
INVALID_CREDENTIALS("auth.0001", "用户名或密码错误"),
|
||||
ACCOUNT_DISABLED("auth.0002", "账号已被禁用"),
|
||||
TOKEN_INVALID("auth.0003", "登录状态已失效"),
|
||||
USER_NOT_FOUND("auth.0004", "用户不存在"),
|
||||
ROLE_NOT_FOUND("auth.0005", "角色不存在"),
|
||||
AUTHORIZATION_DENIED("auth.0006", "无权限执行该操作");
|
||||
|
||||
private final String code;
|
||||
private final String message;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.datamate.common.auth.infrastructure.persistence.mapper;
|
||||
|
||||
import com.datamate.common.auth.domain.model.AuthPermissionInfo;
|
||||
import com.datamate.common.auth.domain.model.AuthRoleInfo;
|
||||
import com.datamate.common.auth.domain.model.AuthUserAccount;
|
||||
import com.datamate.common.auth.domain.model.AuthUserSummary;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 认证授权数据访问
|
||||
*/
|
||||
@Mapper
|
||||
public interface AuthMapper {
|
||||
AuthUserAccount findUserByUsername(@Param("username") String username);
|
||||
|
||||
AuthUserAccount findUserById(@Param("userId") Long userId);
|
||||
|
||||
int updateLastLoginAt(@Param("userId") Long userId);
|
||||
|
||||
List<AuthRoleInfo> findRolesByUserId(@Param("userId") Long userId);
|
||||
|
||||
List<String> findPermissionCodesByUserId(@Param("userId") Long userId);
|
||||
|
||||
List<AuthUserSummary> listUsers();
|
||||
|
||||
List<AuthRoleInfo> listRoles();
|
||||
|
||||
List<AuthPermissionInfo> listPermissions();
|
||||
|
||||
int countRolesByIds(@Param("roleIds") List<String> roleIds);
|
||||
|
||||
int deleteUserRoles(@Param("userId") Long userId);
|
||||
|
||||
int insertUserRoles(@Param("userId") Long userId, @Param("roleIds") List<String> roleIds);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.datamate.common.auth.interfaces.rest;
|
||||
|
||||
import com.datamate.common.auth.application.AuthApplicationService;
|
||||
import com.datamate.common.auth.domain.model.AuthPermissionInfo;
|
||||
import com.datamate.common.auth.domain.model.AuthRoleInfo;
|
||||
import com.datamate.common.auth.interfaces.rest.dto.AssignUserRolesRequest;
|
||||
import com.datamate.common.auth.interfaces.rest.dto.AuthCurrentUserResponse;
|
||||
import com.datamate.common.auth.interfaces.rest.dto.AuthLoginResponse;
|
||||
import com.datamate.common.auth.interfaces.rest.dto.AuthUserWithRolesResponse;
|
||||
import com.datamate.common.auth.interfaces.rest.dto.LoginRequest;
|
||||
import com.datamate.common.infrastructure.exception.BusinessAssert;
|
||||
import com.datamate.common.auth.infrastructure.exception.AuthErrorCode;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 认证授权控制器
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/auth")
|
||||
@RequiredArgsConstructor
|
||||
public class AuthController {
|
||||
private final AuthApplicationService authApplicationService;
|
||||
|
||||
@PostMapping("/login")
|
||||
public AuthLoginResponse login(@RequestBody @Valid LoginRequest loginRequest) {
|
||||
return authApplicationService.login(loginRequest.username(), loginRequest.password());
|
||||
}
|
||||
|
||||
@GetMapping("/me")
|
||||
public AuthCurrentUserResponse me(HttpServletRequest request) {
|
||||
return authApplicationService.getCurrentUser(extractBearerToken(request.getHeader("Authorization")));
|
||||
}
|
||||
|
||||
@PostMapping("/refresh")
|
||||
public AuthLoginResponse refresh(@RequestHeader("Authorization") String authorization) {
|
||||
return authApplicationService.refreshToken(extractBearerToken(authorization));
|
||||
}
|
||||
|
||||
@GetMapping("/users")
|
||||
public List<AuthUserWithRolesResponse> listUsers() {
|
||||
return authApplicationService.listUsersWithRoles();
|
||||
}
|
||||
|
||||
@PutMapping("/users/{userId}/roles")
|
||||
public void assignRoles(@PathVariable("userId") Long userId,
|
||||
@RequestBody @Valid AssignUserRolesRequest request) {
|
||||
authApplicationService.assignUserRoles(userId, request.roleIds());
|
||||
}
|
||||
|
||||
@GetMapping("/roles")
|
||||
public List<AuthRoleInfo> listRoles() {
|
||||
return authApplicationService.listRoles();
|
||||
}
|
||||
|
||||
@GetMapping("/permissions")
|
||||
public List<AuthPermissionInfo> listPermissions() {
|
||||
return authApplicationService.listPermissions();
|
||||
}
|
||||
|
||||
private String extractBearerToken(String authorizationHeader) {
|
||||
BusinessAssert.isTrue(
|
||||
authorizationHeader != null && authorizationHeader.startsWith("Bearer "),
|
||||
AuthErrorCode.TOKEN_INVALID
|
||||
);
|
||||
String token = authorizationHeader.substring("Bearer ".length()).trim();
|
||||
BusinessAssert.isTrue(!token.isEmpty(), AuthErrorCode.TOKEN_INVALID);
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.datamate.common.auth.interfaces.rest.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户角色分配请求
|
||||
*/
|
||||
public record AssignUserRolesRequest(
|
||||
@NotEmpty(message = "角色列表不能为空") List<String> roleIds
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.datamate.common.auth.interfaces.rest.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 当前用户信息响应
|
||||
*/
|
||||
public record AuthCurrentUserResponse(
|
||||
AuthUserView user,
|
||||
List<String> roles,
|
||||
List<String> permissions
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.datamate.common.auth.interfaces.rest.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 登录响应
|
||||
*/
|
||||
public record AuthLoginResponse(
|
||||
String token,
|
||||
String tokenType,
|
||||
long expiresInSeconds,
|
||||
AuthUserView user,
|
||||
List<String> roles,
|
||||
List<String> permissions
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.datamate.common.auth.interfaces.rest.dto;
|
||||
|
||||
/**
|
||||
* 登录用户信息
|
||||
*/
|
||||
public record AuthUserView(
|
||||
Long id,
|
||||
String username,
|
||||
String fullName,
|
||||
String email,
|
||||
String avatarUrl,
|
||||
String organization
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.datamate.common.auth.interfaces.rest.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户与角色响应
|
||||
*/
|
||||
public record AuthUserWithRolesResponse(
|
||||
Long id,
|
||||
String username,
|
||||
String fullName,
|
||||
String email,
|
||||
Boolean enabled,
|
||||
List<String> roleCodes
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.datamate.common.auth.interfaces.rest.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 登录请求
|
||||
*/
|
||||
public record LoginRequest(
|
||||
@NotBlank(message = "用户名不能为空") String username,
|
||||
@NotBlank(message = "密码不能为空") String password
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.datamate.common.infrastructure.config;
|
||||
import com.datamate.common.infrastructure.common.Response;
|
||||
import com.datamate.common.infrastructure.exception.BusinessException;
|
||||
import com.datamate.common.infrastructure.exception.SystemErrorCode;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.BindException;
|
||||
@@ -28,7 +29,8 @@ public class GlobalExceptionHandler {
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public ResponseEntity<Response<?>> handleBusinessException(BusinessException e) {
|
||||
log.warn("BusinessException: code={}, message={}", e.getCode(), e.getMessage(), e);
|
||||
return ResponseEntity.internalServerError().body(Response.error(e.getErrorCodeEnum()));
|
||||
HttpStatus status = resolveBusinessStatus(e.getCode());
|
||||
return ResponseEntity.status(status).body(Response.error(e.getErrorCodeEnum()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,4 +53,17 @@ public class GlobalExceptionHandler {
|
||||
log.error("SystemException: ", e);
|
||||
return ResponseEntity.internalServerError().body(Response.error(SystemErrorCode.SYSTEM_BUSY));
|
||||
}
|
||||
|
||||
private HttpStatus resolveBusinessStatus(String code) {
|
||||
if (code == null) {
|
||||
return HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
if (!code.startsWith("auth.")) {
|
||||
return HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
if ("auth.0006".equals(code)) {
|
||||
return HttpStatus.FORBIDDEN;
|
||||
}
|
||||
return HttpStatus.UNAUTHORIZED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.datamate.common.auth.infrastructure.persistence.mapper.AuthMapper">
|
||||
|
||||
<select id="findUserByUsername" resultType="com.datamate.common.auth.domain.model.AuthUserAccount">
|
||||
SELECT id,
|
||||
username,
|
||||
email,
|
||||
password_hash AS passwordHash,
|
||||
full_name AS fullName,
|
||||
avatar_url AS avatarUrl,
|
||||
organization,
|
||||
enabled,
|
||||
last_login_at AS lastLoginAt
|
||||
FROM users
|
||||
WHERE username = #{username}
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<select id="findUserById" resultType="com.datamate.common.auth.domain.model.AuthUserAccount">
|
||||
SELECT id,
|
||||
username,
|
||||
email,
|
||||
password_hash AS passwordHash,
|
||||
full_name AS fullName,
|
||||
avatar_url AS avatarUrl,
|
||||
organization,
|
||||
enabled,
|
||||
last_login_at AS lastLoginAt
|
||||
FROM users
|
||||
WHERE id = #{userId}
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<update id="updateLastLoginAt">
|
||||
UPDATE users
|
||||
SET last_login_at = NOW()
|
||||
WHERE id = #{userId}
|
||||
</update>
|
||||
|
||||
<select id="findRolesByUserId" resultType="com.datamate.common.auth.domain.model.AuthRoleInfo">
|
||||
SELECT r.id,
|
||||
r.role_code AS roleCode,
|
||||
r.role_name AS roleName,
|
||||
r.description,
|
||||
r.enabled
|
||||
FROM t_auth_roles r
|
||||
INNER JOIN t_auth_user_roles ur ON ur.role_id = r.id
|
||||
WHERE ur.user_id = #{userId}
|
||||
ORDER BY r.role_code
|
||||
</select>
|
||||
|
||||
<select id="findPermissionCodesByUserId" resultType="string">
|
||||
SELECT DISTINCT p.permission_code
|
||||
FROM t_auth_permissions p
|
||||
INNER JOIN t_auth_role_permissions rp ON rp.permission_id = p.id
|
||||
INNER JOIN t_auth_user_roles ur ON ur.role_id = rp.role_id
|
||||
WHERE ur.user_id = #{userId}
|
||||
AND p.enabled = 1
|
||||
ORDER BY p.permission_code
|
||||
</select>
|
||||
|
||||
<select id="listUsers" resultType="com.datamate.common.auth.domain.model.AuthUserSummary">
|
||||
SELECT id,
|
||||
username,
|
||||
email,
|
||||
full_name AS fullName,
|
||||
enabled
|
||||
FROM users
|
||||
ORDER BY id ASC
|
||||
</select>
|
||||
|
||||
<select id="listRoles" resultType="com.datamate.common.auth.domain.model.AuthRoleInfo">
|
||||
SELECT id,
|
||||
role_code AS roleCode,
|
||||
role_name AS roleName,
|
||||
description,
|
||||
enabled
|
||||
FROM t_auth_roles
|
||||
ORDER BY role_code ASC
|
||||
</select>
|
||||
|
||||
<select id="listPermissions" resultType="com.datamate.common.auth.domain.model.AuthPermissionInfo">
|
||||
SELECT id,
|
||||
permission_code AS permissionCode,
|
||||
permission_name AS permissionName,
|
||||
module,
|
||||
action,
|
||||
path_pattern AS pathPattern,
|
||||
method,
|
||||
enabled
|
||||
FROM t_auth_permissions
|
||||
ORDER BY module ASC, action ASC
|
||||
</select>
|
||||
|
||||
<select id="countRolesByIds" resultType="int">
|
||||
SELECT COUNT(1)
|
||||
FROM t_auth_roles
|
||||
WHERE id IN
|
||||
<foreach collection="roleIds" item="roleId" open="(" separator="," close=")">
|
||||
#{roleId}
|
||||
</foreach>
|
||||
</select>
|
||||
|
||||
<delete id="deleteUserRoles">
|
||||
DELETE
|
||||
FROM t_auth_user_roles
|
||||
WHERE user_id = #{userId}
|
||||
</delete>
|
||||
|
||||
<insert id="insertUserRoles">
|
||||
INSERT INTO t_auth_user_roles (user_id, role_id)
|
||||
VALUES
|
||||
<foreach collection="roleIds" item="roleId" separator=",">
|
||||
(#{userId}, #{roleId})
|
||||
</foreach>
|
||||
</insert>
|
||||
</mapper>
|
||||
|
||||
@@ -3,9 +3,13 @@ package com.datamate.common.security;
|
||||
import io.jsonwebtoken.*;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -15,15 +19,23 @@ import java.util.Map;
|
||||
*/
|
||||
@Component
|
||||
public class JwtUtils {
|
||||
private static final String DEFAULT_SECRET = "datamate-secret-key-for-jwt-token-generation";
|
||||
|
||||
@Value("${jwt.secret:datamate-secret-key-for-jwt-token-generation}")
|
||||
@Value("${jwt.secret:" + DEFAULT_SECRET + "}")
|
||||
private String secret;
|
||||
|
||||
@Value("${jwt.expiration:86400}") // 24小时
|
||||
private Long expiration;
|
||||
|
||||
private SecretKey getSigningKey() {
|
||||
return Keys.hmacShaKeyFor(secret.getBytes());
|
||||
String secretValue = StringUtils.hasText(secret) ? secret : DEFAULT_SECRET;
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-512");
|
||||
byte[] keyBytes = digest.digest(secretValue.getBytes(StandardCharsets.UTF_8));
|
||||
return Keys.hmacShaKeyFor(keyBytes);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("Cannot initialize JWT signing key", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,7 +96,18 @@ public class JwtUtils {
|
||||
public Boolean validateToken(String token, String username) {
|
||||
try {
|
||||
String tokenUsername = getUsernameFromToken(token);
|
||||
return (username.equals(tokenUsername) && !isTokenExpired(token));
|
||||
return (username.equals(tokenUsername) && validateToken(token));
|
||||
} catch (JwtException | IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅校验令牌格式与有效期
|
||||
*/
|
||||
public Boolean validateToken(String token) {
|
||||
try {
|
||||
return !isTokenExpired(token);
|
||||
} catch (JwtException | IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user