Procházet zdrojové kódy

二次认证的配置

shimingxy před 2 dny
rodič
revize
3f5d4e4bf6
25 změnil soubory, kde provedl 391 přidání a 70 odebrání
  1. 2 0
      maxkey-core/src/main/java/org/dromara/maxkey/entity/idm/UserInfo.java
  2. 7 1
      maxkey-web-frontend/maxkey-web-app/src/app/routes/config/config.module.ts
  3. 55 0
      maxkey-web-frontend/maxkey-web-app/src/app/routes/config/mfa/mfa.component.html
  4. 0 0
      maxkey-web-frontend/maxkey-web-app/src/app/routes/config/mfa/mfa.component.less
  5. 25 0
      maxkey-web-frontend/maxkey-web-app/src/app/routes/config/mfa/mfa.component.spec.ts
  6. 63 0
      maxkey-web-frontend/maxkey-web-app/src/app/routes/config/mfa/mfa.component.ts
  7. 4 0
      maxkey-web-frontend/maxkey-web-app/src/app/service/users.service.ts
  8. 8 0
      maxkey-web-frontend/maxkey-web-app/src/assets/app-data.json
  9. 6 11
      maxkey-web-frontend/maxkey-web-app/src/assets/i18n/en-US.json
  10. 6 11
      maxkey-web-frontend/maxkey-web-app/src/assets/i18n/zh-CN.json
  11. 6 11
      maxkey-web-frontend/maxkey-web-app/src/assets/i18n/zh-TW.json
  12. 2 2
      maxkey-web-frontend/maxkey-web-app/src/environments/environment.ts
  13. 2 1
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/idm/idm.module.ts
  14. 39 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/idm/users/mfa/mfa.component.html
  15. 0 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/idm/users/mfa/mfa.component.less
  16. 25 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/idm/users/mfa/mfa.component.spec.ts
  17. 65 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/idm/users/mfa/mfa.component.ts
  18. 1 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/idm/users/users.component.html
  19. 22 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/idm/users/users.component.ts
  20. 4 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/service/users.service.ts
  21. 5 11
      maxkey-web-frontend/maxkey-web-mgt-app/src/assets/i18n/en-US.json
  22. 5 11
      maxkey-web-frontend/maxkey-web-mgt-app/src/assets/i18n/zh-CN.json
  23. 5 11
      maxkey-web-frontend/maxkey-web-mgt-app/src/assets/i18n/zh-TW.json
  24. 17 0
      maxkey-webs/maxkey-web-maxkey/src/main/java/org/dromara/maxkey/web/contorller/ProfileController.java
  25. 17 0
      maxkey-webs/maxkey-web-mgt/src/main/java/org/dromara/maxkey/web/idm/contorller/UserInfoController.java

+ 2 - 0
maxkey-core/src/main/java/org/dromara/maxkey/entity/idm/UserInfo.java

@@ -17,6 +17,7 @@
 
 package org.dromara.maxkey.entity.idm;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 
 import java.io.Serializable;
@@ -130,6 +131,7 @@ public class UserInfo extends JpaEntity  implements Serializable {
 
     // for security
     @Column
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
     protected int authnType;
     @Column
     protected String email;

+ 7 - 1
maxkey-web-frontend/maxkey-web-app/src/app/routes/config/config.module.ts

@@ -28,6 +28,7 @@ import { NzPaginationModule } from 'ng-zorro-antd/pagination';
 import { NzStepsModule } from 'ng-zorro-antd/steps';
 
 import { AccoutsComponent } from './accouts/accouts.component';
+import { MfaComponent } from './mfa/mfa.component';
 import { PasswordComponent } from './password/password.component';
 import { ProfileComponent } from './profile/profile.component';
 import { SocialsAssociateComponent } from './socials-associate/socials-associate.component';
@@ -50,6 +51,10 @@ const routes: Routes = [
   {
     path: 'timebased',
     component: TimebasedComponent
+  },
+  {
+    path: 'mfa',
+    component: MfaComponent
   }
 ];
 
@@ -63,7 +68,8 @@ const COMPONENTS = [ProfileComponent];
     SocialsAssociateComponent,
     PasswordComponent,
     ProfileComponent,
-    AccoutsComponent
+    AccoutsComponent,
+    MfaComponent
   ],
   imports: [SharedModule, CommonModule, RouterModule.forChild(routes)],
   exports: [RouterModule]

