feat: [auth] add login api

This commit is contained in:
tjq 2023-03-27 00:34:40 +08:00
parent 0508b902df
commit 198ad9bf46
13 changed files with 129 additions and 48 deletions

View File

@ -98,6 +98,11 @@
<artifactId>powerjob-server-starter</artifactId> <artifactId>powerjob-server-starter</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>tech.powerjob</groupId>
<artifactId>powerjob-server-auth</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>

View File

@ -10,6 +10,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>powerjob-server-auth</artifactId> <artifactId>powerjob-server-auth</artifactId>
<version>${project.parent.version}</version>
<properties> <properties>
<maven.compiler.source>8</maven.compiler.source> <maven.compiler.source>8</maven.compiler.source>

View File

@ -16,8 +16,6 @@ import javax.servlet.http.HttpServletRequest;
@Setter @Setter
@Accessors(chain = true) @Accessors(chain = true)
public class LoginContext { public class LoginContext {
private HttpServletRequest httpServletRequest;
/** /**
* 登陆类型 * 登陆类型
*/ */
@ -26,4 +24,6 @@ public class LoginContext {
* 登陆信息取决于登陆类型比如 PowerJob 自带的账号密码为 uid:xxx;pwd:yyy * 登陆信息取决于登陆类型比如 PowerJob 自带的账号密码为 uid:xxx;pwd:yyy
*/ */
private String loginInfo; private String loginInfo;
private transient HttpServletRequest httpServletRequest;
} }

View File

@ -39,4 +39,6 @@ public class PowerJobUser implements Serializable {
private String extra; private String extra;
/* ************** 以上为数据库字段 ************** */ /* ************** 以上为数据库字段 ************** */
private String jwtToken;
} }

View File

@ -4,8 +4,8 @@ import com.google.common.collect.Maps;
import io.jsonwebtoken.*; import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.Keys;
import io.swagger.v3.oas.annotations.servers.Server;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import tech.powerjob.server.auth.jwt.JwtService; import tech.powerjob.server.auth.jwt.JwtService;
import tech.powerjob.server.auth.jwt.SecretProvider; import tech.powerjob.server.auth.jwt.SecretProvider;
@ -21,7 +21,7 @@ import java.util.UUID;
* @author tjq * @author tjq
* @since 2023/3/20 * @since 2023/3/20
*/ */
@Server @Service
public class JwtServiceImpl implements JwtService { public class JwtServiceImpl implements JwtService {
@Resource @Resource

View File

@ -2,8 +2,6 @@ package tech.powerjob.server.auth.login;
import tech.powerjob.server.auth.LoginContext; import tech.powerjob.server.auth.LoginContext;
import java.util.Optional;
/** /**
* 用户登陆服务 * 用户登陆服务
* *
@ -29,5 +27,5 @@ public interface BizLoginService {
* @param loginContext 登陆上下文 * @param loginContext 登陆上下文
* @return PowerJob 用户 * @return PowerJob 用户
*/ */
Optional<BizUser> login(LoginContext loginContext); BizUser login(LoginContext loginContext);
} }

View File

