Browse Source

后端生成二维码

orangebabu 8 months ago
parent
commit
474f18f61c

+ 3 - 2
maxkey-common/build.gradle

@@ -3,5 +3,6 @@ description = "maxkey-common"
 dependencies {
 	//local jars
 	implementation fileTree(dir: '../maxkey-lib/', include: '*/*.jar')
-	
-}
+
+
+}

+ 41 - 41
maxkey-common/src/main/java/org/dromara/maxkey/util/RQCodeUtils.java

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

+ 9 - 9
maxkey-core/src/main/java/org/dromara/maxkey/persistence/redis/RedisConnectionFactory.java

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

+ 19 - 19
maxkey-persistence/src/main/java/org/dromara/maxkey/persistence/repository/InstitutionsRepository.java

@@ -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
maxkey-persistence/src/main/java/org/dromara/maxkey/persistence/repository/LoginRepository.java

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

+ 63 - 0
maxkey-persistence/src/main/java/org/dromara/maxkey/persistence/service/ScanCodeService.java

@@ -0,0 +1,63 @@
+package org.dromara.maxkey.persistence.service;
+
+import org.dromara.maxkey.persistence.cache.MomentaryService;
+import org.dromara.maxkey.util.IdGenerator;
+import org.dromara.maxkey.util.ObjectTransformer;
+import org.dromara.mybatis.jpa.id.IdentifierGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+import java.time.Duration;
+
+/**
+ * @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(Long ticket) {
+        return SCANCODE_TICKET.formatted(ticket);
+    }
+
+    private String getConfirmKey(Long sessionId) {
+        return SCANCODE_CONFIRM.formatted(sessionId);
+    }
+
+    public Long createTicket() {
+        Long ticket = 0L;
+        ticket = Long.parseLong(idGenerator.generate());
+        momentaryService.put(getKey(ticket), "ORCode", Duration.ofSeconds(validitySeconds));
+        _logger.info("Ticket {} , Duration {}", ticket , Duration.ofSeconds(validitySeconds));
+        return ticket;
+    }
+}

+ 1 - 0
maxkey-web-frontend/maxkey-web-app/angular.json

@@ -127,6 +127,7 @@
   },
   "defaultProject": "ng-alain",
   "cli": {
+    "analytics": false,
     "packageManager": "yarn"
   }
 }

+ 2 - 2
maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/login/login.component.html

@@ -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>
@@ -85,7 +85,7 @@
   <div nz-row *ngIf="loginType=='qrscan'">
     <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:" (click)="getLoginQrCode()">刷新</a>
     </div>
   </div>
   <nz-form-item *ngIf="loginType == 'normal' || loginType == 'mobile'">

+ 10 - 0
maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/login/login.component.ts

@@ -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';
@@ -68,6 +69,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 +298,14 @@ export class UserLoginComponent implements OnInit, OnDestroy {
     });
   }
 
+  getLoginQrCode() {
+    this.qrCodeService.getLoginQrCode().subscribe(res => {
+      if (res.code === 0) {
+
+      }
+    })
+  }
+
   getQrCode(): void {
     this.qrexpire = false;
     if (this.interval$) {

+ 29 - 0
maxkey-web-frontend/maxkey-web-app/src/app/service/QrCode.service.ts

@@ -0,0 +1,29 @@
+/*
+ * 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 { SettingsService, _HttpClient, User } from '@delon/theme';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class QrCodeService {
+  constructor(private http: _HttpClient) {}
+
+  getLoginQrCode() {
+    return this.http.get('/login/genScanCode');
+  }
+}

+ 50 - 25
maxkey-webs/maxkey-web-maxkey/src/main/java/org/dromara/maxkey/web/contorller/LoginEntryPoint.java

@@ -1,22 +1,23 @@
 /*
  * 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 org.apache.commons.lang3.StringUtils;
@@ -28,14 +29,19 @@ 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.password.onetimepwd.AbstractOtpAuthn;
 import org.dromara.maxkey.password.sms.SmsOtpAuthnService;
+import org.dromara.maxkey.persistence.service.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;
@@ -56,6 +62,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 +73,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 +88,25 @@ public class LoginEntryPoint {
 
 	@Autowired
 	SocialsAssociatesService socialsAssociatesService;
-	
+
 	@Autowired
 	KerberosService kerberosService;
-	
+
 	@Autowired
 	UserInfoService userInfoService;
-	
+
 	@Autowired
     AbstractOtpAuthn tfaOtpAuthn;
-	
+
 	@Autowired
     SmsOtpAuthnService smsAuthnService;
-	
-	
-	
+
+	@Autowired
+	ScanCodeService scanCodeService;
+
 	@Autowired
 	AbstractRemeberMeManager remeberMeManager;
-	
+
 	/**
 	 * init login
 	 * @return
@@ -133,11 +142,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 +155,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 +167,7 @@ public class LoginEntryPoint {
         	smsAuthnService.getByInstId(WebContext.getInst().getId()).produce(userInfo);
         	return new Message<AuthJwt>(Message.SUCCESS);
         }
-        
+
         return new Message<AuthJwt>(Message.FAIL);
     }
 
@@ -202,7 +211,7 @@ public class LoginEntryPoint {
 		return new Message<AuthJwt>(Message.FAIL);
 	}
 
- 	
+
  	/**
  	 * normal
  	 * @param loginCredential
@@ -216,7 +225,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 +238,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,7 +251,7 @@ public class LoginEntryPoint {
  		}
  		return authJwtMessage;
  	}
- 	
+
  	/**
  	 * for congress
  	 * @param loginCredential
@@ -259,4 +268,20 @@ public class LoginEntryPoint {
  		return new Message<>(Message.FAIL);
  	}
 
+	 @Operation(summary = "生成登录扫描二维码", description = "生成登录扫描二维码", method = "GET")
+	 @GetMapping("/genScanCode")
+	 public Message<HashMap<String,String>> genScanCode() {
+		 log.debug("/genScanCode.");
+		 UserInfo userInfo = AuthorizationUtils.getUserInfo();
+		 Long ticket = scanCodeService.createTicket();
+		 String ticketString = userInfo == null ? ticket.toString() : ticket+","+userInfo.getId();
+		 log.debug("ticket string {}",ticketString);
+		 String encodeTicket = PasswordReciprocal.getInstance().encode(ticketString);
+		 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);
+	 }
 }