+ 55 - 0
maxkey-web-frontend/maxkey-web-app/src/app/routes/config/mfa/mfa.component.html

@@ -0,0 +1,55 @@
+<nz-card>
+  <form nz-form (ngSubmit)="onSubmit()">
+    <div nz-row style="width: 100%">
+      <div nz-col nzMd="16">
+        <nz-form-item style="display: none">
+          <nz-form-label [nzMd]="6" nzFor="id">id</nz-form-label>
+          <nz-form-control [nzMd]="18" nzErrorTip="The input is not valid id!">
+            <input [(ngModel)]="form.model.id" [ngModelOptions]="{ standalone: true }" nz-input name="id" id="id" value="id" />
+          </nz-form-control>
+        </nz-form-item>
+
+        <nz-form-item>
+          <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="displayName">{{ 'mxk.password.displayName' | i18n }}</nz-form-label>
+          <nz-form-control [nzSm]="14" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not displayName!">
+            <input nz-input disabled="true" [(ngModel)]="form.model.displayName" [ngModelOptions]="{ standalone: true }" value="0" />
+          </nz-form-control>
+        </nz-form-item>
+        <nz-form-item>
+          <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="username">{{ 'mxk.password.username' | i18n }}</nz-form-label>
+          <nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="The input is not valid username!">
+            <input nz-input disabled="true" [(ngModel)]="form.model.username" [ngModelOptions]="{ standalone: true }" />
+          </nz-form-control>
+        </nz-form-item>
+        <nz-form-item>
+          <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="mobile">{{ 'mxk.users.mobile' | i18n }}</nz-form-label>
+          <nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="The input is not valid mobile!">
+            <input nz-input disabled="true" [(ngModel)]="form.model.mobile" [ngModelOptions]="{ standalone: true }" />
+          </nz-form-control>
+        </nz-form-item>
+        <nz-form-item>
+          <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="email">{{ 'mxk.users.email' | i18n }}</nz-form-label>
+          <nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="The input is not valid email!">
+            <input nz-input disabled="true" [(ngModel)]="form.model.email" [ngModelOptions]="{ standalone: true }" />
+          </nz-form-control>
+        </nz-form-item>
+        <nz-form-item>
+          <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="authnType">{{ 'mxk.users.authnType' | i18n }}</nz-form-label>
+          <nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="The input is not valid authnType!">
+            <nz-radio-group [(ngModel)]="form.model.authnType" [ngModelOptions]="{ standalone: true }" nzButtonStyle="solid">
+              <label nz-radio-button nzValue="0">{{ 'mxk.users.authnType.0' | i18n }}</label>
+              <label nz-radio-button nzValue="1">{{ 'mxk.users.authnType.1' | i18n }}</label>
+              <label nz-radio-button nzValue="2">{{ 'mxk.users.authnType.2' | i18n }}</label>
+              <label nz-radio-button nzValue="3">{{ 'mxk.users.authnType.3' | i18n }}</label>
+            </nz-radio-group>
+          </nz-form-control>
+        </nz-form-item>
+        <nz-form-item style="width: 100%">
+          <nz-form-control [nzOffset]="10" [nzSpan]="12">
+            <button nz-button nzType="primary" type="submit" [nzLoading]="form.submitting">{{ 'mxk.text.save' | i18n }}</button>
+          </nz-form-control>
+        </nz-form-item>
+      </div>
+    </div>
+  </form>
+</nz-card>

+ 0 - 0
maxkey-web-frontend/maxkey-web-app/src/app/routes/config/mfa/mfa.component.less


+ 25 - 0
maxkey-web-frontend/maxkey-web-app/src/app/routes/config/mfa/mfa.component.spec.ts