@ -3,6 +3,7 @@ package tech.powerjob.server.auth.login.impl;
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.Loggers;
import tech.powerjob.common.exception.PowerJobException;
import tech.powerjob.common.utils.DigestUtils; import tech.powerjob.common.utils.DigestUtils;
import tech.powerjob.server.auth.LoginContext; import tech.powerjob.server.auth.LoginContext;
import tech.powerjob.server.auth.login.BizLoginService; import tech.powerjob.server.auth.login.BizLoginService;
@ -39,16 +40,15 @@ public class DefaultBizLoginService implements BizLoginService {
@Override @Override
public String loginUrl() { public String loginUrl() {
// 默认登陆方式不需要重定向 return "forward:/user/loginCallback";
return null;
} }
@Override @Override
public Optional<BizUser> login(LoginContext loginContext) { public BizUser login(LoginContext loginContext) {
final String loginInfo = loginContext.getLoginInfo(); final String loginInfo = loginContext.getLoginInfo();
if (StringUtils.isEmpty(loginInfo)) { if (StringUtils.isEmpty(loginInfo)) {
return Optional.empty(); throw new IllegalArgumentException("can't find login Info");
} }
final Map<String, String> loginInfoMap = SJ.splitKvString(loginInfo); final Map<String, String> loginInfoMap = SJ.splitKvString(loginInfo);
@ -57,13 +57,13 @@ public class DefaultBizLoginService implements BizLoginService {
if (StringUtils.isAnyEmpty(username, password)) { if (StringUtils.isAnyEmpty(username, password)) {
Loggers.WEB.debug("[DefaultBizLoginService] username or password is empty, login failed!"); Loggers.WEB.debug("[DefaultBizLoginService] username or password is empty, login failed!");
return Optional.empty(); throw new IllegalArgumentException("username or password is empty!");
} }
final Optional<UserInfoDO> userInfoOpt = userInfoRepository.findByUsername(username); final Optional<UserInfoDO> userInfoOpt = userInfoRepository.findByUsername(username);
if (!userInfoOpt.isPresent()) { if (!userInfoOpt.isPresent()) {
Loggers.WEB.debug("[DefaultBizLoginService] can't find user by username: {}", username); Loggers.WEB.debug("[DefaultBizLoginService] can't find user by username: {}", username);
return Optional.empty(); throw new PowerJobException("can't find user by username: " + username);
} }
final UserInfoDO dbUser = userInfoOpt.get(); final UserInfoDO dbUser = userInfoOpt.get();
@ -71,11 +71,11 @@ public class DefaultBizLoginService implements BizLoginService {
if (s(username, password).equals(dbUser.getPassword())) { if (s(username, password).equals(dbUser.getPassword())) {
BizUser bizUser = new BizUser(); BizUser bizUser = new BizUser();
bizUser.setUsername(username); bizUser.setUsername(username);
return Optional.of(bizUser); return bizUser;
} }
Loggers.WEB.debug("[DefaultBizLoginService] user[{}]'s password is not correct, login failed!", username); Loggers.WEB.debug("[DefaultBizLoginService] user[{}]'s password is incorrect, login failed!", username);
return Optional.empty(); throw new PowerJobException("password is incorrect");
} }
private static String s(String username, String password) { private static String s(String username, String password) {

View File

@ -11,13 +11,13 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import tech.powerjob.common.Loggers; import tech.powerjob.common.Loggers;
import tech.powerjob.common.exception.PowerJobException;
import tech.powerjob.server.auth.LoginContext; import tech.powerjob.server.auth.LoginContext;
import tech.powerjob.server.auth.login.BizLoginService; import tech.powerjob.server.auth.login.BizLoginService;
import tech.powerjob.server.auth.login.BizUser; import tech.powerjob.server.auth.login.BizUser;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Optional;
/** /**
* <a href="https://open.dingtalk.com/document/orgapp/tutorial-obtaining-user-personal-information">钉钉账号体系登录第三方网站</a> * <a href="https://open.dingtalk.com/document/orgapp/tutorial-obtaining-user-personal-information">钉钉账号体系登录第三方网站</a>
@ -78,7 +78,8 @@ public class DingTalkBizLoginService implements BizLoginService {
} }
@Override @Override
public Optional<BizUser> login(LoginContext loginContext) { @SneakyThrows
public BizUser login(LoginContext loginContext) {
try { try {
com.aliyun.dingtalkoauth2_1_0.Client client = authClient(); com.aliyun.dingtalkoauth2_1_0.Client client = authClient();
GetUserTokenRequest getUserTokenRequest = new GetUserTokenRequest() GetUserTokenRequest getUserTokenRequest = new GetUserTokenRequest()
@ -100,12 +101,13 @@ public class DingTalkBizLoginService implements BizLoginService {
bizUser.setNick(dingUser.getNick()); bizUser.setNick(dingUser.getNick());
bizUser.setPhone(dingUser.getMobile()); bizUser.setPhone(dingUser.getMobile());
bizUser.setEmail(dingUser.getEmail()); bizUser.setEmail(dingUser.getEmail());
return Optional.of(bizUser); return bizUser;
} }
} catch (Exception e) { } catch (Exception e) {
Loggers.WEB.error("[DingTalkBizLoginService] login by dingTalk failed!", e); Loggers.WEB.error("[DingTalkBizLoginService] login by dingTalk failed!", e);
throw e;
} }
return Optional.empty(); throw new PowerJobException("login from dingTalk failed!");
} }
/* 以下代码均拷自钉钉官网示例 */ /* 以下代码均拷自钉钉官网示例 */

View File

@ -15,12 +15,19 @@ import java.util.Optional;
*/ */
public interface PowerJobAuthService { public interface PowerJobAuthService {
/**
* 开始登陆
* @param loginContext 请求
* @return 转发 or 重定向到真正的登陆页
*/
String startLogin(LoginContext loginContext);
/** /**
* 执行真正的登陆操作 * 执行真正的登陆操作
* @param loginContext 登录上下文 * @param loginContext 登录上下文
* @return PowerJob 用户 * @return PowerJob 用户
*/ */
Optional<PowerJobUser> login(LoginContext loginContext); PowerJobUser tryLogin(LoginContext loginContext);
/** /**

View File

@ -32,7 +32,7 @@ import java.util.*;
* @since 2023/3/21 * @since 2023/3/21
*/ */
@Service @Service
public class PowerJobLoginServiceImpl implements PowerJobAuthService { public class PowerJobAuthServiceImpl implements PowerJobAuthService {
private final JwtService jwtService; private final JwtService jwtService;
private final UserInfoRepository userInfoRepository; private final UserInfoRepository userInfoRepository;
@ -44,7 +44,7 @@ public class PowerJobLoginServiceImpl implements PowerJobAuthService {
private static final String KEY_USERID = "userId"; private static final String KEY_USERID = "userId";
@Autowired @Autowired
public PowerJobLoginServiceImpl(List<BizLoginService> loginServices, JwtService jwtService, UserInfoRepository userInfoRepository, UserRoleRepository userRoleRepository) { public PowerJobAuthServiceImpl(List<BizLoginService> loginServices, JwtService jwtService, UserInfoRepository userInfoRepository, UserRoleRepository userRoleRepository) {
this.jwtService = jwtService; this.jwtService = jwtService;
this.userInfoRepository = userInfoRepository; this.userInfoRepository = userInfoRepository;
this.userRoleRepository = userRoleRepository; this.userRoleRepository = userRoleRepository;
@ -53,17 +53,16 @@ public class PowerJobLoginServiceImpl implements PowerJobAuthService {
@Override @Override
public Optional<PowerJobUser> login(LoginContext loginContext) { public String startLogin(LoginContext loginContext) {
final BizLoginService loginService = fetchBizLoginService(loginContext);
return loginService.loginUrl();
}
@Override
public PowerJobUser tryLogin(LoginContext loginContext) {
final String loginType = loginContext.getLoginType(); final String loginType = loginContext.getLoginType();
final BizLoginService loginService = type2LoginService.get(loginType); final BizLoginService loginService = fetchBizLoginService(loginContext);
if (loginService == null) { final BizUser bizUser = loginService.login(loginContext);
throw new IllegalArgumentException("can't find LoginService by type: " + loginType);
}
final Optional<BizUser> bizUserOpt = loginService.login(loginContext);
if (!bizUserOpt.isPresent()) {
return Optional.empty();
}
final BizUser bizUser = bizUserOpt.get();
String dbUserName = String.format("%s_%s", loginType, bizUser.getUsername()); String dbUserName = String.format("%s_%s", loginType, bizUser.getUsername());
final Optional<UserInfoDO> powerJobUserOpt = userInfoRepository.findByUsername(dbUserName); final Optional<UserInfoDO> powerJobUserOpt = userInfoRepository.findByUsername(dbUserName);
@ -74,7 +73,7 @@ public class PowerJobLoginServiceImpl implements PowerJobAuthService {
final UserInfoDO dbUser = powerJobUserOpt.get(); final UserInfoDO dbUser = powerJobUserOpt.get();
BeanUtils.copyProperties(dbUser, ret); BeanUtils.copyProperties(dbUser, ret);
ret.setUsername(dbUserName); ret.setUsername(dbUserName);
return Optional.of(ret); return ret;
} }
// 同步在 PowerJob 用户库创建该用户 // 同步在 PowerJob 用户库创建该用户
@ -84,7 +83,7 @@ public class PowerJobLoginServiceImpl implements PowerJobAuthService {
userInfoRepository.saveAndFlush(newUser); userInfoRepository.saveAndFlush(newUser);
ret.setUsername(dbUserName); ret.setUsername(dbUserName);
return Optional.of(ret); return ret;
} }
@Override @Override
@ -156,4 +155,13 @@ public class PowerJobLoginServiceImpl implements PowerJobAuthService {
return Optional.of(Long.parseLong(String.valueOf(userId))); return Optional.of(Long.parseLong(String.valueOf(userId)));
} }
private BizLoginService fetchBizLoginService(LoginContext loginContext) {
final String loginType = loginContext.getLoginType();
final BizLoginService loginService = type2LoginService.get(loginType);
if (loginService == null) {
throw new IllegalArgumentException("can't find LoginService by type: " + loginType);
}
return loginService;
}
} }

View File

@ -43,6 +43,10 @@
<groupId>tech.powerjob</groupId> <groupId>tech.powerjob</groupId>
<artifactId>powerjob-server-core</artifactId> <artifactId>powerjob-server-core</artifactId>
</dependency> </dependency>
<dependency>
<groupId>tech.powerjob</groupId>
<artifactId>powerjob-server-auth</artifactId>
</dependency>
<dependency> <dependency>
<groupId>tech.powerjob</groupId> <groupId>tech.powerjob</groupId>
<artifactId>powerjob-server-migrate</artifactId> <artifactId>powerjob-server-migrate</artifactId>

View File

@ -1,21 +1,28 @@
package tech.powerjob.server.web.controller; package tech.powerjob.server.web.controller;
import tech.powerjob.common.response.ResultDTO;
import org.springframework.beans.BeanUtils;
import tech.powerjob.server.persistence.remote.model.UserInfoDO;
import tech.powerjob.server.persistence.remote.repository.UserInfoRepository;
import tech.powerjob.server.core.service.UserService;
import tech.powerjob.server.web.request.ModifyUserInfoRequest;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.springframework.util.CollectionUtils; import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*; 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.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -28,21 +35,49 @@ import java.util.stream.Collectors;
@RestController @RestController
@RequestMapping("/user") @RequestMapping("/user")
public class UserInfoController { public class UserInfoController {
@Resource @Resource
private UserService userService; private UserService userService;
@Resource @Resource
private UserInfoRepository userInfoRepository; 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
}
@GetMapping("/loginCallback")
public ResultDTO<Void> loginCallback(HttpServletRequest httpServletRequest) {
// 钉钉回调 // 钉钉回调
final String state = httpServletRequest.getParameter("state"); final String state = httpServletRequest.getParameter("state");
if ("DingTalk".equalsIgnoreCase(state)) { if ("DingTalk".equalsIgnoreCase(state)) {
// TODO: 钉钉服务 loginContext.setLoginType("DingTalk");
} }
// TODO: 承接登录回调功能
return null; final PowerJobUser powerJobUser = powerJobAuthService.tryLogin(loginContext);
httpServletResponse.addCookie(new Cookie("powerjob_token", powerJobUser.getJwtToken()));
return ResultDTO.success(powerJobUser);
} }
@PostMapping("save") @PostMapping("save")

View File

@ -0,0 +1,19 @@
package tech.powerjob.server.web.request;
import lombok.Data;
import java.io.Serializable;
/**
* 用户登录请求
*
* @author tjq
* @since 2023/3/26
*/
@Data
public class UserLoginRequest implements Serializable {
private String type;
private String loginInfo;
}