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>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>tech.powerjob</groupId>
<artifactId>powerjob-server-auth</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

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

View File

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

View File

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

View File

@ -4,8 +4,8 @@ import com.google.common.collect.Maps;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import io.swagger.v3.oas.annotations.servers.Server;
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.SecretProvider;
@ -21,7 +21,7 @@ import java.util.UUID;
* @author tjq
* @since 2023/3/20
*/
@Server
@Service
public class JwtServiceImpl implements JwtService {
@Resource

View File

@ -2,8 +2,6 @@ package tech.powerjob.server.auth.login;
import tech.powerjob.server.auth.LoginContext;
import java.util.Optional;
/**
* 用户登陆服务
*
@ -29,5 +27,5 @@ public interface BizLoginService {
* @param loginContext 登陆上下文
* @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.springframework.stereotype.Service;
import tech.powerjob.common.Loggers;
import tech.powerjob.common.exception.PowerJobException;
import tech.powerjob.common.utils.DigestUtils;
import tech.powerjob.server.auth.LoginContext;
import tech.powerjob.server.auth.login.BizLoginService;
@ -39,16 +40,15 @@ public class DefaultBizLoginService implements BizLoginService {
@Override
public String loginUrl() {
// 默认登陆方式不需要重定向
return null;
return "forward:/user/loginCallback";
}
@Override
public Optional<BizUser> login(LoginContext loginContext) {
public BizUser login(LoginContext loginContext) {
final String loginInfo = loginContext.getLoginInfo();
if (StringUtils.isEmpty(loginInfo)) {
return Optional.empty();
throw new IllegalArgumentException("can't find login Info");
}
final Map<String, String> loginInfoMap = SJ.splitKvString(loginInfo);
@ -57,13 +57,13 @@ public class DefaultBizLoginService implements BizLoginService {
if (StringUtils.isAnyEmpty(username, password)) {
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);
if (!userInfoOpt.isPresent()) {
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();
@ -71,11 +71,11 @@ public class DefaultBizLoginService implements BizLoginService {
if (s(username, password).equals(dbUser.getPassword())) {
BizUser bizUser = new BizUser();
bizUser.setUsername(username);
return Optional.of(bizUser);
return bizUser;
}
Loggers.WEB.debug("[DefaultBizLoginService] user[{}]'s password is not correct, login failed!", username);
return Optional.empty();
Loggers.WEB.debug("[DefaultBizLoginService] user[{}]'s password is incorrect, login failed!", username);
throw new PowerJobException("password is incorrect");
}
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.stereotype.Service;
import tech.powerjob.common.Loggers;
import tech.powerjob.common.exception.PowerJobException;
import tech.powerjob.server.auth.LoginContext;
import tech.powerjob.server.auth.login.BizLoginService;
import tech.powerjob.server.auth.login.BizUser;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
/**
* <a href="https://open.dingtalk.com/document/orgapp/tutorial-obtaining-user-personal-information">钉钉账号体系登录第三方网站</a>
@ -78,7 +78,8 @@ public class DingTalkBizLoginService implements BizLoginService {
}
@Override
public Optional<BizUser> login(LoginContext loginContext) {
@SneakyThrows
public BizUser login(LoginContext loginContext) {
try {
com.aliyun.dingtalkoauth2_1_0.Client client = authClient();
GetUserTokenRequest getUserTokenRequest = new GetUserTokenRequest()
@ -100,12 +101,13 @@ public class DingTalkBizLoginService implements BizLoginService {
bizUser.setNick(dingUser.getNick());
bizUser.setPhone(dingUser.getMobile());
bizUser.setEmail(dingUser.getEmail());
return Optional.of(bizUser);
return bizUser;
}
} catch (Exception 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 {
/**
* 开始登陆
* @param loginContext 请求
* @return 转发 or 重定向到真正的登陆页
*/
String startLogin(LoginContext loginContext);
/**
* 执行真正的登陆操作
* @param loginContext 登录上下文
* @return PowerJob 用户
*/
Optional<PowerJobUser> login(LoginContext loginContext);
PowerJobUser tryLogin(LoginContext loginContext);
/**

View File

@ -32,7 +32,7 @@ import java.util.*;
* @since 2023/3/21
*/
@Service
public class PowerJobLoginServiceImpl implements PowerJobAuthService {
public class PowerJobAuthServiceImpl implements PowerJobAuthService {
private final JwtService jwtService;
private final UserInfoRepository userInfoRepository;
@ -44,7 +44,7 @@ public class PowerJobLoginServiceImpl implements PowerJobAuthService {
private static final String KEY_USERID = "userId";
@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.userInfoRepository = userInfoRepository;
this.userRoleRepository = userRoleRepository;
@ -53,17 +53,16 @@ public class PowerJobLoginServiceImpl implements PowerJobAuthService {
@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 BizLoginService loginService = type2LoginService.get(loginType);
if (loginService == null) {
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();
final BizLoginService loginService = fetchBizLoginService(loginContext);
final BizUser bizUser = loginService.login(loginContext);
String dbUserName = String.format("%s_%s", loginType, bizUser.getUsername());
final Optional<UserInfoDO> powerJobUserOpt = userInfoRepository.findByUsername(dbUserName);
@ -74,7 +73,7 @@ public class PowerJobLoginServiceImpl implements PowerJobAuthService {
final UserInfoDO dbUser = powerJobUserOpt.get();
BeanUtils.copyProperties(dbUser, ret);
ret.setUsername(dbUserName);
return Optional.of(ret);
return ret;
}
// 同步在 PowerJob 用户库创建该用户
@ -84,7 +83,7 @@ public class PowerJobLoginServiceImpl implements PowerJobAuthService {
userInfoRepository.saveAndFlush(newUser);
ret.setUsername(dbUserName);
return Optional.of(ret);
return ret;
}
@Override
@ -156,4 +155,13 @@ public class PowerJobLoginServiceImpl implements PowerJobAuthService {
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>
<artifactId>powerjob-server-core</artifactId>
</dependency>
<dependency>
<groupId>tech.powerjob</groupId>
<artifactId>powerjob-server-auth</artifactId>
</dependency>
<dependency>
<groupId>tech.powerjob</groupId>
<artifactId>powerjob-server-migrate</artifactId>

View File

@ -1,21 +1,28 @@
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 lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.util.CollectionUtils;
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;
@ -28,21 +35,49 @@ import java.util.stream.Collectors;
@RestController
@RequestMapping("/user")
public class UserInfoController {
@Resource
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
}
@GetMapping("/loginCallback")
public ResultDTO<Void> loginCallback(HttpServletRequest httpServletRequest) {
// 钉钉回调
final String state = httpServletRequest.getParameter("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")

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;
}