@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MfaComponent } from './mfa.component';
+
+describe('MfaComponent', () => {
+  let component: MfaComponent;
+  let fixture: ComponentFixture<MfaComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ MfaComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(MfaComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 63 - 0
maxkey-web-frontend/maxkey-web-app/src/app/routes/config/mfa/mfa.component.ts

@@ -0,0 +1,63 @@
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, Inject } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { Router, ActivatedRoute } from '@angular/router';
+import { I18NService } from '@core';
+import { SettingsService, User, ALAIN_I18N_TOKEN } from '@delon/theme';
+import { NzMessageService } from 'ng-zorro-antd/message';
+
+import { Users } from '../../../entity/Users';
+import { UsersService } from '../../../service/users.service';
+
+@Component({
+  selector: 'app-mfa',
+  templateUrl: './mfa.component.html',
+  styleUrls: ['./mfa.component.less']
+})
+export class MfaComponent implements OnInit {
+  form: {
+    submitting: boolean;
+    model: Users;
+  } = {
+    submitting: false,
+    model: new Users()
+  };
+  loading = false;
+  constructor(
+    private router: Router,
+    private fb: FormBuilder,
+    private settingsService: SettingsService,
+    private usersService: UsersService,
+
+    private msg: NzMessageService,
+    @Inject(ALAIN_I18N_TOKEN) private i18n: I18NService,
+    private cdr: ChangeDetectorRef
+  ) {}
+
+  ngOnInit(): void {
+    let user: any = this.settingsService.user;
+    this.form.model.id = user.userId;
+    this.form.model.displayName = user.displayName;
+    this.form.model.username = user.username;
+    this.form.model.mobile = user.mobile;
+    this.form.model.email = user.email;
+    this.form.model.authnType = '0';
+    this.usersService.getProfile().subscribe(res => {
+      this.form.model.init(res.data);
+      this.cdr.detectChanges();
+    });
+  }
+
+  onSubmit(): void {
+    this.form.submitting = true;
+    this.form.model.trans();
+    this.usersService.updateAuthnType(this.form.model).subscribe(res => {
+      if (res.code == 0) {
+        this.msg.success(this.i18n.fanyi('mxk.alert.operate.success'));
+      } else {
+        this.msg.error(this.i18n.fanyi('mxk.alert.operate.error'));
+      }
+      this.form.submitting = false;
+      this.cdr.detectChanges();
+    });
+  }
+}

+ 4 - 0
maxkey-web-frontend/maxkey-web-app/src/app/service/users.service.ts

@@ -44,4 +44,8 @@ export class UsersService extends BaseService<Users> {
   updateProfile(body: any): Observable<Message<Users>> {
     return this.http.put<Message<Users>>('/users/profile/update', body);
   }
+
+  updateAuthnType(body: any): Observable<Message<Users>> {
+    return this.http.put<Message<Users>>('/users/profile/updateAuthnType', body);
+  }
 }

+ 8 - 0
maxkey-web-frontend/maxkey-web-app/src/assets/app-data.json

@@ -44,6 +44,14 @@
               "acl": "ROLE_USER",
               "children": []
             },
+             {
+              "text": "二次认证",
+              "i18n": "mxk.menu.config.mfa",
+              "link": "/config/mfa",
+              "icon": "anticon-appstore",
+              "acl": "ROLE_USER",
+              "children": []
+            },
             {
               "text": "密码修改",
               "i18n": "mxk.menu.config.password",

+ 6 - 11
maxkey-web-frontend/maxkey-web-app/src/assets/i18n/en-US.json

@@ -44,6 +44,7 @@
 				"": "Settings",
 				"setting": "Setting",
 				"profile": "Profile",
+				"mfa": "MFA",
 				"password": "Password",
 				"socialsassociate": "Socials",
 				"timebased": "OTP Token"
@@ -162,17 +163,11 @@
 			"userstate.withdrawn": "Withdrawn",
 			"userstate.inactive": "Inactive",
 			"userstate.retiree": "Retiree",
-			"authnType": "AuthenticationType",
-			"authnType.authnType.1": "General login",
-			"authnType.authnType.2": "Mobile Token",
-			"authnType.authnType.3": "SMS Verification",
-			"authnType.authnType.4": "EMAIL Verification",
-			"authnType.authnType.5": "TIME BASED Token",
-			"authnType.authnType.6": "Counter Token",
-			"authnType.authnType.7": "HOTP Token",
-			"authnType.authnType.8": "RSA Token",
-			"authnType.authnType.9": "Digital Certificate",
-			"authnType.authnType.10": "USB Key"
+			"authnType": "MFA Type",
+			"authnType.0": "None",
+			"authnType.1": "TOTP",
+			"authnType.2": "Mail OTP",
+			"authnType.3": "SMS OTP"
 		},
 		"organizations": {
 			"tab.basic": "Basic",

+ 6 - 11
maxkey-web-frontend/maxkey-web-app/src/assets/i18n/zh-CN.json

@@ -44,6 +44,7 @@
 				"": "配置",
 				"setting": "基本设置",
 				"profile": "我的资料",
+				"mfa": "二次认证",
 				"socialsassociate": "社交关联",
 				"password": "密码修改",
 				"timebased": "时间令牌"
@@ -166,17 +167,11 @@
 			"userstate.withdrawn": "离职",
 			"userstate.inactive": "停薪留职",
 			"userstate.retiree": "退休",
-			"authnType": "登录方式",
-			"authnType.authnType.1": "普通登录",
-			"authnType.authnType.2": "手机令牌",
-			"authnType.authnType.3": "短信验证",
-			"authnType.authnType.4": "邮件验证",
-			"authnType.authnType.5": "时间令牌",
-			"authnType.authnType.6": "计数器令牌",
-			"authnType.authnType.7": "HOTP令牌",
-			"authnType.authnType.8": "RSA令牌",
-			"authnType.authnType.9": "数字证书",
-			"authnType.authnType.10": "USB Key"
+			"authnType": "二次认证",
+			"authnType.0": "无",
+			"authnType.1": "TOTP令牌",
+			"authnType.2": "邮件验证码",
+			"authnType.3": "短信验证码"
 		},
 		"organizations": {
 			"tab.basic": "基本信息",

+ 6 - 11
maxkey-web-frontend/maxkey-web-app/src/assets/i18n/zh-TW.json

@@ -44,6 +44,7 @@
 				"": "配置",
 				"setting": "基本設置",
 				"profile": "我的資料",
+				"mfa": "二次认证",
 				"socialsassociate": "社交關聯",
 				"password": "密碼修改",
 				"timebased": "時間令牌"
@@ -166,17 +167,11 @@
 			"userstate.withdrawn": "離職",
 			"userstate.inactive": "停薪留職",
 			"userstate.retiree": "退休",
-			"authnType": "登錄方式",
-			"authnType.authnType.1": "普通登錄",
-			"authnType.authnType.2": "手機令牌",
-			"authnType.authnType.3": "短信驗證",
-			"authnType.authnType.4": "郵件驗證",
-			"authnType.authnType.5": "時間令牌",
-			"authnType.authnType.6": "計數器令牌",
-			"authnType.authnType.7": "HOTP令牌",
-			"authnType.authnType.8": "RSA令牌",
-			"authnType.authnType.9": "數字證書",
-			"authnType.authnType.10": "USB Key"
+			"authnType": "二次认证",
+			"authnType.0": "无",
+			"authnType.1": "TOTP令牌",
+			"authnType.2": "邮件验证码",
+			"authnType.3": "短信验证码"
 		},
 		"organizations": {
 			"tab.basic": "基本信息",

+ 2 - 2
maxkey-web-frontend/maxkey-web-app/src/environments/environment.ts

@@ -27,8 +27,8 @@ export const environment = {
   production: false,
   useHash: true,
   api: {
-    baseUrl: 'http://sso.maxkey.top/sign/',
-    // baseUrl: '/sign/',
+    //baseUrl: 'http://sso.maxkey.top/sign/',
+    baseUrl: 'http://localhost:9527/sign/',
     refreshTokenEnabled: true,
     refreshTokenType: 're-request'
   },

+ 2 - 1
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/idm/idm.module.ts

@@ -29,6 +29,7 @@ import { GroupsComponent } from './groups/groups.component';
 import { SelectGroupsComponent } from './groups/select-groups/select-groups.component';
 import { OrganizationEditerComponent } from './organizations/organization-editer/organization-editer.component';
 import { OrganizationsComponent } from './organizations/organizations.component';
+import { MfaComponent } from './users/mfa/mfa.component';
 import { PasswordComponent } from './users/password/password.component';
 import { SelectUserComponent } from './users/select-user/select-user.component';
 import { UserEditerComponent } from './users/user-editer/user-editer.component';
@@ -57,7 +58,7 @@ const COMPONENTS = [
 ];
 
 @NgModule({
-  declarations: [...COMPONENTS],
+  declarations: [...COMPONENTS, MfaComponent],
   imports: [FormsModule, NzIconModule, SharedModule, CommonModule, RouterModule.forChild(routes)],
   exports: [RouterModule]
 })

+ 39 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/idm/users/mfa/mfa.component.html

@@ -0,0 +1,39 @@
+<div *nzModalTitle> {{ 'mxk.users.authnType' | i18n }} </div>
+<div>
+  <form nz-form (ngSubmit)="onSubmit($event)" se-container="1">
+    <nz-form-item style="width: 100%" class="d-none">
+      <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="id">{{ 'mxk.users.id' | i18n }} </nz-form-label>
+      <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid id!">
+        <input [(ngModel)]="form.model.id" [ngModelOptions]="{ standalone: true }" nz-input name="id" id="id" />
+      </nz-form-control>
+    </nz-form-item>
+    <nz-form-item style="width: 100%">
+      <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="username">{{ 'mxk.users.username' | i18n }} </nz-form-label>
+      <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid username!">
+        <input [(ngModel)]="form.model.username" disabled [ngModelOptions]="{ standalone: true }" nz-input />
+      </nz-form-control>
+    </nz-form-item>
+    <nz-form-item style="width: 100%">
+      <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="displayName">{{ 'mxk.users.displayName' | i18n }} </nz-form-label>
+      <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid displayName!">
+        <input [(ngModel)]="form.model.displayName" disabled [ngModelOptions]="{ standalone: true }" nz-input />
+      </nz-form-control>
+    </nz-form-item>
+    <nz-form-item style="width: 100%">
+      <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="authnType">{{ 'mxk.users.authnType' | i18n }}</nz-form-label>
+      <nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="The input is not valid authnType!">
+        <nz-select [(ngModel)]="form.model.authnType" [ngModelOptions]="{ standalone: true }">
+          <nz-option nzValue="0" nzLabel="{{ 'mxk.users.authnType.0' | i18n }}"></nz-option>
+          <nz-option nzValue="1" nzLabel="{{ 'mxk.users.authnType.1' | i18n }}"></nz-option>
+          <nz-option nzValue="2" nzLabel="{{ 'mxk.users.authnType.2' | i18n }}"></nz-option>
+          <nz-option nzValue="3" nzLabel="{{ 'mxk.users.authnType.3' | i18n }}"></nz-option>
+        </nz-select>
+      </nz-form-control>
+    </nz-form-item>
+  </form>
+</div>
+
+<div *nzModalFooter>
+  <button nz-button nzType="default" (click)="onClose($event)">{{ 'mxk.text.close' | i18n }}</button>
+  <button nz-button nzType="primary" (click)="onSubmit($event)">{{ 'mxk.text.submit' | i18n }}</button>
+</div>

+ 0 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/idm/users/mfa/mfa.component.less


+ 25 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/idm/users/mfa/mfa.component.spec.ts

@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MfaComponent } from './mfa.component';
+
+describe('MfaComponent', () => {
+  let component: MfaComponent;
+  let fixture: ComponentFixture<MfaComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ MfaComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(MfaComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 65 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/idm/users/mfa/mfa.component.ts

@@ -0,0 +1,65 @@
+import { Component, ChangeDetectorRef, OnInit, Input, Inject } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { Router } from '@angular/router';
+import { I18NService } from '@core';
+import { ALAIN_I18N_TOKEN, SettingsService } from '@delon/theme';
+import { NzMessageService } from 'ng-zorro-antd/message';
+import { NzModalRef, NzModalService } from 'ng-zorro-antd/modal';
+
+import { Users } from '../../../../entity/Users';
+import { UsersService } from '../../../../service/users.service';
+
+@Component({
+  selector: 'app-mfa',
+  templateUrl: './mfa.component.html',
+  styleUrls: ['./mfa.component.less']
+})
+export class MfaComponent implements OnInit {
+  @Input() id?: String;
+  @Input() username?: String;
+  @Input() displayName?: String;
+
+  form: {
+    submitting: boolean;
+    model: Users;
+  } = {
+    submitting: false,
+    model: new Users()
+  };
+
+  constructor(
+    private usersService: UsersService,
+    private msg: NzMessageService,
+    @Inject(ALAIN_I18N_TOKEN) private i18n: I18NService,
+    private router: Router,
+    private modalRef: NzModalRef,
+    private cdr: ChangeDetectorRef
+  ) {}
+
+  ngOnInit(): void {
+    if (this.id) {
+      this.usersService.get(this.id).subscribe(res => {
+        this.form.model = res.data;
+      });
+    }
+  }
+
+  onClose(e: MouseEvent): void {
+    e.preventDefault();
+    this.modalRef.destroy({ refresh: false });
+  }
+  onSubmit(e: MouseEvent): void {
+    e.preventDefault();
+    this.form.submitting = true;
+
+    this.usersService.updateAuthnType(this.form.model).subscribe(res => {
+      if (res.code == 0) {
+        this.msg.success(this.i18n.fanyi('mxk.alert.operate.success'));
+        this.modalRef.destroy({ refresh: true });
+        this.cdr.detectChanges();
+      } else {
+        this.msg.error(res.message);
+      }
+    });
+  }
+}

+ 1 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/idm/users/users.component.html

@@ -168,6 +168,7 @@
                     <li nz-menu-item *ngIf="data.status == 1" (click)="changePasswordById($event, data.id)">{{
                       'mxk.text.changepassword' | i18n
                     }}</li>
+                    <li nz-menu-item *ngIf="data.status == 1" (click)="changeMfaById(data.id)">{{ 'mxk.users.authnType' | i18n }}</li>
                     <li nz-menu-item *ngIf="data.status == 1" (click)="onUpdateStatus($event, data.id, 5)">{{ 'mxk.text.lock' | i18n }}</li>
                     <li nz-menu-item *ngIf="data.status == 1" (click)="onUpdateStatus($event, data.id, 4)">{{
                       'mxk.text.disable' | i18n

+ 22 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/idm/users/users.component.ts

@@ -33,6 +33,7 @@ import { TreeNodes } from '../../../entity/TreeNodes';
 import { OrganizationsService } from '../../../service/organizations.service';
 import { UsersService } from '../../../service/users.service';
 import { set2String } from '../../../shared/index';
+import { MfaComponent } from './mfa/mfa.component';
 import { PasswordComponent } from './password/password.component';
 import { UserEditerComponent } from './user-editer/user-editer.component';
 
@@ -150,6 +151,7 @@ export class UsersComponent implements OnInit {
           nzWidth: 450,
           nzOnOk: () => new Promise(resolve => setTimeout(resolve, 1000))
         });
+        break;
       }
     }
   }
@@ -218,6 +220,26 @@ export class UsersComponent implements OnInit {
     });
   }
 
+  changeMfaById(userId: String): void {
+    for (var i = 0; i < this.query.results.rows.length; i++) {
+      let user = this.query.results.rows[i];
+      if (userId == user.id) {
+        const modal = this.modal.create({
+          nzContent: MfaComponent,
+          nzViewContainerRef: this.viewContainerRef,
+          nzComponentParams: {
+            id: user.id,
+            username: user.username,
+            displayName: user.displayName
+          },
+          nzWidth: 450,
+          nzOnOk: () => new Promise(resolve => setTimeout(resolve, 1000))
+        });
+        break;
+      }
+    }
+  }
+
   onNavToUrl(e: MouseEvent, userId: String, username: String, navType: String) {
     e.preventDefault();
     if (navType === 'groups') {

+ 4 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/service/users.service.ts

@@ -48,4 +48,8 @@ export class UsersService extends BaseService<Users> {
       params: this.parseParams(params)
     });
   }
+
+  updateAuthnType(params: NzSafeAny): Observable<Message<Users>> {
+    return this.http.put<Message<Users>>(`${this.server.urls.base}/updateAuthnType`, params);
+  }
 }

