mirror of
https://gitee.com/dromara/MaxKey.git
synced 2025-12-08 01:48:33 +08:00
timebased otp 优化
This commit is contained in:
parent
60560bf72d
commit
a098df18a3
@ -25,7 +25,7 @@ import com.google.zxing.BarcodeFormat;
|
|||||||
import com.google.zxing.MultiFormatWriter;
|
import com.google.zxing.MultiFormatWriter;
|
||||||
import com.google.zxing.common.BitMatrix;
|
import com.google.zxing.common.BitMatrix;
|
||||||
|
|
||||||
public class RQCodeUtils {
|
public class QRCodeUtils {
|
||||||
|
|
||||||
|
|
||||||
public static void write2File(String path,String rqCodeText,String format,int width, int height ){
|
public static void write2File(String path,String rqCodeText,String format,int width, int height ){
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
package org.dromara.maxkey.entity.dto;
|
||||||
|
|
||||||
|
public record TimeBasedDto(String displayName,String username,int digits,int period,String sharedSecret,String qrCode,String otpCode) {
|
||||||
|
|
||||||
|
}
|
||||||
@ -51,10 +51,9 @@ public class TimeBasedOtpAuthn extends AbstractOtpAuthn {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean validate(UserInfo userInfo, String token) {
|
public boolean validate(UserInfo userInfo, String token) {
|
||||||
_logger.debug("utcTime : " + dateFormat.format(new Date()));
|
_logger.debug("utcTime : {}" , dateFormat.format(new Date()));
|
||||||
long currentTimeSeconds = System.currentTimeMillis() / 1000;
|
long currentTimeSeconds = System.currentTimeMillis() / 1000;
|
||||||
String sharedSecret =
|
String sharedSecret = PasswordReciprocal.getInstance().decoder(userInfo.getSharedSecret());
|
||||||
PasswordReciprocal.getInstance().decoder(userInfo.getSharedSecret());
|
|
||||||
byte[] byteSharedSecret = Base32Utils.decode(sharedSecret);
|
byte[] byteSharedSecret = Base32Utils.decode(sharedSecret);
|
||||||
String hexSharedSecret = Hex.encodeHexString(byteSharedSecret);
|
String hexSharedSecret = Hex.encodeHexString(byteSharedSecret);
|
||||||
String timeBasedToken = "";
|
String timeBasedToken = "";
|
||||||
@ -74,8 +73,8 @@ public class TimeBasedOtpAuthn extends AbstractOtpAuthn {
|
|||||||
Long.toHexString(currentTimeSeconds / interval).toUpperCase() + "",
|
Long.toHexString(currentTimeSeconds / interval).toUpperCase() + "",
|
||||||
digits + "");
|
digits + "");
|
||||||
}
|
}
|
||||||
_logger.debug("token : " + token);
|
_logger.debug("token : {}" , token);
|
||||||
_logger.debug("timeBasedToken : " + timeBasedToken);
|
_logger.debug("timeBasedToken {}: " , timeBasedToken);
|
||||||
if (token.equalsIgnoreCase(timeBasedToken)) {
|
if (token.equalsIgnoreCase(timeBasedToken)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,8 @@ export class TimeBased extends BaseEntity {
|
|||||||
digits!: String;
|
digits!: String;
|
||||||
period!: String;
|
period!: String;
|
||||||
sharedSecret!: String;
|
sharedSecret!: String;
|
||||||
|
formatSharedSecret!: String;
|
||||||
hexSharedSecret!: String;
|
hexSharedSecret!: String;
|
||||||
rqCode!: String;
|
qrCode!: String;
|
||||||
otp!: string;
|
otpCode!: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,99 +1,68 @@
|
|||||||
<nz-card>
|
<nz-card>
|
||||||
<form nz-form [formGroup]="formGroup" (ngSubmit)="onSubmit()" se-container="1">
|
<form nz-form [formGroup]="formGroup" se-container="1">
|
||||||
<div nz-row style="width: 100%">
|
<div nz-row style="width: 100%">
|
||||||
<div nz-col nzFlex="2" style="text-align: center"><img src="{{ form.model.rqCode }}" /></div>
|
<div nz-col nzFlex="2" style="text-align: center"><img src="{{ form.model.qrCode }}" /></div>
|
||||||
<div nz-col nzFlex="3">
|
<div nz-col nzFlex="3">
|
||||||
<nz-form-item style="display: none">
|
<nz-form-item style="display: none">
|
||||||
<nz-form-label [nzMd]="6" nzFor="id">id</nz-form-label>
|
<nz-form-label [nzMd]="6" nzFor="id">id</nz-form-label>
|
||||||
<nz-form-control [nzMd]="18" nzErrorTip="The input is not valid id!">
|
<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" />
|
<input [(ngModel)]="form.model.id" [ngModelOptions]="{ standalone: true }" nz-input name="id" id="id"
|
||||||
|
value="id" />
|
||||||
</nz-form-control>
|
</nz-form-control>
|
||||||
</nz-form-item>
|
</nz-form-item>
|
||||||
<nz-form-item>
|
<nz-form-item>
|
||||||
<nz-form-label [nzSm]="6" [nzXs]="24" nzFor="displayName">{{ 'mxk.timebased.displayName' | i18n }}</nz-form-label>
|
<nz-form-label [nzSm]="6" [nzXs]="24" nzFor="displayName">{{ 'mxk.timebased.displayName' | i18n
|
||||||
|
}}</nz-form-label>
|
||||||
<nz-form-control [nzSm]="14" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not displayName!">
|
<nz-form-control [nzSm]="14" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not displayName!">
|
||||||
<input
|
<input nz-input [disabled]="isDisabled" [(ngModel)]="form.model.displayName"
|
||||||
nz-input
|
[ngModelOptions]="{ standalone: true }" name="displayName" id="displayName" value="0" />
|
||||||
[disabled]="isDisabled"
|
|
||||||
[(ngModel)]="form.model.displayName"
|
|
||||||
[ngModelOptions]="{ standalone: true }"
|
|
||||||
name="displayName"
|
|
||||||
id="displayName"
|
|
||||||
value="0"
|
|
||||||
/>
|
|
||||||
</nz-form-control>
|
</nz-form-control>
|
||||||
</nz-form-item>
|
</nz-form-item>
|
||||||
<nz-form-item>
|
<nz-form-item>
|
||||||
<nz-form-label [nzSm]="6" [nzXs]="24" nzFor="username">{{ 'mxk.timebased.username' | i18n }}</nz-form-label>
|
<nz-form-label [nzSm]="6" [nzXs]="24" nzFor="username">{{ 'mxk.timebased.username' | i18n }}</nz-form-label>
|
||||||
<nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="The input is not valid username!">
|
<nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="The input is not valid username!">
|
||||||
<input
|
<input nz-input [disabled]="isDisabled" [(ngModel)]="form.model.username"
|
||||||
nz-input
|
[ngModelOptions]="{ standalone: true }" name="username" id="username" />
|
||||||
[disabled]="isDisabled"
|
|
||||||
[(ngModel)]="form.model.username"
|
|
||||||
[ngModelOptions]="{ standalone: true }"
|
|
||||||
name="username"
|
|
||||||
id="username"
|
|
||||||
/>
|
|
||||||
</nz-form-control>
|
</nz-form-control>
|
||||||
</nz-form-item>
|
</nz-form-item>
|
||||||
<nz-form-item>
|
<nz-form-item>
|
||||||
<nz-form-label [nzSm]="6" [nzXs]="24" nzFor="digits">{{ 'mxk.timebased.digits' | i18n }}</nz-form-label>
|
<nz-form-label [nzSm]="6" [nzXs]="24" nzFor="digits">{{ 'mxk.timebased.digits' | i18n }}</nz-form-label>
|
||||||
<nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="The input is not valid digits!">
|
<nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="The input is not valid digits!">
|
||||||
<input nz-input [disabled]="isDisabled" [(ngModel)]="form.model.digits" [ngModelOptions]="{ standalone: true }" name="digits" id="digits" />
|
<input nz-input [disabled]="isDisabled" [(ngModel)]="form.model.digits"
|
||||||
|
[ngModelOptions]="{ standalone: true }" name="digits" id="digits" />
|
||||||
</nz-form-control>
|
</nz-form-control>
|
||||||
</nz-form-item>
|
</nz-form-item>
|
||||||
<nz-form-item>
|
<nz-form-item>
|
||||||
<nz-form-label [nzSm]="6" [nzXs]="24" nzFor="period">{{ 'mxk.timebased.period' | i18n }}</nz-form-label>
|
<nz-form-label [nzSm]="6" [nzXs]="24" nzFor="period">{{ 'mxk.timebased.period' | i18n }}</nz-form-label>
|
||||||
<nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="The input is not valid period!">
|
<nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="The input is not valid period!">
|
||||||
<input nz-input [disabled]="isDisabled" [(ngModel)]="form.model.period" [ngModelOptions]="{ standalone: true }" name="period" id="period" />
|
<input nz-input [disabled]="isDisabled" [(ngModel)]="form.model.period"
|
||||||
|
[ngModelOptions]="{ standalone: true }" name="period" id="period" />
|
||||||
</nz-form-control>
|
</nz-form-control>
|
||||||
</nz-form-item>
|
</nz-form-item>
|
||||||
<nz-form-item>
|
<nz-form-item>
|
||||||
<nz-form-label [nzSm]="6" [nzXs]="24" nzFor="sharedSecret">{{ 'mxk.timebased.sharedSecret' | i18n }}</nz-form-label>
|
<nz-form-label [nzSm]="6" [nzXs]="24" nzFor="sharedSecret">{{ 'mxk.timebased.sharedSecret' | i18n
|
||||||
|
}}</nz-form-label>
|
||||||
<nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="The input is not valid sharedSecret!">
|
<nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="The input is not valid sharedSecret!">
|
||||||
<input
|
<input nz-input [disabled]="isDisabled" [(ngModel)]="form.model.formatSharedSecret"
|
||||||
nz-input
|
[ngModelOptions]="{ standalone: true }" name="formatSharedSecret" id="formatSharedSecret" />
|
||||||
[disabled]="isDisabled"
|
|
||||||
[(ngModel)]="form.model.sharedSecret"
|
|
||||||
[ngModelOptions]="{ standalone: true }"
|
|
||||||
name="sharedSecret"
|
|
||||||
id="sharedSecret"
|
|
||||||
/>
|
|
||||||
</nz-form-control>
|
</nz-form-control>
|
||||||
</nz-form-item>
|
</nz-form-item>
|
||||||
<nz-form-item>
|
<nz-form-item>
|
||||||
<nz-form-label [nzSm]="6" [nzXs]="24" nzFor="hexSharedSecret">{{ 'mxk.timebased.hexSharedSecret' | i18n }}</nz-form-label>
|
<nz-form-label [nzSm]="6" [nzXs]="24" nzFor="one-timePassword">{{ 'mxk.timebased.one-timePassword' | i18n
|
||||||
<nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="The input is not valid hexSharedSecret!">
|
}}</nz-form-label>
|
||||||
<input
|
|
||||||
nz-input
|
|
||||||
[disabled]="isDisabled"
|
|
||||||
[(ngModel)]="form.model.hexSharedSecret"
|
|
||||||
[ngModelOptions]="{ standalone: true }"
|
|
||||||
name="hexSharedSecret"
|
|
||||||
id="hexSharedSecret"
|
|
||||||
/>
|
|
||||||
</nz-form-control>
|
|
||||||
</nz-form-item>
|
|
||||||
<nz-form-item>
|
|
||||||
<nz-form-label [nzSm]="6" [nzXs]="24" nzFor="one-timePassword">{{ 'mxk.timebased.one-timePassword' | i18n }}</nz-form-label>
|
|
||||||
<nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="The input is not valid One-Time Password!">
|
<nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="The input is not valid One-Time Password!">
|
||||||
<input
|
<input nz-input [(ngModel)]="form.model.otpCode" [ngModelOptions]="{ standalone: true }"
|
||||||
nz-input
|
placeholder="请在生成后输入一次性密码用于验证" name="oneTimePassword" id="oneTimePassword" />
|
||||||
[(ngModel)]="form.model.otp"
|
|
||||||
[ngModelOptions]="{ standalone: true }"
|
|
||||||
placeholder="请在生成后输入一次性密码用于验证"
|
|
||||||
name="oneTimePassword"
|
|
||||||
id="oneTimePassword"
|
|
||||||
/>
|
|
||||||
</nz-form-control>
|
</nz-form-control>
|
||||||
</nz-form-item>
|
</nz-form-item>
|
||||||
<nz-form-item style="width: 100%">
|
<nz-form-item style="width: 100%">
|
||||||
<nz-form-control [nzOffset]="7" [nzSpan]="3">
|
<button nz-button nzType="primary" (click)="generate()">{{ 'mxk.text.generate' | i18n }}</button>
|
||||||
<button nz-button nzType="primary" type="submit" [nzLoading]="form.submitting">{{ 'mxk.text.generate' | i18n }}</button>
|
<button nz-button nzType="primary" (click)="onSubmit()" [nzLoading]="form.submitting">{{ 'mxk.text.save' |
|
||||||
</nz-form-control>
|
i18n }}</button>
|
||||||
<button nz-button nzType="primary" (click)="verify($event,form.model.otp)">{{ 'mxk.text.verify' | i18n }}</button>
|
<button nz-button nzType="primary" (click)="verify($event, form.model.otpCode)">{{ 'mxk.text.verify' | i18n
|
||||||
|
}}</button>
|
||||||
</nz-form-item>
|
</nz-form-item>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</nz-card>
|
</nz-card>
|
||||||
@ -28,16 +28,16 @@ import { Console } from 'console';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-timebased',
|
selector: 'app-timebased',
|
||||||
templateUrl: './timebased.component.html',
|
templateUrl: './timebased.component.html',
|
||||||
styleUrls: ['./timebased.component.less'],
|
styleUrls: ['./timebased.component.less']
|
||||||
})
|
})
|
||||||
export class TimebasedComponent implements OnInit {
|
export class TimebasedComponent implements OnInit {
|
||||||
form: {
|
form: {
|
||||||
submitting: boolean;
|
submitting: boolean;
|
||||||
model: TimeBased;
|
model: TimeBased;
|
||||||
} = {
|
} = {
|
||||||
submitting: false,
|
submitting: false,
|
||||||
model: new TimeBased()
|
model: new TimeBased()
|
||||||
};
|
};
|
||||||
|
|
||||||
isDisabled = true;
|
isDisabled = true;
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ export class TimebasedComponent implements OnInit {
|
|||||||
private timeBasedService: TimeBasedService,
|
private timeBasedService: TimeBasedService,
|
||||||
private msg: NzMessageService,
|
private msg: NzMessageService,
|
||||||
private cdr: ChangeDetectorRef
|
private cdr: ChangeDetectorRef
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
/*this.form = this.fb.group({
|
/*this.form = this.fb.group({
|
||||||
@ -62,7 +62,7 @@ export class TimebasedComponent implements OnInit {
|
|||||||
public: [1, [Validators.min(1), Validators.max(3)]],
|
public: [1, [Validators.min(1), Validators.max(3)]],
|
||||||
publicUsers: [null, []]
|
publicUsers: [null, []]
|
||||||
});*/
|
});*/
|
||||||
this.timeBasedService.get('').subscribe(res => {
|
this.timeBasedService.view('').subscribe(res => {
|
||||||
this.form.model.init(res.data);
|
this.form.model.init(res.data);
|
||||||
this.formatSecret();
|
this.formatSecret();
|
||||||
this.cdr.detectChanges();
|
this.cdr.detectChanges();
|
||||||
@ -70,17 +70,31 @@ export class TimebasedComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
formatSecret(): void {
|
formatSecret(): void {
|
||||||
this.form.model.sharedSecret = concatArrayString(splitString(this.form.model.sharedSecret, 4), ' ');
|
this.form.model.formatSharedSecret = concatArrayString(splitString(this.form.model.sharedSecret, 4), ' ');
|
||||||
this.form.model.hexSharedSecret = concatArrayString(splitString(this.form.model.hexSharedSecret, 4), ' ');
|
//this.form.model.hexSharedSecret = concatArrayString(splitString(this.form.model.hexSharedSecret, 4), ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
generate(): void {
|
||||||
|
this.form.submitting = true;
|
||||||
|
this.form.model.trans();
|
||||||
|
this.timeBasedService.generate('').subscribe(res => {
|
||||||
|
if (res.code == 0) {
|
||||||
|
this.form.model.init(res.data);
|
||||||
|
this.formatSecret();
|
||||||
|
//this.msg.success(`提交成功`);
|
||||||
|
} else {
|
||||||
|
//this.msg.success(`提交失败`);
|
||||||
|
}
|
||||||
|
this.form.submitting = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit(): void {
|
onSubmit(): void {
|
||||||
this.form.submitting = true;
|
this.form.submitting = true;
|
||||||
this.form.model.trans();
|
//this.form.model.trans();
|
||||||
this.timeBasedService.update(this.form.model).subscribe(res => {
|
this.timeBasedService.update(this.form.model).subscribe(res => {
|
||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
this.form.model.init(res.data);
|
|
||||||
this.formatSecret();
|
|
||||||
this.msg.success(`提交成功`);
|
this.msg.success(`提交成功`);
|
||||||
} else {
|
} else {
|
||||||
this.msg.success(`提交失败`);
|
this.msg.success(`提交失败`);
|
||||||
@ -98,7 +112,7 @@ export class TimebasedComponent implements OnInit {
|
|||||||
} else {
|
} else {
|
||||||
this.msg.error('验证失败');
|
this.msg.error('验证失败');
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
// this.timeBasedService.verify(otp)
|
// this.timeBasedService.verify(otp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -27,18 +27,22 @@ import { BaseService } from './base.service';
|
|||||||
})
|
})
|
||||||
export class TimeBasedService extends BaseService<TimeBased> {
|
export class TimeBasedService extends BaseService<TimeBased> {
|
||||||
constructor(private _httpClient: HttpClient) {
|
constructor(private _httpClient: HttpClient) {
|
||||||
super(_httpClient, '/config');
|
super(_httpClient, '/config/timebased');
|
||||||
}
|
}
|
||||||
|
|
||||||
override get(id: String): Observable<Message<TimeBased>> {
|
view(id: String): Observable<Message<TimeBased>> {
|
||||||
return this.http.get<Message<TimeBased>>(`${this.server.urls.base}/timebased?generate=NO`);
|
return this.http.get<Message<TimeBased>>(`${this.server.urls.base}/view`);
|
||||||
|
}
|
||||||
|
|
||||||
|
generate(id: String): Observable<Message<TimeBased>> {
|
||||||
|
return this.http.get<Message<TimeBased>>(`${this.server.urls.base}/generate`);
|
||||||
}
|
}
|
||||||
|
|
||||||
override update(body: any): Observable<Message<TimeBased>> {
|
override update(body: any): Observable<Message<TimeBased>> {
|
||||||
return this.http.get<Message<TimeBased>>(`${this.server.urls.base}/timebased?generate=YES`);
|
return this.http.put<Message<TimeBased>>(`${this.server.urls.base}/update`, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
verify(otp: string): Observable<Message<TimeBased>>{
|
verify(otp: string): Observable<Message<TimeBased>> {
|
||||||
return this.http.get<Message<TimeBased>>(`${this.server.urls.base}/verify?otp=` + otp);
|
return this.http.get<Message<TimeBased>>(`${this.server.urls.base}/verify?otpCode=${otp}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -541,7 +541,7 @@
|
|||||||
"username": "username",
|
"username": "username",
|
||||||
"digits": "digits",
|
"digits": "digits",
|
||||||
"period": "period",
|
"period": "period",
|
||||||
"sharedSecret": "sharedSecret(BASE32)",
|
"sharedSecret": "sharedSecret",
|
||||||
"hexSharedSecret": "sharedSecret( HEX )",
|
"hexSharedSecret": "sharedSecret( HEX )",
|
||||||
"rqCode": "RQCode",
|
"rqCode": "RQCode",
|
||||||
"one-timePassword": "One-Time Password"
|
"one-timePassword": "One-Time Password"
|
||||||
|
|||||||
@ -527,7 +527,7 @@
|
|||||||
"username": "账号",
|
"username": "账号",
|
||||||
"digits": "长度",
|
"digits": "长度",
|
||||||
"period": "周期",
|
"period": "周期",
|
||||||
"sharedSecret": "共享密钥(BASE32)",
|
"sharedSecret": "共享密钥",
|
||||||
"hexSharedSecret": "共享密钥( HEX )",
|
"hexSharedSecret": "共享密钥( HEX )",
|
||||||
"rqCode": "二维码",
|
"rqCode": "二维码",
|
||||||
"one-timePassword": "一次性密码"
|
"one-timePassword": "一次性密码"
|
||||||
|
|||||||
@ -527,7 +527,7 @@
|
|||||||
"username": "賬號",
|
"username": "賬號",
|
||||||
"digits": "長度",
|
"digits": "長度",
|
||||||
"period": "週期",
|
"period": "週期",
|
||||||
"sharedSecret": "共享密鑰(BASE32)",
|
"sharedSecret": "共享密鑰",
|
||||||
"hexSharedSecret": "共享密鑰( HEX )",
|
"hexSharedSecret": "共享密鑰( HEX )",
|
||||||
"rqCode": "二維碼",
|
"rqCode": "二維碼",
|
||||||
"one-timePassword": "一次性密碼"
|
"one-timePassword": "一次性密碼"
|
||||||
|
|||||||
@ -48,7 +48,7 @@ import org.dromara.maxkey.password.sms.SmsOtpAuthnService;
|
|||||||
import org.dromara.maxkey.authn.provider.scancode.ScanCodeService;
|
import org.dromara.maxkey.authn.provider.scancode.ScanCodeService;
|
||||||
import org.dromara.maxkey.persistence.service.SocialsAssociatesService;
|
import org.dromara.maxkey.persistence.service.SocialsAssociatesService;
|
||||||
import org.dromara.maxkey.persistence.service.UserInfoService;
|
import org.dromara.maxkey.persistence.service.UserInfoService;
|
||||||
import org.dromara.maxkey.util.RQCodeUtils;
|
import org.dromara.maxkey.util.QRCodeUtils;
|
||||||
import org.dromara.maxkey.web.WebConstants;
|
import org.dromara.maxkey.web.WebConstants;
|
||||||
import org.dromara.maxkey.web.WebContext;
|
import org.dromara.maxkey.web.WebContext;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -286,7 +286,7 @@ public class LoginEntryPoint {
|
|||||||
String ticket = scanCodeService.createTicket();
|
String ticket = scanCodeService.createTicket();
|
||||||
log.debug("ticket: {}",ticket);
|
log.debug("ticket: {}",ticket);
|
||||||
String encodeTicket = PasswordReciprocal.getInstance().encode(ticket);
|
String encodeTicket = PasswordReciprocal.getInstance().encode(ticket);
|
||||||
BufferedImage bufferedImage = RQCodeUtils.write2BufferedImage(encodeTicket, "gif", 300, 300);
|
BufferedImage bufferedImage = QRCodeUtils.write2BufferedImage(encodeTicket, "gif", 300, 300);
|
||||||
String rqCode = Base64Utils.encodeImage(bufferedImage);
|
String rqCode = Base64Utils.encodeImage(bufferedImage);
|
||||||
HashMap<String,String> codeMap = new HashMap<>();
|
HashMap<String,String> codeMap = new HashMap<>();
|
||||||
codeMap.put("rqCode", rqCode);
|
codeMap.put("rqCode", rqCode);
|
||||||
|
|||||||
@ -18,29 +18,29 @@
|
|||||||
package org.dromara.maxkey.web.contorller;
|
package org.dromara.maxkey.web.contorller;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
import org.apache.commons.codec.binary.Hex;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.dromara.maxkey.authn.annotation.CurrentUser;
|
import org.dromara.maxkey.authn.annotation.CurrentUser;
|
||||||
import org.dromara.maxkey.crypto.Base32Utils;
|
import org.dromara.maxkey.crypto.Base32Utils;
|
||||||
import org.dromara.maxkey.crypto.Base64Utils;
|
import org.dromara.maxkey.crypto.Base64Utils;
|
||||||
import org.dromara.maxkey.crypto.password.PasswordReciprocal;
|
import org.dromara.maxkey.crypto.password.PasswordReciprocal;
|
||||||
import org.dromara.maxkey.entity.Message;
|
import org.dromara.maxkey.entity.Message;
|
||||||
|
import org.dromara.maxkey.entity.dto.TimeBasedDto;
|
||||||
import org.dromara.maxkey.entity.idm.UserInfo;
|
import org.dromara.maxkey.entity.idm.UserInfo;
|
||||||
import org.dromara.maxkey.password.onetimepwd.algorithm.OtpKeyUriFormat;
|
import org.dromara.maxkey.password.onetimepwd.algorithm.OtpKeyUriFormat;
|
||||||
import org.dromara.maxkey.password.onetimepwd.algorithm.OtpSecret;
|
import org.dromara.maxkey.password.onetimepwd.algorithm.OtpSecret;
|
||||||
import org.dromara.maxkey.password.onetimepwd.impl.TimeBasedOtpAuthn;
|
import org.dromara.maxkey.password.onetimepwd.impl.TimeBasedOtpAuthn;
|
||||||
import org.dromara.maxkey.persistence.service.UserInfoService;
|
import org.dromara.maxkey.persistence.service.UserInfoService;
|
||||||
import org.dromara.maxkey.util.RQCodeUtils;
|
import org.dromara.maxkey.util.QRCodeUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.stereotype.Controller;
|
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.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,8 +48,8 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
|||||||
* @author Crystal.Sea
|
* @author Crystal.Sea
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@Controller
|
@RestController
|
||||||
@RequestMapping(value = { "/config" })
|
@RequestMapping(value = { "/config/timebased" })
|
||||||
public class OneTimePasswordController {
|
public class OneTimePasswordController {
|
||||||
static final Logger logger = LoggerFactory.getLogger(OneTimePasswordController.class);
|
static final Logger logger = LoggerFactory.getLogger(OneTimePasswordController.class);
|
||||||
|
|
||||||
@ -62,55 +62,73 @@ public class OneTimePasswordController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
TimeBasedOtpAuthn timeBasedOtpAuthn;
|
TimeBasedOtpAuthn timeBasedOtpAuthn;
|
||||||
|
|
||||||
@RequestMapping(value = {"/timebased"})
|
@GetMapping(value = {"/view"})
|
||||||
@ResponseBody
|
public Message<TimeBasedDto> view(@CurrentUser UserInfo currentUser) {
|
||||||
public Message<?> timebased(
|
UserInfo user = userInfoService.get(currentUser.getId());
|
||||||
@RequestParam(name="generate") String generate,
|
String sharedSecret = "";
|
||||||
@CurrentUser UserInfo currentUser) {
|
String qrCode = "";
|
||||||
HashMap<String,Object >timebased =new HashMap<>();
|
if(StringUtils.isNotBlank(user.getSharedSecret())) {
|
||||||
|
sharedSecret = PasswordReciprocal.getInstance().decoder(user.getSharedSecret());
|
||||||
generate(generate,currentUser);
|
qrCode = genQRCode(sharedSecret,currentUser.getUsername());
|
||||||
|
}
|
||||||
String sharedSecret =
|
return new Message<>(
|
||||||
PasswordReciprocal.getInstance().decoder(currentUser.getSharedSecret());
|
new TimeBasedDto(
|
||||||
|
user.getDisplayName(),
|
||||||
otpKeyUriFormat.setSecret(sharedSecret);
|
user.getUsername(),
|
||||||
String otpauth = otpKeyUriFormat.format(currentUser.getUsername());
|
otpKeyUriFormat.getDigits(),
|
||||||
byte[] byteSharedSecret = Base32Utils.decode(sharedSecret);
|
otpKeyUriFormat.getPeriod(),
|
||||||
String hexSharedSecret = Hex.encodeHexString(byteSharedSecret);
|
sharedSecret,
|
||||||
BufferedImage bufferedImage = RQCodeUtils.write2BufferedImage(otpauth, "gif", 300, 300);
|
qrCode,
|
||||||
String rqCode = Base64Utils.encodeImage(bufferedImage);
|
""
|
||||||
|
));
|
||||||
timebased.put("displayName", currentUser.getDisplayName());
|
|
||||||
timebased.put("username", currentUser.getUsername());
|
|
||||||
timebased.put("digits", otpKeyUriFormat.getDigits());
|
|
||||||
timebased.put("period", otpKeyUriFormat.getPeriod());
|
|
||||||
timebased.put("sharedSecret", sharedSecret);
|
|
||||||
timebased.put("hexSharedSecret", hexSharedSecret);
|
|
||||||
timebased.put("rqCode", rqCode);
|
|
||||||
return new Message<HashMap<String,Object >>(timebased);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void generate(String generate,@CurrentUser UserInfo currentUser) {
|
@GetMapping(value = {"/generate"})
|
||||||
if((StringUtils.isNotBlank(generate)
|
public Message<TimeBasedDto> generate(@CurrentUser UserInfo currentUser) {
|
||||||
&& generate.equalsIgnoreCase("YES"))
|
//generate
|
||||||
||StringUtils.isBlank(currentUser.getSharedSecret())) {
|
byte[] byteSharedSecret = OtpSecret.generate(otpKeyUriFormat.getCrypto());
|
||||||
|
String sharedSecret = Base32Utils.encode(byteSharedSecret);
|
||||||
byte[] byteSharedSecret = OtpSecret.generate(otpKeyUriFormat.getCrypto());
|
String qrCode = genQRCode(sharedSecret,currentUser.getUsername());
|
||||||
String sharedSecret = Base32Utils.encode(byteSharedSecret);
|
return new Message<>(
|
||||||
sharedSecret = PasswordReciprocal.getInstance().encode(sharedSecret);
|
new TimeBasedDto(
|
||||||
currentUser.setSharedSecret(sharedSecret);
|
currentUser.getDisplayName(),
|
||||||
userInfoService.updateSharedSecret(currentUser);
|
currentUser.getUsername(),
|
||||||
|
otpKeyUriFormat.getDigits(),
|
||||||
|
otpKeyUriFormat.getPeriod(),
|
||||||
|
sharedSecret,
|
||||||
|
qrCode,
|
||||||
|
""
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(value = {"/update"})
|
||||||
|
public Message<String> update(@RequestBody TimeBasedDto timeBasedDto , @CurrentUser UserInfo currentUser) {
|
||||||
|
// 从当前用户信息中获取共享密钥
|
||||||
|
UserInfo user = new UserInfo();
|
||||||
|
user.setId(currentUser.getId());
|
||||||
|
user.setSharedSecret(PasswordReciprocal.getInstance().encode(timeBasedDto.sharedSecret()));
|
||||||
|
// 计算当前时间对应的动态密码
|
||||||
|
if (StringUtils.isNotBlank(timeBasedDto.otpCode()) && timeBasedOtpAuthn.validate(user, timeBasedDto.otpCode())) {
|
||||||
|
userInfoService.updateSharedSecret(user);
|
||||||
|
return new Message<>(Message.SUCCESS);
|
||||||
|
} else {
|
||||||
|
return new Message<>(Message.FAIL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping("/verify")
|
public String genQRCode(String sharedSecret,String username) {
|
||||||
public Message<?> verify(@RequestParam("otp") String otp, @CurrentUser UserInfo currentUser) {
|
otpKeyUriFormat.setSecret(sharedSecret);
|
||||||
|
String otpauth = otpKeyUriFormat.format(username);
|
||||||
|
BufferedImage bufferedImage = QRCodeUtils.write2BufferedImage(otpauth, "gif", 300, 300);
|
||||||
|
return Base64Utils.encodeImage(bufferedImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/verify")
|
||||||
|
public Message<String> verify(@RequestParam("otpCode") String otpCode, @CurrentUser UserInfo currentUser) {
|
||||||
// 从当前用户信息中获取共享密钥
|
// 从当前用户信息中获取共享密钥
|
||||||
String sharedSecret = PasswordReciprocal.getInstance().decoder(currentUser.getSharedSecret());
|
UserInfo user = userInfoService.get(currentUser.getId());
|
||||||
// 计算当前时间对应的动态密码
|
// 计算当前时间对应的动态密码
|
||||||
boolean validate = timeBasedOtpAuthn.validate(currentUser, otp);
|
boolean validate = timeBasedOtpAuthn.validate(user, otpCode);
|
||||||
if (validate) {
|
if (validate) {
|
||||||
return new Message<>(0,"One-Time Password verification succeeded");
|
return new Message<>(0,"One-Time Password verification succeeded");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user