feat: add authentication and tenant context

This commit is contained in:
王鹏
2026-07-07 15:12:51 +08:00
parent 982f104f3d
commit 51e0f3a5ad
10 changed files with 341 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
package com.linhelp.common.config;
import com.linhelp.common.security.RoleGuardInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RoleGuardInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/auth/**");
}
}

View File

@@ -0,0 +1,43 @@
package com.linhelp.common.security;
import com.linhelp.common.api.ApiResponse;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@Validated
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final AuthService authService;
public AuthController(AuthService authService) {
this.authService = authService;
}
@PostMapping("/admin-login")
public ApiResponse<LoginResponse> adminLogin(@Valid @RequestBody LoginRequest request) {
return ApiResponse.ok(authService.adminLogin(request));
}
@PostMapping("/dev-miniapp-login")
public ApiResponse<LoginResponse> devMiniappLogin(@Valid @RequestBody LoginRequest request) {
return ApiResponse.ok(authService.devMiniappLogin(request));
}
@PostMapping("/logout")
public ApiResponse<Void> logout() {
authService.logout();
return ApiResponse.ok();
}
@GetMapping("/me")
public ApiResponse<CurrentUser> me() {
return ApiResponse.ok(authService.currentUser());
}
}

View File

@@ -0,0 +1,67 @@
package com.linhelp.common.security;
import cn.dev33.satoken.stp.StpUtil;
import com.linhelp.common.exception.BizException;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class AuthService {
private static final Long DEFAULT_TENANT_ID = 1L;
private static final Long DEFAULT_COMMUNITY_ID = 1L;
private final Map<String, DemoAccount> demoAccounts = new HashMap<String, DemoAccount>();
public AuthService() {
demoAccounts.put("admin", new DemoAccount(1L, "admin123", "COMMUNITY_ADMIN"));
demoAccounts.put("merchant", new DemoAccount(2L, "merchant123", "MERCHANT"));
demoAccounts.put("rider", new DemoAccount(3L, "rider123", "RIDER"));
}
public LoginResponse adminLogin(LoginRequest request) {
DemoAccount account = demoAccounts.get(request.getUsername());
if (account == null || !account.password.equals(request.getPassword())) {
throw new BizException(401, "账号或密码错误");
}
return login(account.userId, account.roleCode);
}
public LoginResponse devMiniappLogin(LoginRequest request) {
return login(1000L, "RESIDENT");
}
public CurrentUser currentUser() {
StpUtil.checkLogin();
return new CurrentUser(
StpUtil.getLoginIdAsLong(),
(Long) StpUtil.getSession().get("tenantId"),
(Long) StpUtil.getSession().get("communityId"),
(String) StpUtil.getSession().get("roleCode")
);
}
public void logout() {
StpUtil.logout();
}
private LoginResponse login(Long userId, String roleCode) {
StpUtil.login(userId);
StpUtil.getSession().set("tenantId", DEFAULT_TENANT_ID);
StpUtil.getSession().set("communityId", DEFAULT_COMMUNITY_ID);
StpUtil.getSession().set("roleCode", roleCode);
return new LoginResponse(StpUtil.getTokenValue(), userId, DEFAULT_TENANT_ID, DEFAULT_COMMUNITY_ID, roleCode);
}
private static class DemoAccount {
private final Long userId;
private final String password;
private final String roleCode;
private DemoAccount(Long userId, String password, String roleCode) {
this.userId = userId;
this.password = password;
this.roleCode = roleCode;
}
}
}

View File

@@ -0,0 +1,34 @@
package com.linhelp.common.security;
public class CurrentUser {
private Long userId;
private Long tenantId;
private Long communityId;
private String roleCode;
public CurrentUser() {
}
public CurrentUser(Long userId, Long tenantId, Long communityId, String roleCode) {
this.userId = userId;
this.tenantId = tenantId;
this.communityId = communityId;
this.roleCode = roleCode;
}
public Long getUserId() {
return userId;
}
public Long getTenantId() {
return tenantId;
}
public Long getCommunityId() {
return communityId;
}
public String getRoleCode() {
return roleCode;
}
}

View File

@@ -0,0 +1,27 @@
package com.linhelp.common.security;
import javax.validation.constraints.NotBlank;
public class LoginRequest {
@NotBlank
private String username;
@NotBlank
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -0,0 +1,40 @@
package com.linhelp.common.security;
public class LoginResponse {
private String token;
private Long userId;
private Long tenantId;
private Long communityId;
private String roleCode;
public LoginResponse() {
}
public LoginResponse(String token, Long userId, Long tenantId, Long communityId, String roleCode) {
this.token = token;
this.userId = userId;
this.tenantId = tenantId;
this.communityId = communityId;
this.roleCode = roleCode;
}
public String getToken() {
return token;
}
public Long getUserId() {
return userId;
}
public Long getTenantId() {
return tenantId;
}
public Long getCommunityId() {
return communityId;
}
public String getRoleCode() {
return roleCode;
}
}

View File

@@ -0,0 +1,12 @@
package com.linhelp.common.security;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireRole {
String[] value();
}

View File

@@ -0,0 +1,35 @@
package com.linhelp.common.security;
import cn.dev33.satoken.stp.StpUtil;
import com.linhelp.common.exception.BizException;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class RoleGuardInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod method = (HandlerMethod) handler;
RequireRole requireRole = method.getMethodAnnotation(RequireRole.class);
if (requireRole == null) {
requireRole = method.getBeanType().getAnnotation(RequireRole.class);
}
if (requireRole == null) {
return true;
}
StpUtil.checkLogin();
String currentRole = (String) StpUtil.getSession().get("roleCode");
for (String role : requireRole.value()) {
if (role.equals(currentRole)) {
return true;
}
}
throw new BizException(403, "无权访问");
}
}

View File

@@ -0,0 +1,44 @@
package com.linhelp.common.security;
public final class TenantContext {
private static final ThreadLocal<Scope> SCOPE = new ThreadLocal<Scope>();
private TenantContext() {
}
public static void set(Long tenantId, Long communityId) {
SCOPE.set(new Scope(tenantId, communityId));
}
public static Long tenantId() {
Scope scope = SCOPE.get();
return scope == null ? null : scope.tenantId();
}
public static Long communityId() {
Scope scope = SCOPE.get();
return scope == null ? null : scope.communityId();
}
public static void clear() {
SCOPE.remove();
}
private static class Scope {
private final Long tenantId;
private final Long communityId;
private Scope(Long tenantId, Long communityId) {
this.tenantId = tenantId;
this.communityId = communityId;
}
private Long tenantId() {
return tenantId;
}
private Long communityId() {
return communityId;
}
}
}

View File

@@ -0,0 +1,22 @@
package com.linhelp.common.security;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class TenantContextTests {
@AfterEach
void clear() {
TenantContext.clear();
}
@Test
void storesTenantAndCommunityForCurrentRequestThread() {
TenantContext.set(1L, 10L);
assertThat(TenantContext.tenantId()).isEqualTo(1L);
assertThat(TenantContext.communityId()).isEqualTo(10L);
}
}