feat: [auth] add controller and loginUserHolder api

This commit is contained in:
tjq 2023-04-16 16:09:39 +08:00
parent 198ad9bf46
commit d789d68180
9 changed files with 154 additions and 67 deletions

View File

@ -0,0 +1,24 @@
package tech.powerjob.server.auth;
/**
* LoginUserHolder
*
* @author tjq
* @since 2023/4/16
*/
public class LoginUserHolder {
private static final ThreadLocal<PowerJobUser> TL = new ThreadLocal<>();
public static PowerJobUser get() {
return TL.get();
}
public static void set(PowerJobUser powerJobUser) {
TL.set(powerJobUser);
}
public static void clean() {
TL.remove();
}
}

View File

@ -7,6 +7,7 @@ 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.LoginUserHolder;
import tech.powerjob.server.auth.PowerJobUser;
import tech.powerjob.server.auth.anno.ApiPermission;
import tech.powerjob.server.auth.service.PowerJobAuthService;
@ -54,6 +55,10 @@ public class PowerJobAuthInterceptor implements HandlerInterceptor {
// 登陆用户进行权限校验
final PowerJobUser powerJobUser = loginUserOpt.get();
// 写入上下文
LoginUserHolder.set(powerJobUser);
final boolean hasPermission = powerJobAuthService.hasPermission(request, powerJobUser, apiPermissionAnno);
if (hasPermission) {
return true;
@ -65,6 +70,11 @@ public class PowerJobAuthInterceptor implements HandlerInterceptor {
throw new PowerJobException("Permission denied!");
}
@Override
public void afterCompletion(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler, Exception ex) throws Exception {
LoginUserHolder.clean();
}
private static String parseResourceName(ApiPermission apiPermission, HandlerMethod handlerMethod) {
final String name = apiPermission.name();
if (StringUtils.isNotEmpty(name)) {

View File

@ -37,10 +37,10 @@ public class JwtServiceImpl implements JwtService {
* <a href="https://music.163.com/#/song?id=167975">GoodSong</a>
*/
private static final String BASE_SECURITY =
"CengMengXiangZhangJianZouTianYa" +
"KanYiKanShiJieDeFanHua" +
"NianShaoDeXinZongYouXieQingKuang" +
"RuJinWoSiHaiWeiJia"
"死去元知万事空" +
"但悲不见九州同" +
"王师北定中原日" +
"家祭无忘告乃翁"
;
@Override
@ -72,7 +72,7 @@ public class JwtServiceImpl implements JwtService {
.build()
.parseClaimsJws(jwtStr);
Map<String, Object> ret = Maps.newHashMap();
claimsJws.getBody().forEach(ret::put);
ret.putAll(claimsJws.getBody());
return ret;
}

View File

@ -50,11 +50,11 @@ public class DingTalkBizLoginService implements BizLoginService {
@Value("${oms.auth.dingtalk.callbackUrl}")
private String dingTalkCallbackUrl;
private static final String DEFAULT_LOGIN_SERVICE = "DingTalk";
private static final String DING_TALK = "DingTalk";
@Override
public String type() {
return DEFAULT_LOGIN_SERVICE;
return DING_TALK;
}
@Override
@ -71,7 +71,7 @@ public class DingTalkBizLoginService implements BizLoginService {
"&response_type=code" +
"&client_id=" + dingTalkAppKey +
"&scope=openid" +
"&state=DingTalk" +
"&state=" + DING_TALK +
"&prompt=consent";
Loggers.WEB.info("[DingTalkBizLoginService] login url: {}", url);
return url;

View File

@ -23,7 +23,7 @@ import java.util.Optional;
* @since 2023/3/20
*/
@Service
public class DefaultBizLoginService implements BizLoginService {
public class PowerJobSelfLoginService implements BizLoginService {
@Resource
private UserInfoRepository userInfoRepository;
@ -40,7 +40,7 @@ public class DefaultBizLoginService implements BizLoginService {
@Override
public String loginUrl() {
return "forward:/user/loginCallback";
return null;
}
@Override

View File

@ -5,6 +5,7 @@ import tech.powerjob.server.auth.PowerJobUser;
import tech.powerjob.server.auth.anno.ApiPermission;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Optional;
/**
@ -15,6 +16,8 @@ import java.util.Optional;
*/
public interface PowerJobAuthService {
List<String> supportTypes();
/**
* 开始登陆
* @param loginContext 请求

View File

@ -1,6 +1,7 @@
package tech.powerjob.server.auth.service;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import org.apache.commons.lang3.StringUtils;
@ -39,7 +40,7 @@ public class PowerJobAuthServiceImpl implements PowerJobAuthService {
private final UserRoleRepository userRoleRepository;
private final Map<String, BizLoginService> type2LoginService = Maps.newHashMap();
private static final String JWT_NAME = "powerjob_token";
private static final String JWT_NAME = "power_jwt";
private static final String KEY_USERID = "userId";
@ -52,6 +53,11 @@ public class PowerJobAuthServiceImpl implements PowerJobAuthService {
}
@Override
public List<String> supportTypes() {
return Lists.newArrayList(type2LoginService.keySet());
}
@Override
public String startLogin(LoginContext loginContext) {
final BizLoginService loginService = fetchBizLoginService(loginContext);
@ -68,20 +74,20 @@ public class PowerJobAuthServiceImpl implements PowerJobAuthService {
final Optional<UserInfoDO> powerJobUserOpt = userInfoRepository.findByUsername(dbUserName);
PowerJobUser ret = new PowerJobUser();
// 存在则响应 PowerJob 用户
// 存在则响应 PowerJob 用户否则同步在 PowerJob 用户库创建该用户
if (powerJobUserOpt.isPresent()) {
final UserInfoDO dbUser = powerJobUserOpt.get();
BeanUtils.copyProperties(dbUser, ret);
ret.setUsername(dbUserName);
return ret;
}
// 同步在 PowerJob 用户库创建该用户
} else {
UserInfoDO newUser = new UserInfoDO();
newUser.setUsername(dbUserName);
Loggers.WEB.info("[PowerJobLoginService] sync user to PowerJobUserSystem: {}", dbUserName);
userInfoRepository.saveAndFlush(newUser);
ret.setUsername(dbUserName);
}
fillJwt(ret);
return ret;
}
@ -164,4 +170,12 @@ public class PowerJobAuthServiceImpl implements PowerJobAuthService {
}
return loginService;
}
private void fillJwt(PowerJobUser powerJobUser) {
Map<String, Object> jwtMap = Maps.newHashMap();
jwtMap.put(KEY_USERID, powerJobUser.getId());
powerJobUser.setJwtToken(jwtService.build(jwtMap));
}
}

View File

@ -0,0 +1,84 @@
package tech.powerjob.server.web.controller;
import lombok.SneakyThrows;
import org.springframework.web.bind.annotation.*;
import tech.powerjob.common.response.ResultDTO;
import tech.powerjob.server.auth.LoginContext;
import tech.powerjob.server.auth.PowerJobUser;
import tech.powerjob.server.auth.service.PowerJobAuthService;
import tech.powerjob.server.web.request.UserLoginRequest;
import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 登录 & 权限相关
*
* @author tjq
* @since 2023/4/16
*/
@RestController
@RequestMapping("/auth")
public class AuthController {
@Resource
private PowerJobAuthService powerJobAuthService;
@GetMapping("/supportTypes")
public ResultDTO<List<String>> options() {
return ResultDTO.success(powerJobAuthService.supportTypes());
}
@SneakyThrows
@GetMapping("/startLogin")
public String tryLogin(UserLoginRequest loginRequest, HttpServletRequest request, HttpServletResponse response) {
LoginContext loginContext = new LoginContext()
.setLoginType(loginRequest.getType())
.setLoginInfo(loginRequest.getLoginInfo())
.setHttpServletRequest(request);
final String realLoginUrl = powerJobAuthService.startLogin(loginContext);
// 统一重定向
response.sendRedirect(realLoginUrl);
return null;
}
@PostMapping("/selfLogin")
public ResultDTO<PowerJobUser> selfLogin(LoginContext loginContext, HttpServletResponse httpServletResponse) {
try {
final PowerJobUser powerJobUser = powerJobAuthService.tryLogin(loginContext);
if (powerJobUser == null) {
return ResultDTO.failed("USER_NOT_FOUND");
}
httpServletResponse.addCookie(new Cookie("power_jwt", powerJobUser.getJwtToken()));
return ResultDTO.success(powerJobUser);
} catch (Exception e) {
return ResultDTO.failed(e.getMessage());
}
}
/**
* 第三方账号体系回调登录接口
* @param httpServletRequest 请求
* @param httpServletResponse 响应
* @return 登录结果
*/
@RequestMapping(value = "/loginCallback", method = {RequestMethod.GET, RequestMethod.POST})
public ResultDTO<PowerJobUser> loginCallback(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
LoginContext loginContext = new LoginContext()
.setHttpServletRequest(httpServletRequest);
// 常见登录组件的标准规范钉钉企业微信飞书第三方原样透传
final String state = httpServletRequest.getParameter("state");
loginContext.setLoginType(state);
final PowerJobUser powerJobUser = powerJobAuthService.tryLogin(loginContext);
httpServletResponse.addCookie(new Cookie("power_jwt", powerJobUser.getJwtToken()));
return ResultDTO.success(powerJobUser);
}
}

View File

@ -4,25 +4,17 @@ import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import tech.powerjob.common.response.ResultDTO;
import tech.powerjob.server.auth.LoginContext;
import tech.powerjob.server.auth.PowerJobUser;
import tech.powerjob.server.auth.service.PowerJobAuthService;
import tech.powerjob.server.core.service.UserService;
import tech.powerjob.server.persistence.remote.model.UserInfoDO;
import tech.powerjob.server.persistence.remote.repository.UserInfoRepository;
import tech.powerjob.server.web.request.ModifyUserInfoRequest;
import tech.powerjob.server.web.request.UserLoginRequest;
import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.stream.Collectors;
@ -39,46 +31,6 @@ public class UserInfoController {
private UserService userService;
@Resource
private UserInfoRepository userInfoRepository;
@Resource
private PowerJobAuthService powerJobAuthService;
@SneakyThrows
@GetMapping("/startLogin")
public String tryLogin(UserLoginRequest loginRequest, HttpServletRequest request, HttpServletResponse response) {
LoginContext loginContext = new LoginContext()
.setLoginType(loginRequest.getType())
.setLoginInfo(loginRequest.getLoginInfo())
.setHttpServletRequest(request);
final String realLoginUrl = powerJobAuthService.startLogin(loginContext);
// 统一重定向
response.sendRedirect(realLoginUrl);
return null;
}
@RequestMapping(value = "/loginCallback", method = {RequestMethod.GET, RequestMethod.POST})
public ResultDTO<PowerJobUser> loginCallback(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
LoginContext loginContext = new LoginContext()
.setHttpServletRequest(httpServletRequest);
// 尝试读取 body
if (RequestMethod.POST.name().equalsIgnoreCase(httpServletRequest.getMethod())) {
// TODO: post 读取 body
}
// 钉钉回调
final String state = httpServletRequest.getParameter("state");
if ("DingTalk".equalsIgnoreCase(state)) {
loginContext.setLoginType("DingTalk");
}
final PowerJobUser powerJobUser = powerJobAuthService.tryLogin(loginContext);
httpServletResponse.addCookie(new Cookie("powerjob_token", powerJobUser.getJwtToken()));
return ResultDTO.success(powerJobUser);
}
@PostMapping("save")
public ResultDTO<Void> save(@RequestBody ModifyUserInfoRequest request) {