mirror of
https://github.com/vran-dev/databasir.git
synced 2025-09-19 18:19:26 +08:00
feat: support oauth2 login
This commit is contained in:
@@ -26,6 +26,7 @@ dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-quartz'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
|
||||
|
||||
implementation 'org.flywaydb:flyway-core'
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package com.databasir.api;
|
||||
|
||||
import com.databasir.api.config.security.DatabasirUserDetails;
|
||||
import com.databasir.common.DatabasirException;
|
||||
import com.databasir.common.JsonData;
|
||||
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.login.data.AccessTokenRefreshRequest;
|
||||
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 lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -27,8 +28,6 @@ import java.util.Objects;
|
||||
@Slf4j
|
||||
public class LoginController {
|
||||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
|
||||
private final LoginService loginService;
|
||||
|
||||
@GetMapping(Routes.Login.LOGOUT)
|
||||
@@ -39,8 +38,8 @@ public class LoginController {
|
||||
}
|
||||
|
||||
@PostMapping(Routes.Login.REFRESH_ACCESS_TOKEN)
|
||||
public JsonData<AccessTokenRefreshResponse> refreshAccessTokens(@RequestBody @Valid
|
||||
AccessTokenRefreshRequest request) {
|
||||
public JsonData<AccessTokenRefreshResponse> refreshAccessTokens(@RequestBody
|
||||
@Valid AccessTokenRefreshRequest request) {
|
||||
try {
|
||||
return JsonData.ok(loginService.refreshAccessTokens(request));
|
||||
} 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));
|
||||
}
|
||||
|
||||
}
|
||||
|
36
api/src/main/java/com/databasir/api/OAuth2AppController.java
Normal file
36
api/src/main/java/com/databasir/api/OAuth2AppController.java
Normal file
@@ -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 REFRESH_ACCESS_TOKEN = "/access_tokens";
|
||||
|
||||
String LOGIN_INFO = "/login_info";
|
||||
|
||||
}
|
||||
|
||||
interface OperationLog {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package com.databasir.api.config;
|
||||
|
||||
import com.databasir.api.Routes;
|
||||
import com.databasir.api.config.oauth2.DatabasirOauth2LoginFilter;
|
||||
import com.databasir.api.config.security.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.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.user.data.UserLoginResponse;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.CredentialsExpiredException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -16,9 +17,6 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@@ -35,28 +33,10 @@ public class DatabasirAuthenticationSuccessHandler implements AuthenticationSucc
|
||||
DatabasirUserDetails user = (DatabasirUserDetails) authentication.getPrincipal();
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
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());
|
||||
data.setAccessToken(loginKey.getAccessToken());
|
||||
long expireAt = loginKey.getAccessTokenExpireAt().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
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);
|
||||
UserLoginResponse data = loginService.getUserLoginData(user.getUserPojo().getId())
|
||||
.orElseThrow(() -> new CredentialsExpiredException("请重新登陆"));
|
||||
objectMapper.writeValue(response.getWriter(), JsonData.ok(data));
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package com.databasir.api.config.security;
|
||||
|
||||
import com.databasir.dao.impl.LoginDao;
|
||||
import com.databasir.dao.impl.UserDao;
|
||||
import com.databasir.dao.impl.UserRoleDao;
|
||||
import com.databasir.dao.tables.pojos.UserPojo;
|
||||
@@ -22,8 +21,6 @@ public class DatabasirUserDetailService implements UserDetailsService {
|
||||
|
||||
private final UserRoleDao userRoleDao;
|
||||
|
||||
private final LoginDao loginDao;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
UserPojo user = userDao.selectByEmailOrUsername(username)
|
||||
|
Reference in New Issue
Block a user