feat: support oauth2 login
This commit is contained in:
parent
18edc58d2f
commit
c1ab379f31
|
@ -26,6 +26,7 @@ dependencies {
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
|
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-quartz'
|
implementation 'org.springframework.boot:spring-boot-starter-quartz'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
|
||||||
|
|
||||||
implementation 'org.flywaydb:flyway-core'
|
implementation 'org.flywaydb:flyway-core'
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.databasir.api;
|
package com.databasir.api;
|
||||||
|
|
||||||
|
import com.databasir.api.config.security.DatabasirUserDetails;
|
||||||
import com.databasir.common.DatabasirException;
|
import com.databasir.common.DatabasirException;
|
||||||
import com.databasir.common.JsonData;
|
import com.databasir.common.JsonData;
|
||||||
import com.databasir.common.exception.InvalidTokenException;
|
import com.databasir.common.exception.InvalidTokenException;
|
||||||
|
@ -7,10 +8,10 @@ import com.databasir.core.domain.DomainErrors;
|
||||||
import com.databasir.core.domain.log.annotation.Operation;
|
import com.databasir.core.domain.log.annotation.Operation;
|
||||||
import com.databasir.core.domain.login.data.AccessTokenRefreshRequest;
|
import com.databasir.core.domain.login.data.AccessTokenRefreshRequest;
|
||||||
import com.databasir.core.domain.login.data.AccessTokenRefreshResponse;
|
import com.databasir.core.domain.login.data.AccessTokenRefreshResponse;
|
||||||
|
import com.databasir.core.domain.login.data.UserLoginResponse;
|
||||||
import com.databasir.core.domain.login.service.LoginService;
|
import com.databasir.core.domain.login.service.LoginService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
@ -27,8 +28,6 @@ import java.util.Objects;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class LoginController {
|
public class LoginController {
|
||||||
|
|
||||||
private final AuthenticationManager authenticationManager;
|
|
||||||
|
|
||||||
private final LoginService loginService;
|
private final LoginService loginService;
|
||||||
|
|
||||||
@GetMapping(Routes.Login.LOGOUT)
|
@GetMapping(Routes.Login.LOGOUT)
|
||||||
|
@ -39,8 +38,8 @@ public class LoginController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(Routes.Login.REFRESH_ACCESS_TOKEN)
|
@PostMapping(Routes.Login.REFRESH_ACCESS_TOKEN)
|
||||||
public JsonData<AccessTokenRefreshResponse> refreshAccessTokens(@RequestBody @Valid
|
public JsonData<AccessTokenRefreshResponse> refreshAccessTokens(@RequestBody
|
||||||
AccessTokenRefreshRequest request) {
|
@Valid AccessTokenRefreshRequest request) {
|
||||||
try {
|
try {
|
||||||
return JsonData.ok(loginService.refreshAccessTokens(request));
|
return JsonData.ok(loginService.refreshAccessTokens(request));
|
||||||
} catch (DatabasirException e) {
|
} catch (DatabasirException e) {
|
||||||
|
@ -54,4 +53,13 @@ public class LoginController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping(Routes.Login.LOGIN_INFO)
|
||||||
|
public JsonData<UserLoginResponse> getUserLoginData() {
|
||||||
|
DatabasirUserDetails user = (DatabasirUserDetails) SecurityContextHolder.getContext()
|
||||||
|
.getAuthentication()
|
||||||
|
.getPrincipal();
|
||||||
|
Integer userId = user.getUserPojo().getId();
|
||||||
|
return JsonData.ok(loginService.getUserLoginData(userId));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.databasir.api;
|
||||||
|
|
||||||
|
import com.databasir.common.JsonData;
|
||||||
|
import com.databasir.core.infrastructure.oauth2.OAuthAppService;
|
||||||
|
import com.databasir.core.infrastructure.oauth2.OAuthHandler;
|
||||||
|
import com.databasir.core.infrastructure.oauth2.data.OAuthAppResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class OAuth2AppController {
|
||||||
|
|
||||||
|
private final OAuthHandler oAuthHandler;
|
||||||
|
|
||||||
|
private final OAuthAppService oAuthAppService;
|
||||||
|
|
||||||
|
@GetMapping("/oauth2/authorization/{registrationId}")
|
||||||
|
@ResponseBody
|
||||||
|
public JsonData<String> authorization(@PathVariable String registrationId) {
|
||||||
|
String authorization = oAuthHandler.authorization(registrationId);
|
||||||
|
return JsonData.ok(authorization);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/oauth2/apps")
|
||||||
|
@ResponseBody
|
||||||
|
public JsonData<List<OAuthAppResponse>> listApps() {
|
||||||
|
return JsonData.ok(oAuthAppService.listAll());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,60 +0,0 @@
|
||||||
package com.databasir.api;
|
|
||||||
|
|
||||||
import com.databasir.common.JsonData;
|
|
||||||
import com.databasir.core.domain.login.data.LoginKeyResponse;
|
|
||||||
import com.databasir.core.infrastructure.oauth2.OAuthAppService;
|
|
||||||
import com.databasir.core.infrastructure.oauth2.OAuthHandler;
|
|
||||||
import com.databasir.core.infrastructure.oauth2.data.OAuthAppResponse;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.servlet.view.RedirectView;
|
|
||||||
|
|
||||||
import javax.servlet.http.Cookie;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Controller
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class OAuth2LoginController {
|
|
||||||
|
|
||||||
private final OAuthHandler oAuthHandler;
|
|
||||||
|
|
||||||
private final OAuthAppService oAuthAppService;
|
|
||||||
|
|
||||||
@GetMapping("/oauth2/authorization/{registrationId}")
|
|
||||||
public RedirectView authorization(@PathVariable String registrationId) {
|
|
||||||
String authorization = oAuthHandler.authorization(registrationId);
|
|
||||||
return new RedirectView(authorization);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/oauth2/login/{registrationId}")
|
|
||||||
public RedirectView callback(@PathVariable String registrationId,
|
|
||||||
@RequestParam Map<String, String> params,
|
|
||||||
HttpServletResponse response) {
|
|
||||||
LoginKeyResponse loginKey = oAuthAppService.oauthCallback(registrationId, params);
|
|
||||||
// set cookie
|
|
||||||
Cookie accessTokenCookie = new Cookie("accessToken", loginKey.getAccessToken());
|
|
||||||
accessTokenCookie.setPath("/");
|
|
||||||
response.addCookie(accessTokenCookie);
|
|
||||||
|
|
||||||
long epochSecond = loginKey.getAccessTokenExpireAt()
|
|
||||||
.atZone(ZoneId.systemDefault())
|
|
||||||
.toInstant()
|
|
||||||
.toEpochMilli();
|
|
||||||
Cookie accessTokenExpireAtCookie = new Cookie("accessTokenExpireAt", epochSecond + "");
|
|
||||||
accessTokenExpireAtCookie.setPath("/");
|
|
||||||
response.addCookie(accessTokenExpireAtCookie);
|
|
||||||
return new RedirectView("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/oauth2/apps")
|
|
||||||
public JsonData<List<OAuthAppResponse>> listApps() {
|
|
||||||
return JsonData.ok(oAuthAppService.listAll());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -102,6 +102,9 @@ public interface Routes {
|
||||||
String LOGOUT = "/logout";
|
String LOGOUT = "/logout";
|
||||||
|
|
||||||
String REFRESH_ACCESS_TOKEN = "/access_tokens";
|
String REFRESH_ACCESS_TOKEN = "/access_tokens";
|
||||||
|
|
||||||
|
String LOGIN_INFO = "/login_info";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OperationLog {
|
interface OperationLog {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.databasir.api.config;
|
package com.databasir.api.config;
|
||||||
|
|
||||||
import com.databasir.api.Routes;
|
import com.databasir.api.Routes;
|
||||||
|
import com.databasir.api.config.oauth2.DatabasirOauth2LoginFilter;
|
||||||
import com.databasir.api.config.security.*;
|
import com.databasir.api.config.security.*;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.databasir.api.config.oauth2;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
public class DatabasirOAuth2Authentication extends AbstractAuthenticationToken {
|
||||||
|
|
||||||
|
private Object credentials;
|
||||||
|
|
||||||
|
private Object principal;
|
||||||
|
|
||||||
|
public DatabasirOAuth2Authentication(UserDetails principal) {
|
||||||
|
super(principal.getAuthorities());
|
||||||
|
this.credentials = null;
|
||||||
|
this.principal = principal;
|
||||||
|
setAuthenticated(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getCredentials() {
|
||||||
|
return this.credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getPrincipal() {
|
||||||
|
return this.principal;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.databasir.api.config.oauth2;
|
||||||
|
|
||||||
|
import com.databasir.api.config.security.DatabasirUserDetailService;
|
||||||
|
import com.databasir.core.domain.user.data.UserDetailResponse;
|
||||||
|
import com.databasir.core.infrastructure.oauth2.OAuthAppService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.AntPathMatcher;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class DatabasirOauth2LoginFilter extends AbstractAuthenticationProcessingFilter {
|
||||||
|
|
||||||
|
public static final String OAUTH_LOGIN_URI = "/oauth2/login/*";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OAuthAppService oAuthAppService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DatabasirUserDetailService databasirUserDetailService;
|
||||||
|
|
||||||
|
public DatabasirOauth2LoginFilter(AuthenticationManager authenticationManager,
|
||||||
|
OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler) {
|
||||||
|
super(OAUTH_LOGIN_URI, authenticationManager);
|
||||||
|
this.setAuthenticationSuccessHandler(oAuth2AuthenticationSuccessHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
|
||||||
|
throws AuthenticationException, IOException, ServletException {
|
||||||
|
Map<String, String[]> params = request.getParameterMap();
|
||||||
|
String registrationId = new AntPathMatcher().extractPathWithinPattern(OAUTH_LOGIN_URI, request.getRequestURI());
|
||||||
|
UserDetailResponse userDetailResponse = oAuthAppService.oauthCallback(registrationId, params);
|
||||||
|
UserDetails details = databasirUserDetailService.loadUserByUsername(userDetailResponse.getUsername());
|
||||||
|
DatabasirOAuth2Authentication authentication = new DatabasirOAuth2Authentication(details);
|
||||||
|
authentication.setAuthenticated(true);
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("login {} success", registrationId);
|
||||||
|
}
|
||||||
|
return authentication;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.databasir.api.config.oauth2;
|
||||||
|
|
||||||
|
import com.databasir.api.config.security.DatabasirUserDetails;
|
||||||
|
import com.databasir.common.JsonData;
|
||||||
|
import com.databasir.core.domain.login.data.LoginKeyResponse;
|
||||||
|
import com.databasir.core.domain.login.data.UserLoginResponse;
|
||||||
|
import com.databasir.core.domain.login.service.LoginService;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.security.authentication.CredentialsExpiredException;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class OAuth2AuthenticationSuccessHandler implements AuthenticationSuccessHandler {
|
||||||
|
|
||||||
|
private final LoginService loginService;
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationSuccess(HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
Authentication authentication) throws IOException, ServletException {
|
||||||
|
DatabasirUserDetails details = (DatabasirUserDetails) authentication.getPrincipal();
|
||||||
|
LoginKeyResponse loginKey = loginService.generate(details.getUserPojo().getId());
|
||||||
|
UserLoginResponse data = loginService.getUserLoginData(details.getUserPojo().getId())
|
||||||
|
.orElseThrow(() -> new CredentialsExpiredException("请重新登陆"));
|
||||||
|
objectMapper.writeValue(response.getWriter(), JsonData.ok(data));
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,11 +2,12 @@ package com.databasir.api.config.security;
|
||||||
|
|
||||||
import com.databasir.common.JsonData;
|
import com.databasir.common.JsonData;
|
||||||
import com.databasir.core.domain.login.data.LoginKeyResponse;
|
import com.databasir.core.domain.login.data.LoginKeyResponse;
|
||||||
|
import com.databasir.core.domain.login.data.UserLoginResponse;
|
||||||
import com.databasir.core.domain.login.service.LoginService;
|
import com.databasir.core.domain.login.service.LoginService;
|
||||||
import com.databasir.core.domain.user.data.UserLoginResponse;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.security.authentication.CredentialsExpiredException;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
@ -16,9 +17,6 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@ -35,28 +33,10 @@ public class DatabasirAuthenticationSuccessHandler implements AuthenticationSucc
|
||||||
DatabasirUserDetails user = (DatabasirUserDetails) authentication.getPrincipal();
|
DatabasirUserDetails user = (DatabasirUserDetails) authentication.getPrincipal();
|
||||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||||
UserLoginResponse data = new UserLoginResponse();
|
|
||||||
data.setId(user.getUserPojo().getId());
|
|
||||||
data.setNickname(user.getUserPojo().getNickname());
|
|
||||||
data.setEmail(user.getUserPojo().getEmail());
|
|
||||||
data.setUsername(user.getUserPojo().getUsername());
|
|
||||||
|
|
||||||
LoginKeyResponse loginKey = loginService.generate(user.getUserPojo().getId());
|
LoginKeyResponse loginKey = loginService.generate(user.getUserPojo().getId());
|
||||||
data.setAccessToken(loginKey.getAccessToken());
|
UserLoginResponse data = loginService.getUserLoginData(user.getUserPojo().getId())
|
||||||
long expireAt = loginKey.getAccessTokenExpireAt().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
.orElseThrow(() -> new CredentialsExpiredException("请重新登陆"));
|
||||||
data.setAccessTokenExpireAt(expireAt);
|
|
||||||
data.setRefreshToken(loginKey.getRefreshToken());
|
|
||||||
|
|
||||||
List<UserLoginResponse.RoleResponse> roles = user.getRoles()
|
|
||||||
.stream()
|
|
||||||
.map(ur -> {
|
|
||||||
UserLoginResponse.RoleResponse roleResponse = new UserLoginResponse.RoleResponse();
|
|
||||||
roleResponse.setRole(ur.getRole());
|
|
||||||
roleResponse.setGroupId(ur.getGroupId());
|
|
||||||
return roleResponse;
|
|
||||||
})
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
data.setRoles(roles);
|
|
||||||
objectMapper.writeValue(response.getWriter(), JsonData.ok(data));
|
objectMapper.writeValue(response.getWriter(), JsonData.ok(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package com.databasir.api.config.security;
|
package com.databasir.api.config.security;
|
||||||
|
|
||||||
import com.databasir.dao.impl.LoginDao;
|
|
||||||
import com.databasir.dao.impl.UserDao;
|
import com.databasir.dao.impl.UserDao;
|
||||||
import com.databasir.dao.impl.UserRoleDao;
|
import com.databasir.dao.impl.UserRoleDao;
|
||||||
import com.databasir.dao.tables.pojos.UserPojo;
|
import com.databasir.dao.tables.pojos.UserPojo;
|
||||||
|
@ -22,8 +21,6 @@ public class DatabasirUserDetailService implements UserDetailsService {
|
||||||
|
|
||||||
private final UserRoleDao userRoleDao;
|
private final UserRoleDao userRoleDao;
|
||||||
|
|
||||||
private final LoginDao loginDao;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
UserPojo user = userDao.selectByEmailOrUsername(username)
|
UserPojo user = userDao.selectByEmailOrUsername(username)
|
||||||
|
|
|
@ -2,6 +2,8 @@ package com.databasir.common;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class JsonData<T> {
|
public class JsonData<T> {
|
||||||
|
|
||||||
|
@ -21,7 +23,7 @@ public class JsonData<T> {
|
||||||
private String errMessage;
|
private String errMessage;
|
||||||
|
|
||||||
public static <T> JsonData<T> ok() {
|
public static <T> JsonData<T> ok() {
|
||||||
return ok(null);
|
return ok(Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> JsonData<T> ok(T data) {
|
public static <T> JsonData<T> ok(T data) {
|
||||||
|
@ -30,6 +32,13 @@ public class JsonData<T> {
|
||||||
return jsonData;
|
return jsonData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> JsonData<T> ok(Optional<T> data) {
|
||||||
|
JsonData<T> jsonData = new JsonData<>();
|
||||||
|
jsonData.setData(data.orElse(null));
|
||||||
|
return jsonData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static <T> JsonData<T> error(String errorCode, String errMessage) {
|
public static <T> JsonData<T> error(String errorCode, String errMessage) {
|
||||||
JsonData<T> jsonData = new JsonData<>();
|
JsonData<T> jsonData = new JsonData<>();
|
||||||
jsonData.setErrCode(errorCode);
|
jsonData.setErrCode(errorCode);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package com.databasir.core.domain.user.data;
|
package com.databasir.core.domain.login.data;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@ public class UserLoginResponse {
|
||||||
|
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
private String accessToken;
|
private String accessToken;
|
||||||
|
|
||||||
private long accessTokenExpireAt;
|
private long accessTokenExpireAt;
|
|
@ -4,11 +4,14 @@ import com.databasir.core.domain.DomainErrors;
|
||||||
import com.databasir.core.domain.login.data.AccessTokenRefreshRequest;
|
import com.databasir.core.domain.login.data.AccessTokenRefreshRequest;
|
||||||
import com.databasir.core.domain.login.data.AccessTokenRefreshResponse;
|
import com.databasir.core.domain.login.data.AccessTokenRefreshResponse;
|
||||||
import com.databasir.core.domain.login.data.LoginKeyResponse;
|
import com.databasir.core.domain.login.data.LoginKeyResponse;
|
||||||
|
import com.databasir.core.domain.login.data.UserLoginResponse;
|
||||||
import com.databasir.core.infrastructure.jwt.JwtTokens;
|
import com.databasir.core.infrastructure.jwt.JwtTokens;
|
||||||
import com.databasir.dao.impl.LoginDao;
|
import com.databasir.dao.impl.LoginDao;
|
||||||
import com.databasir.dao.impl.UserDao;
|
import com.databasir.dao.impl.UserDao;
|
||||||
|
import com.databasir.dao.impl.UserRoleDao;
|
||||||
import com.databasir.dao.tables.pojos.LoginPojo;
|
import com.databasir.dao.tables.pojos.LoginPojo;
|
||||||
import com.databasir.dao.tables.pojos.UserPojo;
|
import com.databasir.dao.tables.pojos.UserPojo;
|
||||||
|
import com.databasir.dao.tables.pojos.UserRolePojo;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
@ -16,8 +19,11 @@ import org.springframework.stereotype.Service;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@ -28,6 +34,8 @@ public class LoginService {
|
||||||
|
|
||||||
private final UserDao userDao;
|
private final UserDao userDao;
|
||||||
|
|
||||||
|
private final UserRoleDao userRoleDao;
|
||||||
|
|
||||||
private final JwtTokens jwtTokens;
|
private final JwtTokens jwtTokens;
|
||||||
|
|
||||||
public AccessTokenRefreshResponse refreshAccessTokens(AccessTokenRefreshRequest request) {
|
public AccessTokenRefreshResponse refreshAccessTokens(AccessTokenRefreshRequest request) {
|
||||||
|
@ -91,4 +99,34 @@ public class LoginService {
|
||||||
.refreshTokenExpireAt(refreshTokenExpireAt)
|
.refreshTokenExpireAt(refreshTokenExpireAt)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<UserLoginResponse> getUserLoginData(Integer userId) {
|
||||||
|
return loginDao.selectByUserId(userId)
|
||||||
|
.map(login -> {
|
||||||
|
UserPojo user = userDao.selectById(login.getUserId());
|
||||||
|
UserLoginResponse data = new UserLoginResponse();
|
||||||
|
data.setId(user.getId());
|
||||||
|
data.setNickname(user.getNickname());
|
||||||
|
data.setEmail(user.getEmail());
|
||||||
|
data.setUsername(user.getUsername());
|
||||||
|
data.setAccessToken(login.getAccessToken());
|
||||||
|
data.setAvatar(user.getAvatar());
|
||||||
|
long expireAt = login.getAccessTokenExpireAt().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||||
|
data.setAccessTokenExpireAt(expireAt);
|
||||||
|
data.setRefreshToken(login.getRefreshToken());
|
||||||
|
List<UserRolePojo> rolePojoList = userRoleDao.selectByUserIds(Collections.singletonList(user.getId()));
|
||||||
|
List<UserLoginResponse.RoleResponse> roles = rolePojoList
|
||||||
|
.stream()
|
||||||
|
.map(ur -> {
|
||||||
|
UserLoginResponse.RoleResponse roleResponse = new UserLoginResponse.RoleResponse();
|
||||||
|
roleResponse.setRole(ur.getRole());
|
||||||
|
roleResponse.setGroupId(ur.getGroupId());
|
||||||
|
return roleResponse;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
data.setRoles(roles);
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
package com.databasir.core.domain.user.data;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import javax.validation.constraints.NotBlank;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class UserLoginRequest {
|
|
||||||
|
|
||||||
@NotBlank
|
|
||||||
private String username;
|
|
||||||
|
|
||||||
@NotBlank
|
|
||||||
private String password;
|
|
||||||
|
|
||||||
}
|
|
|
@ -91,6 +91,21 @@ public class UserService {
|
||||||
return userResponseConverter.detailResponse(pojo, roles, groupNameMapById);
|
return userResponseConverter.detailResponse(pojo, roles, groupNameMapById);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<UserDetailResponse> get(String email) {
|
||||||
|
return userDao.selectByEmail(email)
|
||||||
|
.map(user -> {
|
||||||
|
List<UserRolePojo> roles = userRoleDao.selectByUserIds(Collections.singletonList(user.getId()));
|
||||||
|
List<Integer> groupIds = roles.stream()
|
||||||
|
.map(UserRolePojo::getGroupId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(toList());
|
||||||
|
Map<Integer, String> groupNameMapById = groupDao.selectInIds(groupIds)
|
||||||
|
.stream()
|
||||||
|
.collect(toMap(GroupPojo::getId, GroupPojo::getName));
|
||||||
|
return userResponseConverter.detailResponse(user, roles, groupNameMapById);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public String renewPassword(Integer userId) {
|
public String renewPassword(Integer userId) {
|
||||||
UserPojo userPojo = userDao.selectById(userId);
|
UserPojo userPojo = userDao.selectById(userId);
|
||||||
|
|
|
@ -7,6 +7,8 @@ import com.databasir.dao.impl.OAuthAppDao;
|
||||||
import com.databasir.dao.tables.pojos.OauthAppPojo;
|
import com.databasir.dao.tables.pojos.OauthAppPojo;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.jooq.tools.StringUtils;
|
||||||
|
import org.springframework.security.authentication.CredentialsExpiredException;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
|
@ -47,17 +49,23 @@ public class GithubOauthHandler implements OAuthHandler {
|
||||||
String clientSecret = authApp.getClientSecret();
|
String clientSecret = authApp.getClientSecret();
|
||||||
String baseUrl = authApp.getResourceUrl();
|
String baseUrl = authApp.getResourceUrl();
|
||||||
|
|
||||||
Map<String, String> params = context.getCallbackParameters();
|
Map<String, String[]> params = context.getCallbackParameters();
|
||||||
String code = params.get("code");
|
String code = params.get("code")[0];
|
||||||
String accessToken = githubRemoteService.getToken(baseUrl, clientId, clientSecret, code)
|
String accessToken = githubRemoteService.getToken(baseUrl, clientId, clientSecret, code)
|
||||||
.get("access_token")
|
.get("access_token")
|
||||||
.asText();
|
.asText();
|
||||||
|
if (StringUtils.isBlank(accessToken)) {
|
||||||
|
throw new CredentialsExpiredException("授权失效,请重新登陆");
|
||||||
|
}
|
||||||
String email = null;
|
String email = null;
|
||||||
for (JsonNode node : githubRemoteService.getEmail(baseUrl, accessToken)) {
|
for (JsonNode node : githubRemoteService.getEmail(baseUrl, accessToken)) {
|
||||||
if (node.get("primary").asBoolean()) {
|
if (node.get("primary").asBoolean()) {
|
||||||
email = node.get("email").asText();
|
email = node.get("email").asText();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (StringUtils.isBlank(email)) {
|
||||||
|
throw new CredentialsExpiredException("授权失效,请重新登陆");
|
||||||
|
}
|
||||||
JsonNode profile = githubRemoteService.getProfile(baseUrl, accessToken);
|
JsonNode profile = githubRemoteService.getProfile(baseUrl, accessToken);
|
||||||
String nickname = profile.get("name").asText();
|
String nickname = profile.get("name").asText();
|
||||||
String avatar = profile.get("avatar_url").asText();
|
String avatar = profile.get("avatar_url").asText();
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
package com.databasir.core.infrastructure.oauth2;
|
package com.databasir.core.infrastructure.oauth2;
|
||||||
|
|
||||||
import com.databasir.core.domain.login.data.LoginKeyResponse;
|
|
||||||
import com.databasir.core.domain.login.service.LoginService;
|
|
||||||
import com.databasir.core.domain.user.data.UserCreateRequest;
|
import com.databasir.core.domain.user.data.UserCreateRequest;
|
||||||
|
import com.databasir.core.domain.user.data.UserDetailResponse;
|
||||||
import com.databasir.core.domain.user.service.UserService;
|
import com.databasir.core.domain.user.service.UserService;
|
||||||
|
import com.databasir.core.infrastructure.oauth2.converter.OAuthAppResponseConverter;
|
||||||
import com.databasir.core.infrastructure.oauth2.data.OAuthAppResponse;
|
import com.databasir.core.infrastructure.oauth2.data.OAuthAppResponse;
|
||||||
import com.databasir.dao.impl.OAuthAppDao;
|
import com.databasir.dao.impl.OAuthAppDao;
|
||||||
import com.databasir.dao.tables.pojos.OauthAppPojo;
|
import com.databasir.dao.tables.pojos.OauthAppPojo;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@ -24,16 +26,16 @@ public class OAuthAppService {
|
||||||
|
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
|
|
||||||
private final LoginService loginService;
|
private final OAuthAppResponseConverter oAuthAppResponseConverter;
|
||||||
|
|
||||||
public LoginKeyResponse oauthCallback(String registrationId, Map<String, String> params) {
|
public UserDetailResponse oauthCallback(String registrationId, Map<String, String[]> params) {
|
||||||
|
|
||||||
// match handler
|
// match handler
|
||||||
OauthAppPojo app = oAuthAppDao.selectByRegistrationId(registrationId);
|
OauthAppPojo app = oAuthAppDao.selectByRegistrationId(registrationId);
|
||||||
OAuthHandler oAuthHandler = oAuthHandlers.stream()
|
OAuthHandler oAuthHandler = oAuthHandlers.stream()
|
||||||
.filter(handler -> handler.support(app.getAppType()))
|
.filter(handler -> handler.support(app.getAppType()))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow();
|
.orElseThrow(() -> new UsernameNotFoundException("暂不支持该类型登陆"));
|
||||||
|
|
||||||
// process by handler
|
// process by handler
|
||||||
OAuthProcessContext context = OAuthProcessContext.builder()
|
OAuthProcessContext context = OAuthProcessContext.builder()
|
||||||
|
@ -42,20 +44,25 @@ public class OAuthAppService {
|
||||||
.build();
|
.build();
|
||||||
OAuthProcessResult result = oAuthHandler.process(context);
|
OAuthProcessResult result = oAuthHandler.process(context);
|
||||||
|
|
||||||
// create new user
|
// get or create new user
|
||||||
UserCreateRequest user = new UserCreateRequest();
|
return userService.get(result.getEmail())
|
||||||
user.setUsername(result.getUsername());
|
.orElseGet(() -> {
|
||||||
user.setNickname(result.getNickname());
|
UserCreateRequest user = new UserCreateRequest();
|
||||||
user.setEmail(result.getEmail());
|
user.setUsername(result.getUsername());
|
||||||
user.setAvatar(result.getAvatar());
|
user.setNickname(result.getNickname());
|
||||||
user.setPassword(UUID.randomUUID().toString().substring(0, 6));
|
user.setEmail(result.getEmail());
|
||||||
Integer userId = userService.create(user);
|
user.setAvatar(result.getAvatar());
|
||||||
|
user.setPassword(UUID.randomUUID().toString().substring(0, 6));
|
||||||
return loginService.generate(userId);
|
Integer id = userService.create(user);
|
||||||
|
return userService.get(id);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OAuthAppResponse> listAll() {
|
public List<OAuthAppResponse> listAll() {
|
||||||
return null;
|
List<OauthAppPojo> apps = oAuthAppDao.selectAll();
|
||||||
|
return apps.stream()
|
||||||
|
.map(oAuthAppResponseConverter::to)
|
||||||
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,6 @@ public class OAuthProcessContext {
|
||||||
private String registrationId;
|
private String registrationId;
|
||||||
|
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private Map<String, String> callbackParameters = new HashMap<>();
|
private Map<String, String[]> callbackParameters = new HashMap<>();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.databasir.core.infrastructure.oauth2.converter;
|
||||||
|
|
||||||
|
import com.databasir.core.infrastructure.oauth2.data.OAuthAppResponse;
|
||||||
|
import com.databasir.dao.tables.pojos.OauthAppPojo;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
|
||||||
|
@Mapper(componentModel = "spring")
|
||||||
|
public interface OAuthAppResponseConverter {
|
||||||
|
|
||||||
|
OAuthAppResponse to(OauthAppPojo pojo);
|
||||||
|
}
|
|
@ -15,18 +15,14 @@ public class OAuthAppResponse {
|
||||||
|
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
|
||||||
private String name;
|
private String appName;
|
||||||
|
|
||||||
private String icon;
|
private String appIcon;
|
||||||
|
|
||||||
|
private String appType;
|
||||||
|
|
||||||
private String registrationId;
|
private String registrationId;
|
||||||
|
|
||||||
private String clientId;
|
|
||||||
|
|
||||||
private String clientSecret;
|
|
||||||
|
|
||||||
private LocalDateTime updateAt;
|
|
||||||
|
|
||||||
private LocalDateTime createAt;
|
private LocalDateTime createAt;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,12 @@ public abstract class BaseDao<R> {
|
||||||
.fetchInto(pojoType);
|
.fetchInto(pojoType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<R> selectAll() {
|
||||||
|
return this.getDslContext()
|
||||||
|
.selectFrom(table())
|
||||||
|
.fetchInto(pojoType);
|
||||||
|
}
|
||||||
|
|
||||||
public Page<R> selectByPage(Pageable request, Condition condition) {
|
public Page<R> selectByPage(Pageable request, Condition condition) {
|
||||||
Integer count = getDslContext()
|
Integer count = getDslContext()
|
||||||
.selectCount().from(table).where(condition)
|
.selectCount().from(table).where(condition)
|
||||||
|
|
|
@ -40,6 +40,13 @@ public class LoginDao extends BaseDao<LoginPojo> {
|
||||||
.fetchOptionalInto(LoginPojo.class);
|
.fetchOptionalInto(LoginPojo.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<LoginPojo> selectByAccessToken(String accessToken) {
|
||||||
|
return getDslContext()
|
||||||
|
.selectFrom(LOGIN).where(LOGIN.ACCESS_TOKEN.eq(accessToken)
|
||||||
|
.and(LOGIN.ACCESS_TOKEN_EXPIRE_AT.ge(LocalDateTime.now())))
|
||||||
|
.fetchOptionalInto(LoginPojo.class);
|
||||||
|
}
|
||||||
|
|
||||||
public void updateAccessToken(String accessToken, LocalDateTime accessTokenExpireAt, Integer userId) {
|
public void updateAccessToken(String accessToken, LocalDateTime accessTokenExpireAt, Integer userId) {
|
||||||
getDslContext()
|
getDslContext()
|
||||||
.update(LOGIN)
|
.update(LOGIN)
|
||||||
|
|
Loading…
Reference in New Issue