+ 5 - 11
maxkey-web-frontend/maxkey-web-mgt-app/src/assets/i18n/en-US.json

@@ -180,17 +180,11 @@
 			"userstate.withdrawn": "Withdrawn",
 			"userstate.inactive": "Inactive",
 			"userstate.retiree": "Retiree",
-			"authnType": "AuthenticationType",
-			"authnType.authnType.1": "General login",
-			"authnType.authnType.2": "Mobile Token",
-			"authnType.authnType.3": "SMS Verification",
-			"authnType.authnType.4": "EMAIL Verification",
-			"authnType.authnType.5": "TIME BASED Token",
-			"authnType.authnType.6": "Counter Token",
-			"authnType.authnType.7": "HOTP Token",
-			"authnType.authnType.8": "RSA Token",
-			"authnType.authnType.9": "Digital Certificate",
-			"authnType.authnType.10": "USB Key"
+			"authnType": "MFA Type",
+			"authnType.0": "None",
+			"authnType.1": "TOTP",
+			"authnType.2": "Mail OTP",
+			"authnType.3": "SMS OTP"
 		},
 		"organizations": {
 			"tab.basic": "Basic",

+ 5 - 11
maxkey-web-frontend/maxkey-web-mgt-app/src/assets/i18n/zh-CN.json

@@ -180,17 +180,11 @@
 			"userstate.withdrawn": "离职",
 			"userstate.inactive": "停薪留职",
 			"userstate.retiree": "退休",
-			"authnType": "登录方式",
-			"authnType.authnType.1": "普通登录",
-			"authnType.authnType.2": "手机令牌",
-			"authnType.authnType.3": "短信验证",
-			"authnType.authnType.4": "邮件验证",
-			"authnType.authnType.5": "时间令牌",
-			"authnType.authnType.6": "计数器令牌",
-			"authnType.authnType.7": "HOTP令牌",
-			"authnType.authnType.8": "RSA令牌",
-			"authnType.authnType.9": "数字证书",
-			"authnType.authnType.10": "USB Key"
+			"authnType": "二次认证",
+			"authnType.0": "无",
+			"authnType.1": "TOTP令牌",
+			"authnType.2": "邮件验证码",
+			"authnType.3": "短信验证码"
 		},
 		"organizations": {
 			"tab.basic": "基本信息",

+ 5 - 11
maxkey-web-frontend/maxkey-web-mgt-app/src/assets/i18n/zh-TW.json

@@ -181,17 +181,11 @@
 			"userstate.withdrawn": "離職",
 			"userstate.inactive": "停薪留職",
 			"userstate.retiree": "退休",
-			"authnType": "登錄方式",
-			"authnType.authnType.1": "普通登錄",
-			"authnType.authnType.2": "手機令牌",
-			"authnType.authnType.3": "短信驗證",
-			"authnType.authnType.4": "郵件驗證",
-			"authnType.authnType.5": "時間令牌",
-			"authnType.authnType.6": "計數器令牌",
-			"authnType.authnType.7": "HOTP令牌",
-			"authnType.authnType.8": "RSA令牌",
-			"authnType.authnType.9": "數字證書",
-			"authnType.authnType.10": "USB Key"
+			"authnType": "二次认证",
+			"authnType.0": "无",
+			"authnType.1": "TOTP令牌",
+			"authnType.2": "邮件验证码",
+			"authnType.3": "短信验证码"
 		},
 		"organizations": {
 			"tab.basic": "基本信息",

+ 17 - 0
maxkey-webs/maxkey-web-maxkey/src/main/java/org/dromara/maxkey/web/contorller/ProfileController.java

@@ -90,5 +90,22 @@ public class ProfileController {
         return new Message<UserInfo>(Message.FAIL);
         
     }
+	
+	/**
+     * AuthnType.
+     * 
+     * @param userInfo
+     * @param result
+     * @return
+     */
+	@PutMapping("/updateAuthnType")
+	public Message<UserInfo> updateAuthnType(@RequestBody UserInfo userInfo,@CurrentUser UserInfo currentUser) {
+		userInfo.setId(currentUser.getId());
+        logger.debug("updateAuthnType {}",userInfo);
+        if (userInfoService.updateAuthnType(userInfo)) {
+        	return new Message<>(Message.SUCCESS);
+        } 
+        return new Message<>(Message.FAIL);
+    }
 
 }

+ 17 - 0
maxkey-webs/maxkey-web-mgt/src/main/java/org/dromara/maxkey/web/idm/contorller/UserInfoController.java

@@ -59,6 +59,7 @@ import org.springframework.web.bind.WebDataBinder;
 import org.springframework.web.bind.annotation.InitBinder;
 import org.springframework.web.bind.annotation.ModelAttribute;
 import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PutMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
@@ -254,6 +255,22 @@ public class UserInfoController {
 		}
 	}
 	
+	/**
+     * AuthnType.
+     * 
+     * @param userInfo
+     * @param result
+     * @return
+     */
+	@PutMapping("/updateAuthnType")
+	public Message<UserInfo> updateAuthnType(@RequestBody UserInfo userInfo) {
+        logger.debug("updateAuthnType {}",userInfo);
+        if (userInfoService.updateAuthnType(userInfo)) {
+        	return new Message<>(Message.SUCCESS);
+        } 
+        return new Message<>(Message.FAIL);
+    }
+	
     @RequestMapping(value = "/import")
     public Message<?> importingUsers(
     		@ModelAttribute("excelImportFile")ExcelImport excelImportFile,