Merge branch 'main' of https://github.com/dromara/MaxKey
This commit is contained in:
+42
@@ -0,0 +1,42 @@
|
||||
package org.dromara.maxkey.authn;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @description:
|
||||
* @author: orangeBabu
|
||||
* @time: 16/8/2024 AM10:54
|
||||
*/
|
||||
|
||||
|
||||
public class QrCodeCredentialDto {
|
||||
|
||||
|
||||
@NotEmpty(message = "jwtToken不能为空")
|
||||
@Schema(name = "jwtToken", description = "token")
|
||||
String jwtToken;
|
||||
|
||||
@NotEmpty(message = "返回码不能为空")
|
||||
@Schema(name = "code", description = "返回码")
|
||||
String code;
|
||||
|
||||
public @NotEmpty(message = "jwtToken不能为空") String getJwtToken() {
|
||||
return jwtToken;
|
||||
}
|
||||
|
||||
public void setJwtToken(@NotEmpty(message = "jwtToken不能为空") String jwtToken) {
|
||||
this.jwtToken = jwtToken;
|
||||
}
|
||||
|
||||
public @NotEmpty(message = "返回码不能为空") String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(@NotEmpty(message = "返回码不能为空") String code) {
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package org.dromara.maxkey.authn;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
|
||||
/**
|
||||
* @description:
|
||||
* @author: orangeBabu
|
||||
* @time: 16/8/2024 PM4:28
|
||||
*/
|
||||
public class ScanCode {
|
||||
|
||||
@NotEmpty(message = "二维码内容不能为空")
|
||||
String code;
|
||||
|
||||
@NotEmpty(message = "登录方式不能为空")
|
||||
String authType;
|
||||
|
||||
public @NotEmpty(message = "二维码内容不能为空") String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(@NotEmpty(message = "二维码内容不能为空") String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public @NotEmpty(message = "登录方式不能为空") String getAuthType() {
|
||||
return authType;
|
||||
}
|
||||
|
||||
public void setAuthType(@NotEmpty(message = "登录方式不能为空") String authType) {
|
||||
this.authType = authType;
|
||||
}
|
||||
}
|
||||
+10
-10
@@ -1,19 +1,19 @@
|
||||
/*
|
||||
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package org.dromara.maxkey.authn.session;
|
||||
|
||||
@@ -27,16 +27,16 @@ public interface SessionManager {
|
||||
public void create(String sessionId, Session session);
|
||||
|
||||
public Session remove(String sessionId);
|
||||
|
||||
|
||||
public Session get(String sessionId);
|
||||
|
||||
|
||||
public Session refresh(String sessionId ,LocalDateTime refreshTime);
|
||||
|
||||
|
||||
public Session refresh(String sessionId);
|
||||
|
||||
|
||||
public List<HistoryLogin> querySessions();
|
||||
|
||||
|
||||
public int getValiditySeconds();
|
||||
|
||||
|
||||
public void terminate(String sessionId,String userId,String username);
|
||||
}
|
||||
|
||||
+31
-22
@@ -1,25 +1,26 @@
|
||||
/*
|
||||
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package org.dromara.maxkey.authn.web;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
import org.dromara.maxkey.authn.SignPrincipal;
|
||||
import org.dromara.maxkey.authn.jwt.AuthTokenService;
|
||||
import org.dromara.maxkey.authn.session.Session;
|
||||
@@ -37,14 +38,14 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
public class AuthorizationUtils {
|
||||
private static final Logger _logger = LoggerFactory.getLogger(AuthorizationUtils.class);
|
||||
|
||||
|
||||
public static final class BEARERTYPE{
|
||||
|
||||
|
||||
public static final String CONGRESS = "congress";
|
||||
|
||||
|
||||
public static final String AUTHORIZATION = "Authorization";
|
||||
}
|
||||
|
||||
|
||||
public static void authenticateWithCookie(
|
||||
HttpServletRequest request,
|
||||
AuthTokenService authTokenService,
|
||||
@@ -55,12 +56,12 @@ public class AuthorizationUtils {
|
||||
String authorization = authCookie.getValue();
|
||||
_logger.trace("Try congress authenticate .");
|
||||
doJwtAuthenticate(BEARERTYPE.CONGRESS,authorization,authTokenService,sessionManager);
|
||||
}else {
|
||||
}else {
|
||||
_logger.debug("cookie is null , clear authentication .");
|
||||
clearAuthentication();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void authenticate(
|
||||
HttpServletRequest request,
|
||||
AuthTokenService authTokenService,
|
||||
@@ -71,9 +72,9 @@ public class AuthorizationUtils {
|
||||
_logger.trace("Try Authorization authenticate .");
|
||||
doJwtAuthenticate(BEARERTYPE.AUTHORIZATION,authorization,authTokenService,sessionManager);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static void doJwtAuthenticate(
|
||||
String bearerType,
|
||||
String authorization,
|
||||
@@ -99,17 +100,25 @@ public class AuthorizationUtils {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static Session getSession(SessionManager sessionManager, String authorization) throws ParseException {
|
||||
_logger.debug("get session by authorization {}", authorization);
|
||||
SignedJWT signedJWT = SignedJWT.parse(authorization);
|
||||
String sessionId = signedJWT.getJWTClaimsSet().getJWTID();
|
||||
_logger.debug("sessionId {}", sessionId);
|
||||
return sessionManager.get(sessionId);
|
||||
}
|
||||
|
||||
|
||||
public static Authentication getAuthentication() {
|
||||
Authentication authentication = (Authentication) getAuthentication(WebContext.getRequest());
|
||||
return authentication;
|
||||
}
|
||||
|
||||
|
||||
public static Authentication getAuthentication(HttpServletRequest request) {
|
||||
Authentication authentication = (Authentication) request.getSession().getAttribute(WebConstants.AUTHENTICATION);
|
||||
return authentication;
|
||||
}
|
||||
|
||||
|
||||
//set Authentication to http session
|
||||
public static void setAuthentication(Authentication authentication) {
|
||||
WebContext.setAttribute(WebConstants.AUTHENTICATION, authentication);
|
||||
@@ -118,24 +127,24 @@ public class AuthorizationUtils {
|
||||
public static void clearAuthentication() {
|
||||
WebContext.removeAttribute(WebConstants.AUTHENTICATION);
|
||||
}
|
||||
|
||||
|
||||
public static boolean isAuthenticated() {
|
||||
return getAuthentication() != null;
|
||||
}
|
||||
|
||||
|
||||
public static boolean isNotAuthenticated() {
|
||||
return ! isAuthenticated();
|
||||
}
|
||||
|
||||
|
||||
public static SignPrincipal getPrincipal() {
|
||||
Authentication authentication = getAuthentication();
|
||||
return getPrincipal(authentication);
|
||||
}
|
||||
|
||||
|
||||
public static SignPrincipal getPrincipal(Authentication authentication) {
|
||||
return authentication == null ? null : (SignPrincipal) authentication.getPrincipal();
|
||||
}
|
||||
|
||||
|
||||
public static UserInfo getUserInfo(Authentication authentication) {
|
||||
UserInfo userInfo = null;
|
||||
SignPrincipal principal = getPrincipal(authentication);
|
||||
@@ -144,9 +153,9 @@ public class AuthorizationUtils {
|
||||
}
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
|
||||
public static UserInfo getUserInfo() {
|
||||
return getUserInfo(getAuthentication());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+49
-45
@@ -1,19 +1,19 @@
|
||||
/*
|
||||
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package org.dromara.maxkey.authn.provider;
|
||||
|
||||
@@ -45,37 +45,41 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
||||
/**
|
||||
* login Authentication abstract class.
|
||||
*
|
||||
*
|
||||
* @author Crystal.Sea
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractAuthenticationProvider {
|
||||
private static final Logger _logger =
|
||||
private static final Logger _logger =
|
||||
LoggerFactory.getLogger(AbstractAuthenticationProvider.class);
|
||||
|
||||
public static String PROVIDER_SUFFIX = "AuthenticationProvider";
|
||||
|
||||
|
||||
public class AuthType{
|
||||
public static final String NORMAL = "normal";
|
||||
public static final String TFA = "tfa";
|
||||
public static final String MOBILE = "mobile";
|
||||
public static final String TRUSTED = "trusted";
|
||||
/**
|
||||
* 扫描认证
|
||||
*/
|
||||
public static final String SCAN_CODE = "scancode";
|
||||
}
|
||||
|
||||
|
||||
protected ApplicationConfig applicationConfig;
|
||||
|
||||
protected AbstractAuthenticationRealm authenticationRealm;
|
||||
|
||||
protected AbstractOtpAuthn tfaOtpAuthn;
|
||||
|
||||
|
||||
protected MailOtpAuthnService otpAuthnService;
|
||||
|
||||
protected SessionManager sessionManager;
|
||||
|
||||
|
||||
protected AuthTokenService authTokenService;
|
||||
|
||||
|
||||
public static ArrayList<GrantedAuthority> grantedAdministratorsAuthoritys = new ArrayList<GrantedAuthority>();
|
||||
|
||||
|
||||
static {
|
||||
grantedAdministratorsAuthoritys.add(new SimpleGrantedAuthority("ROLE_ADMINISTRATORS"));
|
||||
}
|
||||
@@ -83,7 +87,7 @@ public abstract class AbstractAuthenticationProvider {
|
||||
public abstract String getProviderName();
|
||||
|
||||
public abstract Authentication doAuthenticate(LoginCredential authentication);
|
||||
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public boolean supports(Class authentication) {
|
||||
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
|
||||
@@ -92,13 +96,13 @@ public abstract class AbstractAuthenticationProvider {
|
||||
public Authentication authenticate(LoginCredential authentication){
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public Authentication authenticate(LoginCredential authentication,boolean trusted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* createOnlineSession
|
||||
* createOnlineSession
|
||||
* @param credential
|
||||
* @param userInfo
|
||||
* @return
|
||||
@@ -112,7 +116,7 @@ public abstract class AbstractAuthenticationProvider {
|
||||
|
||||
List<GrantedAuthority> grantedAuthoritys = authenticationRealm.grantAuthority(userInfo);
|
||||
principal.setAuthenticated(true);
|
||||
|
||||
|
||||
for(GrantedAuthority administratorsAuthority : grantedAdministratorsAuthoritys) {
|
||||
if(grantedAuthoritys.contains(administratorsAuthority)) {
|
||||
principal.setRoleAdministrators(true);
|
||||
@@ -120,37 +124,37 @@ public abstract class AbstractAuthenticationProvider {
|
||||
}
|
||||
}
|
||||
_logger.debug("Granted Authority {}" , grantedAuthoritys);
|
||||
|
||||
|
||||
principal.setGrantedAuthorityApps(authenticationRealm.queryAuthorizedApps(grantedAuthoritys));
|
||||
|
||||
|
||||
UsernamePasswordAuthenticationToken authenticationToken =
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
principal,
|
||||
"PASSWORD",
|
||||
principal,
|
||||
"PASSWORD",
|
||||
grantedAuthoritys
|
||||
);
|
||||
|
||||
|
||||
authenticationToken.setDetails(
|
||||
new WebAuthenticationDetails(WebContext.getRequest()));
|
||||
|
||||
|
||||
/*
|
||||
* put Authentication to current session context
|
||||
*/
|
||||
session.setAuthentication(authenticationToken);
|
||||
|
||||
|
||||
//create session
|
||||
this.sessionManager.create(session.getId(), session);
|
||||
|
||||
|
||||
//set Authentication to http session
|
||||
AuthorizationUtils.setAuthentication(authenticationToken);
|
||||
|
||||
|
||||
return authenticationToken;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* login user by j_username and j_cname first query user by j_cname if first
|
||||
* step userinfo is null,query user from system.
|
||||
*
|
||||
*
|
||||
* @param username String
|
||||
* @param password String
|
||||
* @return
|
||||
@@ -164,7 +168,7 @@ public abstract class AbstractAuthenticationProvider {
|
||||
} else {
|
||||
_logger.debug("User Login. ");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return userInfo;
|
||||
@@ -172,7 +176,7 @@ public abstract class AbstractAuthenticationProvider {
|
||||
|
||||
/**
|
||||
* check input password empty.
|
||||
*
|
||||
*
|
||||
* @param password String
|
||||
* @return
|
||||
*/
|
||||
@@ -185,7 +189,7 @@ public abstract class AbstractAuthenticationProvider {
|
||||
|
||||
/**
|
||||
* check input username or password empty.
|
||||
*
|
||||
*
|
||||
* @param email String
|
||||
* @return
|
||||
*/
|
||||
@@ -198,7 +202,7 @@ public abstract class AbstractAuthenticationProvider {
|
||||
|
||||
/**
|
||||
* check input username empty.
|
||||
*
|
||||
*
|
||||
* @param username String
|
||||
* @return
|
||||
*/
|
||||
@@ -219,8 +223,8 @@ public abstract class AbstractAuthenticationProvider {
|
||||
loginUser.setDisplayName("not exist");
|
||||
loginUser.setLoginCount(0);
|
||||
authenticationRealm.insertLoginHistory(
|
||||
loginUser,
|
||||
ConstsLoginType.LOCAL,
|
||||
loginUser,
|
||||
ConstsLoginType.LOCAL,
|
||||
"",
|
||||
i18nMessage,
|
||||
WebConstants.LOGIN_RESULT.USER_NOT_EXIST);
|
||||
@@ -228,22 +232,22 @@ public abstract class AbstractAuthenticationProvider {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected boolean statusValid(LoginCredential loginCredential , UserInfo userInfo) {
|
||||
if(userInfo.getIsLocked()==ConstsStatus.LOCK) {
|
||||
authenticationRealm.insertLoginHistory(
|
||||
userInfo,
|
||||
loginCredential.getAuthType(),
|
||||
loginCredential.getProvider(),
|
||||
loginCredential.getCode(),
|
||||
authenticationRealm.insertLoginHistory(
|
||||
userInfo,
|
||||
loginCredential.getAuthType(),
|
||||
loginCredential.getProvider(),
|
||||
loginCredential.getCode(),
|
||||
WebConstants.LOGIN_RESULT.USER_LOCKED
|
||||
);
|
||||
}else if(userInfo.getStatus()!=ConstsStatus.ACTIVE) {
|
||||
authenticationRealm.insertLoginHistory(
|
||||
userInfo,
|
||||
loginCredential.getAuthType(),
|
||||
loginCredential.getProvider(),
|
||||
loginCredential.getCode(),
|
||||
authenticationRealm.insertLoginHistory(
|
||||
userInfo,
|
||||
loginCredential.getAuthType(),
|
||||
loginCredential.getProvider(),
|
||||
loginCredential.getCode(),
|
||||
WebConstants.LOGIN_RESULT.USER_INACTIVE
|
||||
);
|
||||
}
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package org.dromara.maxkey.authn.provider.impl;
|
||||
|
||||
import org.dromara.maxkey.authn.LoginCredential;
|
||||
import org.dromara.maxkey.authn.SignPrincipal;
|
||||
import org.dromara.maxkey.authn.provider.AbstractAuthenticationProvider;
|
||||
import org.dromara.maxkey.authn.provider.scancode.ScanCodeService;
|
||||
import org.dromara.maxkey.authn.provider.scancode.ScanCodeState;
|
||||
|
||||
import org.dromara.maxkey.authn.realm.AbstractAuthenticationRealm;
|
||||
import org.dromara.maxkey.authn.session.SessionManager;
|
||||
|
||||
import org.dromara.maxkey.constants.ConstsLoginType;
|
||||
import org.dromara.maxkey.crypto.password.PasswordReciprocal;
|
||||
import org.dromara.maxkey.entity.idm.UserInfo;
|
||||
;
|
||||
import org.dromara.maxkey.web.WebConstants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @description:
|
||||
* @author: orangeBabu
|
||||
* @time: 16/8/2024 PM4:54
|
||||
*/
|
||||
public class ScanCodeAuthenticationProvider extends AbstractAuthenticationProvider {
|
||||
|
||||
private static final Logger _logger = LoggerFactory.getLogger(ScanCodeAuthenticationProvider.class);
|
||||
|
||||
@Autowired
|
||||
ScanCodeService scanCodeService;
|
||||
|
||||
public ScanCodeAuthenticationProvider() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ScanCodeAuthenticationProvider(
|
||||
AbstractAuthenticationRealm authenticationRealm,
|
||||
SessionManager sessionManager) {
|
||||
this.authenticationRealm = authenticationRealm;
|
||||
this.sessionManager = sessionManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProviderName() {
|
||||
return "scancode" + PROVIDER_SUFFIX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication doAuthenticate(LoginCredential loginCredential) {
|
||||
UsernamePasswordAuthenticationToken authenticationToken = null;
|
||||
|
||||
String encodeTicket = PasswordReciprocal.getInstance().decoder(loginCredential.getUsername());
|
||||
|
||||
ScanCodeState scanCodeState = scanCodeService.consume(encodeTicket);
|
||||
|
||||
if (Objects.isNull(scanCodeState)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SignPrincipal signPrincipal = (SignPrincipal) sessionManager.get(scanCodeState.getSessionId()).getAuthentication().getPrincipal();
|
||||
//获取用户信息
|
||||
UserInfo userInfo = signPrincipal.getUserInfo();
|
||||
|
||||
isUserExist(loginCredential , userInfo);
|
||||
|
||||
statusValid(loginCredential , userInfo);
|
||||
|
||||
|
||||
//创建登录会话
|
||||
authenticationToken = createOnlineTicket(loginCredential,userInfo);
|
||||
// user authenticated
|
||||
_logger.debug("'{}' authenticated successfully by {}.",
|
||||
loginCredential.getPrincipal(), getProviderName());
|
||||
|
||||
authenticationRealm.insertLoginHistory(userInfo,
|
||||
ConstsLoginType.LOCAL,
|
||||
"",
|
||||
"xe00000004",
|
||||
WebConstants.LOGIN_RESULT.SUCCESS);
|
||||
|
||||
return authenticationToken;
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package org.dromara.maxkey.authn.provider.scancode;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.dromara.maxkey.authn.session.Session;
|
||||
import org.dromara.maxkey.authn.session.SessionManager;
|
||||
import org.dromara.maxkey.exception.BusinessException;
|
||||
import org.dromara.maxkey.persistence.cache.MomentaryService;
|
||||
import org.dromara.maxkey.util.IdGenerator;
|
||||
import org.dromara.maxkey.util.JsonUtils;
|
||||
import org.dromara.maxkey.util.TimeJsonUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @description:
|
||||
* @author: orangeBabu
|
||||
* @time: 15/8/2024 AM9:49
|
||||
*/
|
||||
|
||||
@Repository
|
||||
public class ScanCodeService {
|
||||
private static final Logger _logger = LoggerFactory.getLogger(ScanCodeService.class);
|
||||
|
||||
static final String SCANCODE_TICKET = "login:scancode:%s";
|
||||
|
||||
static final String SCANCODE_CONFIRM = "login:scancode:confirm:%s";
|
||||
|
||||
public static class STATE {
|
||||
public static final String SCANED = "scaned";
|
||||
public static final String CONFIRMED = "confirmed";
|
||||
public static final String CANCELED = "canceled";
|
||||
|
||||
public static final String CANCEL = "cancel";
|
||||
public static final String CONFIRM = "confirm";
|
||||
}
|
||||
|
||||
int validitySeconds = 60 * 3; //default 3 minutes.
|
||||
|
||||
int cancelValiditySeconds = 60 * 1; //default 1 minutes.
|
||||
|
||||
|
||||
@Autowired
|
||||
IdGenerator idGenerator;
|
||||
|
||||
@Autowired
|
||||
MomentaryService momentaryService;
|
||||
|
||||
private String getKey(String ticket) {
|
||||
return SCANCODE_TICKET.formatted(ticket);
|
||||
}
|
||||
|
||||
private String getConfirmKey(Long sessionId) {
|
||||
return SCANCODE_CONFIRM.formatted(sessionId);
|
||||
}
|
||||
|
||||
public String createTicket() {
|
||||
String ticket = idGenerator.generate();
|
||||
ScanCodeState scanCodeState = new ScanCodeState();
|
||||
scanCodeState.setState("unscanned");
|
||||
|
||||
// 将对象序列化为 JSON 字符串
|
||||
String jsonString = TimeJsonUtils.gsonToString(scanCodeState);
|
||||
momentaryService.put(getKey(ticket), "", jsonString);
|
||||
_logger.info("Ticket {} , Duration {}", ticket, jsonString);
|
||||
|
||||
return ticket;
|
||||
}
|
||||
|
||||
public boolean validateTicket(String ticket, Session session) {
|
||||
String key = getKey(ticket);
|
||||
Object value = momentaryService.get(key, "");
|
||||
if (Objects.isNull(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ScanCodeState scanCodeState = new ScanCodeState();
|
||||
scanCodeState.setState("scanned");
|
||||
scanCodeState.setTicket(ticket);
|
||||
scanCodeState.setSessionId(session.getId());
|
||||
momentaryService.put(key, "", TimeJsonUtils.gsonToString(scanCodeState));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public ScanCodeState consume(String ticket){
|
||||
String key = getKey(ticket);
|
||||
Object o = momentaryService.get(key, "");
|
||||
if (Objects.nonNull(o)) {
|
||||
String redisObject = o.toString();
|
||||
ScanCodeState scanCodeState = TimeJsonUtils.gsonStringToObject(redisObject, ScanCodeState.class);
|
||||
if ("scanned".equals(scanCodeState.getState())) {
|
||||
momentaryService.remove(key, "");
|
||||
return scanCodeState;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
throw new BusinessException(20004, "该二维码失效");
|
||||
}
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package org.dromara.maxkey.authn.provider.scancode;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import org.dromara.maxkey.authn.session.Session;
|
||||
|
||||
/**
|
||||
* @description:
|
||||
* @author: orangeBabu
|
||||
* @time: 16/8/2024 PM5:42
|
||||
*/
|
||||
public class ScanCodeState {
|
||||
|
||||
String sessionId;
|
||||
|
||||
String ticket;
|
||||
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||
Long confirmKey;
|
||||
|
||||
String state;
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public String getTicket() {
|
||||
return ticket;
|
||||
}
|
||||
|
||||
public void setTicket(String ticket) {
|
||||
this.ticket = ticket;
|
||||
}
|
||||
|
||||
public Long getConfirmKey() {
|
||||
return confirmKey;
|
||||
}
|
||||
|
||||
public void setConfirmKey(Long confirmKey) {
|
||||
this.confirmKey = confirmKey;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package org.dromara.maxkey.authn.provider.scancode;
|
||||
|
||||
/**
|
||||
* @description:
|
||||
* @author: orangeBabu
|
||||
* @time: 17/8/2024 PM5:08
|
||||
*/
|
||||
public class ScancodeSignInfo {
|
||||
|
||||
String username;
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
}
|
||||
+28
-14
@@ -1,19 +1,19 @@
|
||||
/*
|
||||
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package org.dromara.maxkey.autoconfigure;
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.dromara.maxkey.authn.provider.AbstractAuthenticationProvider;
|
||||
import org.dromara.maxkey.authn.provider.AuthenticationProviderFactory;
|
||||
import org.dromara.maxkey.authn.provider.impl.MobileAuthenticationProvider;
|
||||
import org.dromara.maxkey.authn.provider.impl.NormalAuthenticationProvider;
|
||||
import org.dromara.maxkey.authn.provider.impl.ScanCodeAuthenticationProvider;
|
||||
import org.dromara.maxkey.authn.provider.impl.TrustedAuthenticationProvider;
|
||||
import org.dromara.maxkey.authn.realm.AbstractAuthenticationRealm;
|
||||
import org.dromara.maxkey.authn.session.SessionManager;
|
||||
@@ -44,21 +45,23 @@ import org.springframework.jdbc.core.JdbcTemplate;
|
||||
@AutoConfiguration
|
||||
public class AuthnProviderAutoConfiguration {
|
||||
static final Logger _logger = LoggerFactory.getLogger(AuthnProviderAutoConfiguration.class);
|
||||
|
||||
|
||||
@Bean
|
||||
public AbstractAuthenticationProvider authenticationProvider(
|
||||
NormalAuthenticationProvider normalAuthenticationProvider,
|
||||
MobileAuthenticationProvider mobileAuthenticationProvider,
|
||||
TrustedAuthenticationProvider trustedAuthenticationProvider
|
||||
TrustedAuthenticationProvider trustedAuthenticationProvider,
|
||||
ScanCodeAuthenticationProvider scanCodeAuthenticationProvider
|
||||
) {
|
||||
AuthenticationProviderFactory authenticationProvider = new AuthenticationProviderFactory();
|
||||
authenticationProvider.addAuthenticationProvider(normalAuthenticationProvider);
|
||||
authenticationProvider.addAuthenticationProvider(mobileAuthenticationProvider);
|
||||
authenticationProvider.addAuthenticationProvider(trustedAuthenticationProvider);
|
||||
|
||||
authenticationProvider.addAuthenticationProvider(scanCodeAuthenticationProvider);
|
||||
|
||||
return authenticationProvider;
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public NormalAuthenticationProvider normalAuthenticationProvider(
|
||||
AbstractAuthenticationRealm authenticationRealm,
|
||||
@@ -74,7 +77,18 @@ public class AuthnProviderAutoConfiguration {
|
||||
authTokenService
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public ScanCodeAuthenticationProvider scanCodeAuthenticationProvider(
|
||||
AbstractAuthenticationRealm authenticationRealm,
|
||||
SessionManager sessionManager
|
||||
) {
|
||||
return new ScanCodeAuthenticationProvider(
|
||||
authenticationRealm,
|
||||
sessionManager
|
||||
);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MobileAuthenticationProvider mobileAuthenticationProvider(
|
||||
AbstractAuthenticationRealm authenticationRealm,
|
||||
@@ -104,22 +118,22 @@ public class AuthnProviderAutoConfiguration {
|
||||
sessionManager
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public PasswordPolicyValidator passwordPolicyValidator(JdbcTemplate jdbcTemplate,MessageSource messageSource) {
|
||||
return new PasswordPolicyValidator(jdbcTemplate,messageSource);
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public LoginRepository loginRepository(JdbcTemplate jdbcTemplate) {
|
||||
return new LoginRepository(jdbcTemplate);
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public LoginHistoryRepository loginHistoryRepository(JdbcTemplate jdbcTemplate) {
|
||||
return new LoginHistoryRepository(jdbcTemplate);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* remeberMeService .
|
||||
* @return
|
||||
@@ -135,5 +149,5 @@ public class AuthnProviderAutoConfiguration {
|
||||
return new JdbcRemeberMeManager(
|
||||
jdbcTemplate,applicationConfig,authTokenService,validity);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -3,5 +3,6 @@ description = "maxkey-common"
|
||||
dependencies {
|
||||
//local jars
|
||||
implementation fileTree(dir: '../maxkey-lib/', include: '*/*.jar')
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.dromara.maxkey.adapter;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public class LocalDateTimeAdapter extends TypeAdapter<LocalDateTime> {
|
||||
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, LocalDateTime value) throws IOException {
|
||||
out.value(value.format(formatter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDateTime read(JsonReader in) throws IOException {
|
||||
return LocalDateTime.parse(in.nextString(), formatter);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package org.dromara.maxkey.util;
|
||||
|
||||
@@ -27,58 +27,58 @@ import com.google.zxing.common.BitMatrix;
|
||||
|
||||
public class RQCodeUtils {
|
||||
|
||||
|
||||
|
||||
public static void write2File(String path,String rqCodeText,String format,int width, int height ){
|
||||
try {
|
||||
try {
|
||||
BitMatrix byteMatrix=genRQCode(rqCodeText,width,height);
|
||||
|
||||
File file = new File(path);
|
||||
|
||||
QRCode.writeToPath(byteMatrix, format, file);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
File file = new File(path);
|
||||
|
||||
QRCode.writeToPath(byteMatrix, format, file);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static BufferedImage write2BufferedImage(String rqCodeText,String format,int width, int height ){
|
||||
try {
|
||||
BitMatrix byteMatrix=genRQCode(rqCodeText,width,height);
|
||||
|
||||
return QRCode.toBufferedImage(byteMatrix);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
public static BufferedImage write2BufferedImage(String rqCodeText,String format,int width, int height){
|
||||
try {
|
||||
BitMatrix byteMatrix=genRQCode(rqCodeText,width,height);
|
||||
|
||||
return QRCode.toBufferedImage(byteMatrix);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static void write2OutputStream(OutputStream stream,String rqCodeText,String format,int width, int height ){
|
||||
try {
|
||||
try {
|
||||
BitMatrix byteMatrix=genRQCode(rqCodeText,width,height);
|
||||
|
||||
QRCode.writeToStream(byteMatrix, format, stream);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
QRCode.writeToStream(byteMatrix, format, stream);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static BitMatrix genRQCode(String rqCodeText,int width, int height ){
|
||||
|
||||
|
||||
public static BitMatrix genRQCode(String rqCodeText,int width, int height){
|
||||
if(width==0){
|
||||
width=200;
|
||||
}
|
||||
if(height==0){
|
||||
height=200;
|
||||
}
|
||||
try {
|
||||
try {
|
||||
return new MultiFormatWriter().encode(
|
||||
rqCodeText,
|
||||
BarcodeFormat.QR_CODE,
|
||||
width,
|
||||
height);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
rqCodeText,
|
||||
BarcodeFormat.QR_CODE,
|
||||
width,
|
||||
height);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.dromara.maxkey.util;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.dromara.maxkey.adapter.LocalDateTimeAdapter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @description:
|
||||
* @author: orangeBabu
|
||||
* @time: 16/8/2024 PM6:23
|
||||
*/
|
||||
public class TimeJsonUtils {
|
||||
public static <T> T gsonStringToObject(String json, Class<T> cls) {
|
||||
Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter())
|
||||
.create();
|
||||
return gson.fromJson(json, cls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gson Transform java bean object to json string .
|
||||
*
|
||||
* @param bean Object
|
||||
* @return string
|
||||
*/
|
||||
public static String gsonToString(Object bean) {
|
||||
String json = "";
|
||||
Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter())
|
||||
.create();
|
||||
json = gson.toJson(bean);
|
||||
|
||||
return json;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.dromara.maxkey.exception;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* @description:
|
||||
* @author: orangeBabu
|
||||
* @time: 16/8/2024 PM3:03
|
||||
*/
|
||||
public class BusinessException extends RuntimeException {
|
||||
/**
|
||||
* 异常编码
|
||||
*/
|
||||
private Integer code;
|
||||
|
||||
/**
|
||||
* 异常消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
|
||||
public BusinessException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public BusinessException(Integer code, String message) {
|
||||
this.message = message;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(Integer code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
+9
-9
@@ -1,19 +1,19 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package org.dromara.maxkey.persistence.redis;
|
||||
|
||||
@@ -26,7 +26,7 @@ import redis.clients.jedis.JedisPoolConfig;
|
||||
|
||||
public class RedisConnectionFactory {
|
||||
private static final Logger _logger = LoggerFactory.getLogger(RedisConnectionFactory.class);
|
||||
|
||||
|
||||
public static class DEFAULT_CONFIG {
|
||||
/**
|
||||
* Redis默认服务器IP
|
||||
@@ -95,7 +95,7 @@ public class RedisConnectionFactory {
|
||||
timeOut = DEFAULT_CONFIG.DEFAULT_TIMEOUT;
|
||||
}
|
||||
|
||||
if (this.password == null || this.password.equals("") || this.password.equalsIgnoreCase("password")) {
|
||||
if (this.password == null || this.password.equals("")) {
|
||||
this.password = null;
|
||||
}
|
||||
jedisPool = new JedisPool(poolConfig, hostName, port, timeOut, password);
|
||||
@@ -120,7 +120,7 @@ public class RedisConnectionFactory {
|
||||
Jedis jedis = jedisPool.getResource();
|
||||
_logger.trace("return jedisPool Resource .");
|
||||
return jedis;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void close(Jedis conn) {
|
||||
@@ -130,7 +130,7 @@ public class RedisConnectionFactory {
|
||||
_logger.trace("closed conn .");
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String getHostName() {
|
||||
return hostName;
|
||||
}
|
||||
@@ -170,5 +170,5 @@ public class RedisConnectionFactory {
|
||||
public JedisPoolConfig getPoolConfig() {
|
||||
return poolConfig;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
package org.dromara.maxkey.web;
|
||||
|
||||
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.UnexpectedTypeException;
|
||||
import org.dromara.maxkey.entity.Message;
|
||||
import org.dromara.maxkey.exception.BusinessException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.ObjectError;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @description:
|
||||
* @author: orangeBabu
|
||||
* @time: 16/8/2024 PM3:02
|
||||
*/
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
|
||||
/**
|
||||
* 缺少请求体异常处理器
|
||||
* @param e 缺少请求体异常 使用get方式请求 而实体使用@RequestBody修饰
|
||||
*/
|
||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||
public Message<Void> parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
log.error("请求地址'{}',请求体缺失'{}'", requestURI, e.getMessage(),e);
|
||||
return new Message<>(Message.FAIL, "缺少请求体");
|
||||
}
|
||||
|
||||
// get请求的对象参数校验异常
|
||||
@ExceptionHandler({MissingServletRequestParameterException.class})
|
||||
public Message<Void> bindExceptionHandler(MissingServletRequestParameterException e,HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
log.error("请求地址'{}',get方式请求参数'{}'必传", requestURI, e.getMessage(),e);
|
||||
return new Message<>(Message.FAIL, "请求的对象参数校验异常");
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求方式不支持
|
||||
*/
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
public Message<Void> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
log.error("请求地址 '{}',不支持'{}' 请求", requestURI, e.getMethod(),e);
|
||||
return new Message<>(HttpStatus.METHOD_NOT_ALLOWED.value(),HttpStatus.METHOD_NOT_ALLOWED.getReasonPhrase());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 参数不正确
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||
public Message<Void> methodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
String error = String.format("%s 应该是 %s 类型", e.getName(), e.getRequiredType().getSimpleName());
|
||||
log.error("请求地址'{}',{},参数类型不正确", requestURI,error,e);
|
||||
return new Message<>(Message.FAIL, "参数类型不正确");
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统异常
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
public Message<Void> handleException(Exception e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
log.error("请求地址'{}',发生系统异常.", requestURI, e);
|
||||
return new Message<>(Message.FAIL, HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
|
||||
}
|
||||
|
||||
/**
|
||||
* 捕获转换类型异常
|
||||
* @param e
|
||||
* @return
|
||||
*/
|
||||
@ExceptionHandler(UnexpectedTypeException.class)
|
||||
public Message<String> unexpectedTypeHandler(UnexpectedTypeException e)
|
||||
{
|
||||
log.error("类型转换错误:{}",e.getMessage(), e);
|
||||
return new Message<>(HttpStatus.INTERNAL_SERVER_ERROR.value(),e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 捕获转换类型异常
|
||||
* @param e
|
||||
* @return
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public Message<String> methodArgumentNotValidException(MethodArgumentNotValidException e)
|
||||
{
|
||||
BindingResult bindingResult = e.getBindingResult();
|
||||
List<ObjectError> errors = bindingResult.getAllErrors();
|
||||
log.error("参数验证异常:{}",e.getMessage(), e);
|
||||
if (!errors.isEmpty()) {
|
||||
// 只显示第一个错误信息
|
||||
return new Message<>(HttpStatus.BAD_REQUEST.value(), errors.get(0).getDefaultMessage());
|
||||
}
|
||||
return new Message<>(HttpStatus.BAD_REQUEST.value(),"MethodArgumentNotValid");
|
||||
}
|
||||
|
||||
// 运行时异常
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public Message<String> runtimeExceptionHandler(RuntimeException e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
log.error("请求地址'{}',捕获运行时异常'{}'", requestURI, e.getMessage(),e);
|
||||
return new Message<>(Message.FAIL, e.getMessage());
|
||||
}
|
||||
// 系统级别异常
|
||||
@ExceptionHandler(Throwable.class)
|
||||
public Message<String> throwableExceptionHandler(Throwable e,HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
log.error("请求地址'{}',捕获系统级别异常'{}'", requestURI,e.getMessage(),e);
|
||||
return new Message<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* IllegalArgumentException 捕获转换类型异常
|
||||
* @param e
|
||||
* @return
|
||||
*/
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public Message<String> illegalArgumentException(IllegalArgumentException e)
|
||||
{
|
||||
String message = e.getMessage();
|
||||
log.error("IllegalArgumentException:{}",e.getMessage(),e);
|
||||
if (Objects.nonNull(message)) {
|
||||
//错误信息
|
||||
return new Message<>(HttpStatus.BAD_REQUEST.value(),message);
|
||||
}
|
||||
return new Message<>(HttpStatus.BAD_REQUEST.value(),"error");
|
||||
}
|
||||
/**
|
||||
* InvalidFormatException 捕获转换类型异常
|
||||
* @param e
|
||||
* @return
|
||||
*/
|
||||
@ExceptionHandler(InvalidFormatException.class)
|
||||
public Message<String> invalidFormatException(InvalidFormatException e)
|
||||
{
|
||||
String message = e.getMessage();
|
||||
log.error("InvalidFormatException:{}",e.getMessage(),e);
|
||||
if (Objects.nonNull(message)) {
|
||||
//错误信息
|
||||
return new Message<>(HttpStatus.BAD_REQUEST.value(),message);
|
||||
}
|
||||
return new Message<>(HttpStatus.BAD_REQUEST.value(),"error");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 自定义验证异常
|
||||
*/
|
||||
@ExceptionHandler(BindException.class)
|
||||
public Message<Void> handleBindException(BindException e) {
|
||||
BindingResult bindingResult = e.getBindingResult();
|
||||
List<ObjectError> errors = bindingResult.getAllErrors();
|
||||
log.error("参数验证异常:{}",e.getMessage(), e);
|
||||
if (!errors.isEmpty()) {
|
||||
// 只显示第一个错误信息
|
||||
return new Message<>(HttpStatus.BAD_REQUEST.value(), errors.get(0).getDefaultMessage());
|
||||
}
|
||||
return new Message<>(HttpStatus.BAD_REQUEST.value(),"MethodArgumentNotValid");
|
||||
}
|
||||
|
||||
/**
|
||||
* 业务异常处理
|
||||
* 业务自定义code 与 message
|
||||
*
|
||||
*/
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public Message<String> handleBusinessException(BusinessException e) {
|
||||
log.error("业务自定义异常:{},{}",e.getCode(),e.getMessage(),e);
|
||||
return new Message<>(e.getCode(),e.getMessage());
|
||||
}
|
||||
}
|
||||
+19
-19
@@ -1,19 +1,19 @@
|
||||
/*
|
||||
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package org.dromara.maxkey.persistence.repository;
|
||||
|
||||
@@ -23,38 +23,38 @@ import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.dromara.maxkey.entity.Institutions;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
import com.alibaba.nacos.common.utils.CollectionUtils;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
|
||||
public class InstitutionsRepository {
|
||||
static final Logger _logger = LoggerFactory.getLogger(InstitutionsRepository.class);
|
||||
|
||||
private static final String SELECT_STATEMENT =
|
||||
|
||||
private static final String SELECT_STATEMENT =
|
||||
"select * from mxk_institutions where id = ? or domain = ? or consoledomain = ?" ;
|
||||
|
||||
|
||||
private static final String DEFAULT_INSTID = "1";
|
||||
|
||||
protected static final Cache<String, Institutions> institutionsStore =
|
||||
protected static final Cache<String, Institutions> institutionsStore =
|
||||
Caffeine.newBuilder()
|
||||
.expireAfterWrite(60, TimeUnit.MINUTES)
|
||||
.build();
|
||||
|
||||
|
||||
//id domain mapping
|
||||
protected static final ConcurrentHashMap<String,String> mapper = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
protected JdbcTemplate jdbcTemplate;
|
||||
|
||||
|
||||
public InstitutionsRepository(JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
|
||||
public Institutions get(String instIdOrDomain) {
|
||||
_logger.trace(" instId {}" , instIdOrDomain);
|
||||
Institutions inst = getByInstIdOrDomain(instIdOrDomain);
|
||||
@@ -64,15 +64,15 @@ public class InstitutionsRepository {
|
||||
}
|
||||
return inst;
|
||||
}
|
||||
|
||||
|
||||
private Institutions getByInstIdOrDomain(String instIdOrDomain) {
|
||||
_logger.trace(" instId {}" , instIdOrDomain);
|
||||
Institutions inst = institutionsStore.getIfPresent(mapper.get(instIdOrDomain)==null ? DEFAULT_INSTID : mapper.get(instIdOrDomain) );
|
||||
if(inst == null) {
|
||||
List<Institutions> institutions =
|
||||
List<Institutions> institutions =
|
||||
jdbcTemplate.query(SELECT_STATEMENT,new InstitutionsRowMapper(),instIdOrDomain,instIdOrDomain,instIdOrDomain);
|
||||
|
||||
if (CollectionUtils.isNotEmpty(institutions)) {
|
||||
|
||||
if (ObjectUtils.isNotEmpty(institutions)) {
|
||||
inst = institutions.get(0);
|
||||
}
|
||||
if(inst != null ) {
|
||||
@@ -81,10 +81,10 @@ public class InstitutionsRepository {
|
||||
mapper.put(inst.getId(), inst.getDomain());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return inst;
|
||||
}
|
||||
|
||||
|
||||
public class InstitutionsRowMapper implements RowMapper<Institutions> {
|
||||
@Override
|
||||
public Institutions mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
|
||||
+41
-42
@@ -1,19 +1,19 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package org.dromara.maxkey.persistence.repository;
|
||||
|
||||
@@ -24,13 +24,12 @@ import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.maxkey.constants.ConstsRoles;
|
||||
import org.dromara.maxkey.constants.ConstsStatus;
|
||||
import org.dromara.maxkey.entity.idm.Groups;
|
||||
import org.dromara.maxkey.entity.idm.UserInfo;
|
||||
import org.dromara.maxkey.util.StrUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
@@ -57,28 +56,28 @@ public class LoginRepository {
|
||||
private static final String GROUPS_SELECT_STATEMENT = "select distinct g.id,g.groupcode,g.groupname from mxk_userinfo u,mxk_groups g,mxk_group_member gm where u.id = ? and u.id=gm.memberid and gm.groupid=g.id ";
|
||||
|
||||
private static final String DEFAULT_USERINFO_SELECT_STATEMENT = "select * from mxk_userinfo where username = ? ";
|
||||
|
||||
|
||||
private static final String DEFAULT_USERINFO_SELECT_STATEMENT_USERNAME_MOBILE = "select * from mxk_userinfo where (username = ? or mobile = ?)";
|
||||
|
||||
|
||||
private static final String DEFAULT_USERINFO_SELECT_STATEMENT_USERNAME_MOBILE_EMAIL = "select * from mxk_userinfo where (username = ? or mobile = ? or email = ?) ";
|
||||
|
||||
|
||||
private static final String DEFAULT_MYAPPS_SELECT_STATEMENT = "select distinct app.id,app.appname from mxk_apps app,mxk_access gp,mxk_groups g where app.id=gp.appid and app.status = 1 and gp.groupid=g.id and g.id in(%s)";
|
||||
|
||||
|
||||
protected JdbcTemplate jdbcTemplate;
|
||||
|
||||
|
||||
/**
|
||||
* 1 (USERNAME) 2 (USERNAME | MOBILE) 3 (USERNAME | MOBILE | EMAIL)
|
||||
*/
|
||||
public static int LOGIN_ATTRIBUTE_TYPE = 2;
|
||||
|
||||
|
||||
public LoginRepository(){
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public LoginRepository(JdbcTemplate jdbcTemplate){
|
||||
this.jdbcTemplate=jdbcTemplate;
|
||||
}
|
||||
|
||||
|
||||
public UserInfo find(String username, String password) {
|
||||
List<UserInfo> listUserInfo = null ;
|
||||
if( LOGIN_ATTRIBUTE_TYPE == 1) {
|
||||
@@ -89,37 +88,37 @@ public class LoginRepository {
|
||||
listUserInfo = findByUsernameOrMobileOrEmail(username,password);
|
||||
}
|
||||
_logger.debug("load UserInfo : {}" , listUserInfo);
|
||||
return (CollectionUtils.isNotEmpty(listUserInfo))? listUserInfo.get(0) : null;
|
||||
return (ObjectUtils.isNotEmpty(listUserInfo))? listUserInfo.get(0) : null;
|
||||
}
|
||||
|
||||
|
||||
public List<UserInfo> findByUsername(String username, String password) {
|
||||
return jdbcTemplate.query(
|
||||
DEFAULT_USERINFO_SELECT_STATEMENT,
|
||||
DEFAULT_USERINFO_SELECT_STATEMENT,
|
||||
new UserInfoRowMapper(),
|
||||
username
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public List<UserInfo> findByUsernameOrMobile(String username, String password) {
|
||||
return jdbcTemplate.query(
|
||||
DEFAULT_USERINFO_SELECT_STATEMENT_USERNAME_MOBILE,
|
||||
DEFAULT_USERINFO_SELECT_STATEMENT_USERNAME_MOBILE,
|
||||
new UserInfoRowMapper(),
|
||||
username,username
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public List<UserInfo> findByUsernameOrMobileOrEmail(String username, String password) {
|
||||
return jdbcTemplate.query(
|
||||
DEFAULT_USERINFO_SELECT_STATEMENT_USERNAME_MOBILE_EMAIL,
|
||||
DEFAULT_USERINFO_SELECT_STATEMENT_USERNAME_MOBILE_EMAIL,
|
||||
new UserInfoRowMapper(),
|
||||
username,username,username
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 閿佸畾鐢ㄦ埛锛歩slock锛�1 鐢ㄦ埛瑙i攣 2 鐢ㄦ埛閿佸畾
|
||||
*
|
||||
*
|
||||
* @param userInfo
|
||||
*/
|
||||
public void updateLock(UserInfo userInfo) {
|
||||
@@ -137,7 +136,7 @@ public class LoginRepository {
|
||||
|
||||
/**
|
||||
* 閿佸畾鐢ㄦ埛锛歩slock锛�1 鐢ㄦ埛瑙i攣 2 鐢ㄦ埛閿佸畾
|
||||
*
|
||||
*
|
||||
* @param userInfo
|
||||
*/
|
||||
public void updateUnlock(UserInfo userInfo) {
|
||||
@@ -155,7 +154,7 @@ public class LoginRepository {
|
||||
|
||||
/**
|
||||
* reset BadPasswordCount And Lockout
|
||||
*
|
||||
*
|
||||
* @param userInfo
|
||||
*/
|
||||
public void updateLockout(UserInfo userInfo) {
|
||||
@@ -173,7 +172,7 @@ public class LoginRepository {
|
||||
|
||||
/**
|
||||
* if login password is error ,BadPasswordCount++ and set bad date
|
||||
*
|
||||
*
|
||||
* @param userInfo
|
||||
*/
|
||||
public void updateBadPasswordCount(UserInfo userInfo) {
|
||||
@@ -190,15 +189,15 @@ public class LoginRepository {
|
||||
_logger.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public List<GrantedAuthority> queryAuthorizedApps(List<GrantedAuthority> grantedAuthoritys) {
|
||||
String grantedAuthorityString="'ROLE_ALL_USER'";
|
||||
for(GrantedAuthority grantedAuthority : grantedAuthoritys) {
|
||||
grantedAuthorityString += ",'"+ grantedAuthority.getAuthority()+"'";
|
||||
}
|
||||
|
||||
|
||||
ArrayList<GrantedAuthority> listAuthorizedApps = (ArrayList<GrantedAuthority>) jdbcTemplate.query(
|
||||
String.format(DEFAULT_MYAPPS_SELECT_STATEMENT, grantedAuthorityString),
|
||||
String.format(DEFAULT_MYAPPS_SELECT_STATEMENT, grantedAuthorityString),
|
||||
new RowMapper<GrantedAuthority>() {
|
||||
public GrantedAuthority mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
return new SimpleGrantedAuthority(rs.getString("id"));
|
||||
@@ -208,7 +207,7 @@ public class LoginRepository {
|
||||
_logger.debug("list Authorized Apps {}" , listAuthorizedApps);
|
||||
return listAuthorizedApps;
|
||||
}
|
||||
|
||||
|
||||
public List<Groups> queryGroups(UserInfo userInfo) {
|
||||
List<Groups> listRoles = jdbcTemplate.query(GROUPS_SELECT_STATEMENT, new RowMapper<Groups>() {
|
||||
public Groups mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
@@ -222,7 +221,7 @@ public class LoginRepository {
|
||||
|
||||
/**
|
||||
* grant Authority by userinfo
|
||||
*
|
||||
*
|
||||
* @param userInfo
|
||||
* @return ArrayList<GrantedAuthority>
|
||||
*/
|
||||
@@ -237,7 +236,7 @@ public class LoginRepository {
|
||||
grantedAuthority.add(ConstsRoles.ROLE_ORDINARY_USER);
|
||||
for (Groups group : listGroups) {
|
||||
grantedAuthority.add(new SimpleGrantedAuthority(group.getId()));
|
||||
if(group.getGroupCode().startsWith("ROLE_")
|
||||
if(group.getGroupCode().startsWith("ROLE_")
|
||||
&& !grantedAuthority.contains(new SimpleGrantedAuthority(group.getGroupCode()))) {
|
||||
grantedAuthority.add(new SimpleGrantedAuthority(group.getGroupCode()));
|
||||
}
|
||||
@@ -246,19 +245,19 @@ public class LoginRepository {
|
||||
|
||||
return grantedAuthority;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void updateLastLogin(UserInfo userInfo) {
|
||||
jdbcTemplate.update(LOGIN_USERINFO_UPDATE_STATEMENT,
|
||||
new Object[] {
|
||||
userInfo.getLastLoginTime(),
|
||||
userInfo.getLastLoginIp(),
|
||||
userInfo.getLoginCount() + 1,
|
||||
userInfo.getId()
|
||||
new Object[] {
|
||||
userInfo.getLastLoginTime(),
|
||||
userInfo.getLastLoginIp(),
|
||||
userInfo.getLoginCount() + 1,
|
||||
userInfo.getId()
|
||||
},
|
||||
new int[] { Types.TIMESTAMP, Types.VARCHAR, Types.INTEGER, Types.VARCHAR });
|
||||
}
|
||||
|
||||
|
||||
public class UserInfoRowMapper implements RowMapper<UserInfo> {
|
||||
@Override
|
||||
public UserInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
@@ -372,7 +371,7 @@ public class LoginRepository {
|
||||
if (userInfo.getTheme() == null || userInfo.getTheme().equalsIgnoreCase("")) {
|
||||
userInfo.setTheme("default");
|
||||
}
|
||||
|
||||
|
||||
return userInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +127,7 @@
|
||||
},
|
||||
"defaultProject": "ng-alain",
|
||||
"cli": {
|
||||
"analytics": false,
|
||||
"packageManager": "yarn"
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -14,7 +14,7 @@
|
||||
<i nz-icon nzType="mobile" nzTheme="outline"></i>
|
||||
{{ 'mxk.login.tab-mobile' | i18n }}
|
||||
</label>
|
||||
<label nz-radio-button nzValue="qrscan" style="width: 50%; text-align: center" (click)="getQrCode()">
|
||||
<label nz-radio-button nzValue="qrscan" style="width: 50%; text-align: center" (click)="getLoginQrCode()">
|
||||
<i nz-icon nzType="qrcode" nzTheme="outline"></i>{{ 'mxk.login.tab-qrscan' | i18n }}
|
||||
</label>
|
||||
</nz-radio-group>
|
||||
@@ -83,9 +83,9 @@
|
||||
</nz-form-item>
|
||||
</div>
|
||||
<div nz-row *ngIf="loginType=='qrscan'">
|
||||
<div class="qrcode" id="div_qrcodelogin" style="background: #fff;padding: 20px;"> </div>
|
||||
<div class="qrcode" id="div_qrcodelogin" style="background: #fff;padding: 20px;"></div>
|
||||
<div id="qrexpire" *ngIf="qrexpire" style="width: 100%;text-align: center;background: #fff;padding-bottom: 20px;">
|
||||
二维码过期 <a href="javascript:" (click)="getQrCode()">刷新</a>
|
||||
二维码过期 <a href="javascript:void(0);" (click)="getLoginQrCode()">刷新</a>
|
||||
</div>
|
||||
</div>
|
||||
<nz-form-item *ngIf="loginType == 'normal' || loginType == 'mobile'">
|
||||
|
||||
@@ -29,6 +29,7 @@ import { finalize } from 'rxjs/operators';
|
||||
import { AuthnService } from '../../../service/authn.service';
|
||||
import { ImageCaptchaService } from '../../../service/image-captcha.service';
|
||||
import { SocialsProviderService } from '../../../service/socials-provider.service';
|
||||
import {QrCodeService} from "../../../service/QrCode.service";
|
||||
import { CONSTS } from '../../../shared/consts';
|
||||
|
||||
import { stringify } from 'querystring';
|
||||
@@ -60,6 +61,8 @@ export class UserLoginComponent implements OnInit, OnDestroy {
|
||||
state = '';
|
||||
count = 0;
|
||||
interval$: any;
|
||||
//二维码内容
|
||||
ticket = '';
|
||||
|
||||
constructor(
|
||||
fb: FormBuilder,
|
||||
@@ -68,6 +71,7 @@ export class UserLoginComponent implements OnInit, OnDestroy {
|
||||
private authnService: AuthnService,
|
||||
private socialsProviderService: SocialsProviderService,
|
||||
private imageCaptchaService: ImageCaptchaService,
|
||||
private qrCodeService: QrCodeService,
|
||||
@Optional()
|
||||
@Inject(ReuseTabService)
|
||||
private reuseTabService: ReuseTabService,
|
||||
@@ -296,6 +300,65 @@ export class UserLoginComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取二维码
|
||||
*/
|
||||
getLoginQrCode() {
|
||||
this.qrexpire = false;
|
||||
|
||||
this.qrCodeService.getLoginQrCode().subscribe(res => {
|
||||
if (res.code === 0 && res.data.rqCode) { // 使用返回的 rqCode
|
||||
const qrImageElement = document.getElementById('div_qrcodelogin');
|
||||
this.ticket = res.data.ticket;
|
||||
if (qrImageElement) {
|
||||
qrImageElement.innerHTML = `<img src="${res.data.rqCode}" alt="QR Code" style="width: 200px; height: 200px;">`;
|
||||
}
|
||||
|
||||
/* // 设置5分钟后 qrexpire 为 false
|
||||
setTimeout(() => {
|
||||
this.qrexpire = true;
|
||||
this.cdr.detectChanges(); // 更新视图
|
||||
}, 5 * 60 * 1000); // 5 分钟*/
|
||||
this.loginByQrCode();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 二维码轮询登录
|
||||
*/
|
||||
loginByQrCode() {
|
||||
const interval = setInterval(() => {
|
||||
this.qrCodeService.loginByQrCode({
|
||||
authType: 'scancode',
|
||||
code: this.ticket,
|
||||
}).subscribe(res => {
|
||||
if (res.code === 0) {
|
||||
this.qrexpire = true;
|
||||
// 清空路由复用信息
|
||||
this.reuseTabService.clear();
|
||||
// 设置用户Token信息
|
||||
this.authnService.auth(res.data);
|
||||
this.authnService.navigate({});
|
||||
} else if (res.code === 20004) {
|
||||
this.qrexpire = true;
|
||||
}
|
||||
|
||||
// Handle response here
|
||||
|
||||
// If you need to stop the interval after a certain condition is met,
|
||||
// you can clear the interval like this:
|
||||
if (this.qrexpire) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
|
||||
this.cdr.detectChanges(); // 更新视图
|
||||
});
|
||||
}, 5 * 1000); // 5 seconds
|
||||
}
|
||||
|
||||
|
||||
|
||||
getQrCode(): void {
|
||||
this.qrexpire = false;
|
||||
if (this.interval$) {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { _HttpClient } from '@delon/theme';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class QrCodeService {
|
||||
constructor(private http: _HttpClient) {}
|
||||
|
||||
getLoginQrCode() {
|
||||
return this.http.get('/login/genScanCode');
|
||||
}
|
||||
|
||||
loginByQrCode(authParam: any) {
|
||||
return this.http.post('/login/sign/qrcode', authParam);
|
||||
}
|
||||
}
|
||||
+110
-27
@@ -1,41 +1,54 @@
|
||||
/*
|
||||
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package org.dromara.maxkey.web.contorller;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.text.ParseException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.maxkey.authn.LoginCredential;
|
||||
import org.dromara.maxkey.authn.QrCodeCredentialDto;
|
||||
import org.dromara.maxkey.authn.ScanCode;
|
||||
import org.dromara.maxkey.authn.jwt.AuthJwt;
|
||||
import org.dromara.maxkey.authn.jwt.AuthTokenService;
|
||||
import org.dromara.maxkey.authn.provider.AbstractAuthenticationProvider;
|
||||
import org.dromara.maxkey.authn.session.Session;
|
||||
import org.dromara.maxkey.authn.session.SessionManager;
|
||||
import org.dromara.maxkey.authn.support.kerberos.KerberosService;
|
||||
import org.dromara.maxkey.authn.support.rememberme.AbstractRemeberMeManager;
|
||||
import org.dromara.maxkey.authn.support.rememberme.RemeberMe;
|
||||
import org.dromara.maxkey.authn.support.socialsignon.service.SocialSignOnProviderService;
|
||||
import org.dromara.maxkey.authn.web.AuthorizationUtils;
|
||||
import org.dromara.maxkey.configuration.ApplicationConfig;
|
||||
import org.dromara.maxkey.constants.ConstsLoginType;
|
||||
import org.dromara.maxkey.crypto.Base64Utils;
|
||||
import org.dromara.maxkey.crypto.password.PasswordReciprocal;
|
||||
import org.dromara.maxkey.entity.*;
|
||||
import org.dromara.maxkey.entity.idm.UserInfo;
|
||||
import org.dromara.maxkey.exception.BusinessException;
|
||||
import org.dromara.maxkey.password.onetimepwd.AbstractOtpAuthn;
|
||||
import org.dromara.maxkey.password.sms.SmsOtpAuthnService;
|
||||
import org.dromara.maxkey.authn.provider.scancode.ScanCodeService;
|
||||
import org.dromara.maxkey.persistence.service.SocialsAssociatesService;
|
||||
import org.dromara.maxkey.persistence.service.UserInfoService;
|
||||
import org.dromara.maxkey.util.RQCodeUtils;
|
||||
import org.dromara.maxkey.web.WebConstants;
|
||||
import org.dromara.maxkey.web.WebContext;
|
||||
import org.slf4j.Logger;
|
||||
@@ -43,6 +56,7 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -56,6 +70,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import static org.reflections.Reflections.log;
|
||||
|
||||
/**
|
||||
* @author Crystal.Sea
|
||||
*
|
||||
@@ -65,13 +81,13 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||
@RequestMapping(value = "/login")
|
||||
public class LoginEntryPoint {
|
||||
private static Logger logger = LoggerFactory.getLogger(LoginEntryPoint.class);
|
||||
|
||||
|
||||
@Autowired
|
||||
AuthTokenService authTokenService;
|
||||
|
||||
|
||||
@Autowired
|
||||
ApplicationConfig applicationConfig;
|
||||
|
||||
|
||||
@Autowired
|
||||
AbstractAuthenticationProvider authenticationProvider ;
|
||||
|
||||
@@ -80,24 +96,28 @@ public class LoginEntryPoint {
|
||||
|
||||
@Autowired
|
||||
SocialsAssociatesService socialsAssociatesService;
|
||||
|
||||
|
||||
@Autowired
|
||||
KerberosService kerberosService;
|
||||
|
||||
|
||||
@Autowired
|
||||
UserInfoService userInfoService;
|
||||
|
||||
|
||||
@Autowired
|
||||
AbstractOtpAuthn tfaOtpAuthn;
|
||||
|
||||
|
||||
@Autowired
|
||||
SmsOtpAuthnService smsAuthnService;
|
||||
|
||||
|
||||
|
||||
|
||||
@Autowired
|
||||
ScanCodeService scanCodeService;
|
||||
|
||||
@Autowired
|
||||
AbstractRemeberMeManager remeberMeManager;
|
||||
|
||||
|
||||
@Autowired
|
||||
SessionManager sessionManager;
|
||||
|
||||
/**
|
||||
* init login
|
||||
* @return
|
||||
@@ -133,11 +153,11 @@ public class LoginEntryPoint {
|
||||
model.put("otpType", tfaOtpAuthn.getOtpType());
|
||||
model.put("otpInterval", tfaOtpAuthn.getInterval());
|
||||
}
|
||||
|
||||
|
||||
if( applicationConfig.getLoginConfig().isKerberos()){
|
||||
model.put("userDomainUrlJson", kerberosService.buildKerberosProxys());
|
||||
}
|
||||
|
||||
|
||||
Institutions inst = (Institutions)WebContext.getAttribute(WebConstants.CURRENT_INST);
|
||||
model.put("inst", inst);
|
||||
if(applicationConfig.getLoginConfig().isCaptcha()) {
|
||||
@@ -146,10 +166,10 @@ public class LoginEntryPoint {
|
||||
model.put("state", authTokenService.genRandomJwt());
|
||||
//load Social Sign On Providers
|
||||
model.put("socials", socialSignOnProviderService.loadSocials(inst.getId()));
|
||||
|
||||
|
||||
return new Message<HashMap<String , Object>>(model);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@RequestMapping(value={"/sendotp/{mobile}"}, produces = {MediaType.APPLICATION_JSON_VALUE})
|
||||
public Message<AuthJwt> produceOtp(@PathVariable("mobile") String mobile) {
|
||||
@@ -158,7 +178,7 @@ public class LoginEntryPoint {
|
||||
smsAuthnService.getByInstId(WebContext.getInst().getId()).produce(userInfo);
|
||||
return new Message<AuthJwt>(Message.SUCCESS);
|
||||
}
|
||||
|
||||
|
||||
return new Message<AuthJwt>(Message.FAIL);
|
||||
}
|
||||
|
||||
@@ -202,10 +222,10 @@ public class LoginEntryPoint {
|
||||
return new Message<AuthJwt>(Message.FAIL);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* normal
|
||||
* @param loginCredential
|
||||
* @param credential
|
||||
* @return
|
||||
*/
|
||||
@Operation(summary = "登录接口", description = "登录接口",method="POST")
|
||||
@@ -216,7 +236,7 @@ public class LoginEntryPoint {
|
||||
String authType = credential.getAuthType();
|
||||
logger.debug("Login AuthN Type {}" , authType);
|
||||
if (StringUtils.isNotBlank(authType)){
|
||||
Authentication authentication = authenticationProvider.authenticate(credential);
|
||||
Authentication authentication = authenticationProvider.authenticate(credential);
|
||||
if(authentication != null) {
|
||||
AuthJwt authJwt = authTokenService.genAuthJwt(authentication);
|
||||
if(StringUtils.isNotBlank(credential.getRemeberMe())
|
||||
@@ -229,9 +249,9 @@ public class LoginEntryPoint {
|
||||
(Integer)WebContext.getAttribute(WebConstants.CURRENT_USER_PASSWORD_SET_TYPE));
|
||||
}
|
||||
authJwtMessage = new Message<>(authJwt);
|
||||
|
||||
|
||||
}else {//fail
|
||||
String errorMsg = WebContext.getAttribute(WebConstants.LOGIN_ERROR_SESSION_MESSAGE) == null ?
|
||||
String errorMsg = WebContext.getAttribute(WebConstants.LOGIN_ERROR_SESSION_MESSAGE) == null ?
|
||||
"" : WebContext.getAttribute(WebConstants.LOGIN_ERROR_SESSION_MESSAGE).toString();
|
||||
authJwtMessage.setMessage(errorMsg);
|
||||
logger.debug("login fail , message {}",errorMsg);
|
||||
@@ -242,10 +262,10 @@ public class LoginEntryPoint {
|
||||
}
|
||||
return authJwtMessage;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* for congress
|
||||
* @param loginCredential
|
||||
* @param credential
|
||||
* @return
|
||||
*/
|
||||
@PostMapping(value={"/congress"})
|
||||
@@ -259,4 +279,67 @@ public class LoginEntryPoint {
|
||||
return new Message<>(Message.FAIL);
|
||||
}
|
||||
|
||||
@Operation(summary = "生成登录扫描二维码", description = "生成登录扫描二维码", method = "GET")
|
||||
@GetMapping("/genScanCode")
|
||||
public Message<HashMap<String,String>> genScanCode() {
|
||||
log.debug("/genScanCode.");
|
||||
String ticket = scanCodeService.createTicket();
|
||||
log.debug("ticket: {}",ticket);
|
||||
String encodeTicket = PasswordReciprocal.getInstance().encode(ticket);
|
||||
BufferedImage bufferedImage = RQCodeUtils.write2BufferedImage(encodeTicket, "gif", 300, 300);
|
||||
String rqCode = Base64Utils.encodeImage(bufferedImage);
|
||||
HashMap<String,String> codeMap = new HashMap<>();
|
||||
codeMap.put("rqCode", rqCode);
|
||||
codeMap.put("ticket", encodeTicket);
|
||||
return new Message<>(Message.SUCCESS, codeMap);
|
||||
}
|
||||
|
||||
@Operation(summary = "web二维码登录", description = "web二维码登录", method = "POST")
|
||||
@PostMapping("/sign/qrcode")
|
||||
public Message<AuthJwt> signByQrcode( HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
@Validated @RequestBody ScanCode scanCode) {
|
||||
LoginCredential loginCredential = new LoginCredential();
|
||||
loginCredential.setAuthType(scanCode.getAuthType());
|
||||
loginCredential.setUsername(scanCode.getCode());
|
||||
|
||||
try {
|
||||
Authentication authentication = authenticationProvider.authenticate(loginCredential);
|
||||
if (Objects.nonNull(authentication)) {
|
||||
//success
|
||||
AuthJwt authJwt = authTokenService.genAuthJwt(authentication);
|
||||
return new Message<>(authJwt);
|
||||
} else {
|
||||
return new Message<>(Message.FAIL, "尚未扫码");
|
||||
}
|
||||
} catch (BusinessException businessException) {
|
||||
return new Message<>(businessException.getCode(), businessException.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "app扫描二维码", description = "扫描二维码登录", method = "POST")
|
||||
@PostMapping("/scanCode")
|
||||
public Message<String> scanCode(@Validated @RequestBody QrCodeCredentialDto credentialDto) throws ParseException {
|
||||
log.debug("/scanCode.");
|
||||
String jwtToken = credentialDto.getJwtToken();
|
||||
String code = credentialDto.getCode();
|
||||
try {
|
||||
//获取登录会话
|
||||
Session session = AuthorizationUtils.getSession(sessionManager, jwtToken);
|
||||
if (Objects.isNull(session)) {
|
||||
return new Message<>(Message.FAIL, "登录会话失效,请重新登录");
|
||||
}
|
||||
//查询二维码是否过期
|
||||
String ticketString = PasswordReciprocal.getInstance().decoder(code);
|
||||
boolean codeResult = scanCodeService.validateTicket(ticketString, session);
|
||||
if (!codeResult) {
|
||||
return new Message<>(Message.FAIL, "二维码已过期,请重新获取");
|
||||
}
|
||||
|
||||
} catch (ParseException e) {
|
||||
log.error("ParseException.",e);
|
||||
return new Message<>(Message.FAIL, "token格式错误");
|
||||
}
|
||||
return new Message<>(Message.SUCCESS, "成功");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user