Skip to content

Commit

Permalink
feat(system/client): 新增客户端管理
Browse files Browse the repository at this point in the history
  • Loading branch information
KAI authored and Charles7c committed Dec 26, 2024
1 parent 6bcff72 commit 95f2617
Show file tree
Hide file tree
Showing 30 changed files with 1,648 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public class SysConstants {
/**
* 账号登录 URI
*/
public static final String LOGIN_URI = "/auth/account";
public static final String LOGIN_URI = "/auth/login";

/**
* 退出 URI
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,16 @@ public class UserContext implements Serializable {
*/
private Set<RoleContext> roles;

/**
* 设备类型
*/
private String clientType;

/**
* 客户端ID
*/
private String clientId;

public UserContext(Set<String> permissions, Set<RoleContext> roles, Integer passwordExpirationDays) {
this.permissions = permissions;
this.setRoles(roles);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package top.continew.admin.auth;

import jakarta.servlet.http.HttpServletRequest;
import top.continew.admin.auth.enums.AuthTypeEnum;
import top.continew.admin.auth.model.req.AuthReq;
import top.continew.admin.system.model.resp.ClientResp;

/**
* 认证接口
*
* @author KAI
* @since 2024/12/22 14:52:23
*/
public interface AuthHandler<T extends AuthReq, R> {

/**
* 执行登录
*
* @param authReq 登录请求参数
* @param request HTTP请求对象
* @return 登录响应
*/
R login(T authReq, ClientResp clientResp, HttpServletRequest request);

/**
* 获取登录类型
*
* @return 登录类型Enum
*/
AuthTypeEnum getAuthType();

/**
* 校验参数
*
* @param authReq 登录请求参数
*/
void validate(T authReq);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package top.continew.admin.auth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import top.continew.admin.auth.model.req.AuthReq;
import top.continew.admin.auth.AuthHandler;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* 登录类型策略上文
*
* @author KAI
* @since 2024/12/20 15:16:55
*/
@Component
public class AuthHandlerContext {
private final Map<String, AuthHandler<?, ?>> handlerMap = new HashMap<>();

@Autowired
public AuthHandlerContext(List<AuthHandler<?, ?>> strategies) {
for (AuthHandler<?, ?> strategy : strategies) {
handlerMap.put(strategy.getAuthType().getValue(), strategy);
}
}

@SuppressWarnings("unchecked")
public <T extends AuthReq, R> AuthHandler<T, R> getHandler(String type) {
AuthHandler<?, ?> strategy = handlerMap.get(type);
if (strategy == null) {
throw new IllegalArgumentException("No handler found for type: " + type);
}
return (AuthHandler<T, R>)strategy;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package top.continew.admin.auth.enums;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import top.continew.admin.common.constant.UiConstants;
import top.continew.starter.core.enums.BaseEnum;

/**
* 认证类型
*
* @author Charles7c
* @since 2023/12/23 13:38
*/
@Getter
@RequiredArgsConstructor
public enum AuthTypeEnum implements BaseEnum<String> {

/**
* 账号
*/
ACCOUNT("account", "账号", UiConstants.COLOR_ERROR),

/**
* 邮箱
*/
EMAIL("email", "邮箱", UiConstants.COLOR_PRIMARY),

/**
* 手机号
*/
PHONE("phone", "手机号", UiConstants.COLOR_SUCCESS),

/**
* 第三方授权
*/
SOCIAL_AUTH("socialAuth", "第三方授权", UiConstants.COLOR_DEFAULT);

private final String value;
private final String description;
private final String color;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package top.continew.admin.auth.handler;

import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import top.continew.admin.common.context.RoleContext;
import top.continew.admin.common.context.UserContext;
import top.continew.admin.common.context.UserContextHolder;
import top.continew.admin.common.context.UserExtraContext;
import top.continew.admin.system.model.entity.UserDO;
import top.continew.admin.system.model.resp.ClientResp;
import top.continew.admin.system.service.OptionService;
import top.continew.admin.system.service.RoleService;
import top.continew.starter.web.util.SpringWebUtils;

import java.util.Set;
import java.util.concurrent.CompletableFuture;

import static top.continew.admin.system.enums.PasswordPolicyEnum.PASSWORD_EXPIRATION_DAYS;

/**
* 认证处理器抽象类
*
* @author KAI
* @since 2024/12/22 14:52
*/
@Component
@RequiredArgsConstructor
public abstract class AbstractAuthHandler {
@Resource
private RoleService roleService;
@Resource
private OptionService optionService;
@Resource
private ThreadPoolTaskExecutor threadPoolTaskExecutor;

public static final String CAPTCHA_EXPIRED = "验证码已失效";
public static final String CAPTCHA_ERROR = "验证码错误";
public static final String CLIENT_ID = "clientId";

/**
* 获取登录凭证
*
* @param user 用户信息
* @param clientResp 客户端信息
* @return token 认证信息
*/
protected String authCertificate(UserDO user, ClientResp clientResp) {
preLogin(user, clientResp);
// 核心登录逻辑
Long userId = user.getId();
CompletableFuture<Set<String>> permissionFuture = CompletableFuture.supplyAsync(() -> roleService
.listPermissionByUserId(userId), threadPoolTaskExecutor);
CompletableFuture<Set<RoleContext>> roleFuture = CompletableFuture.supplyAsync(() -> roleService
.listByUserId(userId), threadPoolTaskExecutor);
CompletableFuture<Integer> passwordExpirationDaysFuture = CompletableFuture.supplyAsync(() -> optionService
.getValueByCode2Int(PASSWORD_EXPIRATION_DAYS.name()));
CompletableFuture.allOf(permissionFuture, roleFuture, passwordExpirationDaysFuture);

UserContext userContext = new UserContext(permissionFuture.join(), roleFuture
.join(), passwordExpirationDaysFuture.join());

BeanUtil.copyProperties(user, userContext);
SaLoginModel model = new SaLoginModel();
// 设置登录 token 最低活跃频率 如未指定,则使用全局配置的 activeTimeout 值
model.setActiveTimeout(clientResp.getActiveTimeout());
// 设置登录 token 有效期,单位:秒 (如未指定,自动取全局配置的 timeout 值
model.setTimeout(clientResp.getTimeout());
// 设置设备类型
model.setDevice(clientResp.getClientType());
userContext.setClientType(clientResp.getClientType());
// 设置客户端id
userContext.setClientId(clientResp.getClientId());
model.setExtra(CLIENT_ID, clientResp.getClientId());
// 自定义用户上下文处理
customizeUserContext(userContext, user, clientResp);

// 登录并缓存用户信息
StpUtil.login(userContext.getId(), model.setExtraData(BeanUtil.beanToMap(new UserExtraContext(SpringWebUtils
.getRequest()))));
UserContextHolder.setContext(userContext);

// 后置处理
String token = StpUtil.getTokenValue();
postLogin(token, user, clientResp);
return token;
}

/**
* 登录前置处理
*
* @param user 用户信息
* @param clientResp 客户端信息
*/
private void preLogin(UserDO user, ClientResp clientResp) {
}

/**
* 自定义用户上下文处理
*
* @param userContext 用户上下文
* @param user 用户信息
* @param clientResp 客户端信息
*/
protected void customizeUserContext(UserContext userContext, UserDO user, ClientResp clientResp) {
}

/**
* 登录后置处理
*
* @param token 登录令牌
* @param user 用户信息
* @param clientResp 客户端信息
*/
protected void postLogin(String token, UserDO user, ClientResp clientResp) {
}
}
Loading

0 comments on commit 95f2617

Please sign in to comment.