feat: PowerJobAuthInterceptor

This commit is contained in:
tjq 2023-03-25 13:38:43 +08:00
parent 926c8758ed
commit ce6c62f786
9 changed files with 145 additions and 27 deletions

View File

@ -0,0 +1,18 @@
package tech.powerjob.common;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 统一定义日志
*
* @author tjq
* @since 2023/3/25
*/
public class Loggers {
/**
* Web 层统一日志
*/
public static final Logger WEB = LoggerFactory.getLogger("P_SERVER_LOGGER_WEB");
}

View File

@ -1,7 +1,8 @@
package tech.powerjob.server.auth.login; package tech.powerjob.server.auth;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.experimental.Accessors;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -13,6 +14,7 @@ import javax.servlet.http.HttpServletRequest;
*/ */
@Getter @Getter
@Setter @Setter
@Accessors(chain = true)
public class LoginContext { public class LoginContext {
private HttpServletRequest httpServletRequest; private HttpServletRequest httpServletRequest;

View File

@ -2,12 +2,19 @@ package tech.powerjob.server.auth.anno;
import tech.powerjob.server.auth.Permission; import tech.powerjob.server.auth.Permission;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** /**
* API 权限 * API 权限
* *
* @author tjq * @author tjq
* @since 2023/3/20 * @since 2023/3/20
*/ */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiPermission { public @interface ApiPermission {
/** /**
* API 名称 * API 名称

View File

@ -0,0 +1,82 @@
package tech.powerjob.server.auth.interceptor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import tech.powerjob.common.Loggers;
import tech.powerjob.common.exception.PowerJobException;
import tech.powerjob.server.auth.PowerJobUser;
import tech.powerjob.server.auth.anno.ApiPermission;
import tech.powerjob.server.auth.service.PowerJobAuthService;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Optional;
/**
* login auth and permission check
*
* @author tjq
* @since 2023/3/25
*/
@Component
public class PowerJobAuthInterceptor implements HandlerInterceptor {
@Resource
private PowerJobAuthService powerJobAuthService;
@Override
public boolean preHandle(@NonNull HttpServletRequest request,@NonNull HttpServletResponse response,@NonNull Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
final Method method = handlerMethod.getMethod();
final ApiPermission apiPermissionAnno = method.getAnnotation(ApiPermission.class);
// 无注解代表不需要权限无需登陆直接访问
if (apiPermissionAnno == null) {
return true;
}
// 尝试直接解析登陆
final Optional<PowerJobUser> loginUserOpt = powerJobAuthService.parse(request);
// 未登录前先使用302重定向到登录页面
if (!loginUserOpt.isPresent()) {
response.setStatus(302);
response.setHeader("location", request.getContextPath() + "/login");
return false;
}
// 登陆用户进行权限校验
final PowerJobUser powerJobUser = loginUserOpt.get();
final boolean hasPermission = powerJobAuthService.hasPermission(powerJobUser, apiPermissionAnno);
if (hasPermission) {
return true;
}
final String resourceName = parseResourceName(apiPermissionAnno, handlerMethod);
Loggers.WEB.info("[PowerJobAuthInterceptor] user[{}] has no permission to access: {}", powerJobUser.getUsername(), resourceName);
throw new PowerJobException("Permission denied!");
}
private static String parseResourceName(ApiPermission apiPermission, HandlerMethod handlerMethod) {
final String name = apiPermission.name();
if (StringUtils.isNotEmpty(name)) {
return name;
}
try {
final String clzName = handlerMethod.getBean().getClass().getSimpleName();
final String methodName = handlerMethod.getMethod().getName();
return String.format("%s_%s", clzName, methodName);
} catch (Exception ignore) {
}
return "UNKNOWN";
}
}

View File

@ -1,6 +1,6 @@
package tech.powerjob.server.auth.login.biz; package tech.powerjob.server.auth.login;
import tech.powerjob.server.auth.login.LoginContext; import tech.powerjob.server.auth.LoginContext;
import java.util.Optional; import java.util.Optional;

View File

@ -1,4 +1,4 @@
package tech.powerjob.server.auth.login.biz; package tech.powerjob.server.auth.login;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;

View File

@ -1,12 +1,12 @@
package tech.powerjob.server.auth.login.biz.impl; package tech.powerjob.server.auth.login.impl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import tech.powerjob.common.Loggers;
import tech.powerjob.common.utils.DigestUtils; import tech.powerjob.common.utils.DigestUtils;
import tech.powerjob.server.auth.login.LoginContext; import tech.powerjob.server.auth.LoginContext;
import tech.powerjob.server.auth.login.biz.BizLoginService; import tech.powerjob.server.auth.login.BizLoginService;
import tech.powerjob.server.auth.login.biz.BizUser; import tech.powerjob.server.auth.login.BizUser;
import tech.powerjob.server.common.SJ; import tech.powerjob.server.common.SJ;
import tech.powerjob.server.persistence.remote.model.UserInfoDO; import tech.powerjob.server.persistence.remote.model.UserInfoDO;
import tech.powerjob.server.persistence.remote.repository.UserInfoRepository; import tech.powerjob.server.persistence.remote.repository.UserInfoRepository;
@ -21,7 +21,6 @@ import java.util.Optional;
* @author tjq * @author tjq
* @since 2023/3/20 * @since 2023/3/20
*/ */
@Slf4j
@Service @Service
public class DefaultBizLoginService implements BizLoginService { public class DefaultBizLoginService implements BizLoginService {
@ -51,13 +50,13 @@ public class DefaultBizLoginService implements BizLoginService {
final String password = loginInfoMap.get(KEY_PASSWORD); final String password = loginInfoMap.get(KEY_PASSWORD);
if (StringUtils.isAnyEmpty(username, password)) { if (StringUtils.isAnyEmpty(username, password)) {
log.debug("[DefaultBizLoginService] username or password is empty, login failed!"); Loggers.WEB.debug("[DefaultBizLoginService] username or password is empty, login failed!");
return Optional.empty(); return Optional.empty();
} }
final Optional<UserInfoDO> userInfoOpt = userInfoRepository.findByUsername(username); final Optional<UserInfoDO> userInfoOpt = userInfoRepository.findByUsername(username);
if (!userInfoOpt.isPresent()) { if (!userInfoOpt.isPresent()) {
log.debug("[DefaultBizLoginService] can't find user by username: {}", username); Loggers.WEB.debug("[DefaultBizLoginService] can't find user by username: {}", username);
return Optional.empty(); return Optional.empty();
} }
@ -69,7 +68,7 @@ public class DefaultBizLoginService implements BizLoginService {
return Optional.of(bizUser); return Optional.of(bizUser);
} }
log.debug("[DefaultBizLoginService] user[{}]'s password is not correct, login failed!", username); Loggers.WEB.debug("[DefaultBizLoginService] user[{}]'s password is not correct, login failed!", username);
return Optional.empty(); return Optional.empty();
} }

View File

@ -1,7 +1,10 @@
package tech.powerjob.server.auth.login; package tech.powerjob.server.auth.service;
import tech.powerjob.server.auth.LoginContext;
import tech.powerjob.server.auth.PowerJobUser; import tech.powerjob.server.auth.PowerJobUser;
import tech.powerjob.server.auth.anno.ApiPermission;
import javax.servlet.http.HttpServletRequest;
import java.util.Optional; import java.util.Optional;
/** /**
@ -10,7 +13,7 @@ import java.util.Optional;
* @author tjq * @author tjq
* @since 2023/3/21 * @since 2023/3/21
*/ */
public interface PowerJobLoginService { public interface PowerJobAuthService {
/** /**
* 执行真正的登陆操作 * 执行真正的登陆操作
@ -24,5 +27,7 @@ public interface PowerJobLoginService {
* @param loginContext 登录上下文 * @param loginContext 登录上下文
* @return PowerJob 用户 * @return PowerJob 用户
*/ */
Optional<PowerJobUser> parse(LoginContext loginContext); Optional<PowerJobUser> parse(HttpServletRequest httpServletRequest);
boolean hasPermission(PowerJobUser user, ApiPermission apiPermission);
} }

View File

@ -1,14 +1,16 @@
package tech.powerjob.server.auth.login; package tech.powerjob.server.auth.service;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import tech.powerjob.common.Loggers;
import tech.powerjob.server.auth.LoginContext;
import tech.powerjob.server.auth.PowerJobUser; import tech.powerjob.server.auth.PowerJobUser;
import tech.powerjob.server.auth.login.biz.BizLoginService; import tech.powerjob.server.auth.anno.ApiPermission;
import tech.powerjob.server.auth.login.biz.BizUser; import tech.powerjob.server.auth.login.BizLoginService;
import tech.powerjob.server.auth.login.BizUser;
import tech.powerjob.server.persistence.remote.model.UserInfoDO; import tech.powerjob.server.persistence.remote.model.UserInfoDO;
import tech.powerjob.server.persistence.remote.repository.UserInfoRepository; import tech.powerjob.server.persistence.remote.repository.UserInfoRepository;
@ -23,9 +25,8 @@ import java.util.Optional;
* @author tjq * @author tjq
* @since 2023/3/21 * @since 2023/3/21
*/ */
@Slf4j
@Service @Service
public class PowerJobLoginServiceImpl implements PowerJobLoginService { public class PowerJobLoginServiceImpl implements PowerJobAuthService {
private final UserInfoRepository userInfoRepository; private final UserInfoRepository userInfoRepository;
private final Map<String, BizLoginService> type2LoginService = Maps.newHashMap(); private final Map<String, BizLoginService> type2LoginService = Maps.newHashMap();
@ -67,7 +68,7 @@ public class PowerJobLoginServiceImpl implements PowerJobLoginService {
// 同步在 PowerJob 用户库创建该用户 // 同步在 PowerJob 用户库创建该用户
UserInfoDO newUser = new UserInfoDO(); UserInfoDO newUser = new UserInfoDO();
newUser.setUsername(dbUserName); newUser.setUsername(dbUserName);
log.info("[PowerJobLoginService] sync user to PowerJobUserSystem: {}", dbUserName); Loggers.WEB.info("[PowerJobLoginService] sync user to PowerJobUserSystem: {}", dbUserName);
userInfoRepository.saveAndFlush(newUser); userInfoRepository.saveAndFlush(newUser);
ret.setUsername(dbUserName); ret.setUsername(dbUserName);
@ -75,9 +76,9 @@ public class PowerJobLoginServiceImpl implements PowerJobLoginService {
} }
@Override @Override
public Optional<PowerJobUser> parse(LoginContext loginContext) { public Optional<PowerJobUser> parse(HttpServletRequest httpServletRequest) {
final Optional<String> usernameOpt = parseUsername(loginContext); final Optional<String> usernameOpt = parseUsername(httpServletRequest);
if (!usernameOpt.isPresent()) { if (!usernameOpt.isPresent()) {
return Optional.empty(); return Optional.empty();
} }
@ -88,8 +89,12 @@ public class PowerJobLoginServiceImpl implements PowerJobLoginService {
}); });
} }
private Optional<String> parseUsername(LoginContext loginContext) { @Override
final HttpServletRequest httpServletRequest = loginContext.getHttpServletRequest(); public boolean hasPermission(PowerJobUser user, ApiPermission apiPermission) {
return false;
}
private Optional<String> parseUsername(HttpServletRequest httpServletRequest) {
final String tokenHeader = httpServletRequest.getHeader(TOKEN_HEADER_NAME); final String tokenHeader = httpServletRequest.getHeader(TOKEN_HEADER_NAME);
if (StringUtils.isEmpty(tokenHeader)) { if (StringUtils.isEmpty(tokenHeader)) {
return Optional.empty(); return Optional.empty();