View Javadoc
1   /*
2    * Copyright © 2025 rosestack.github.io
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package io.github.rose.security.rest.mfa.provider.impl;
17  
18  import io.github.rose.core.json.JsonUtils;
19  import io.github.rose.security.rest.mfa.config.OtpBasedMfaConfig;
20  import io.github.rose.security.rest.mfa.provider.MfaProvider;
21  import io.github.rose.security.rest.mfa.provider.OtpBasedMfaProviderConfig;
22  import io.github.rose.security.util.SecurityUser;
23  import org.apache.commons.lang3.RandomStringUtils;
24  import org.springframework.cache.CacheManager;
25  import org.springframework.stereotype.Component;
26  
27  import java.io.Serializable;
28  import java.util.concurrent.TimeUnit;
29  
30  import static io.github.rose.security.CacheConstants.TWO_FA_VERIFICATION_CODE_CACHE;
31  
32  @Component
33  public abstract class OtpBasedMfaProvider<C extends OtpBasedMfaProviderConfig, A extends OtpBasedMfaConfig>
34      implements MfaProvider<C, A> {
35  
36      private final CacheManager cacheManager;
37  
38      public OtpBasedMfaProvider(CacheManager cacheManager) {
39          this.cacheManager = cacheManager;
40      }
41  
42      @Override
43      public final void prepareVerificationCode(SecurityUser user, C providerConfig, A twoFaConfig) {
44          String verificationCode = RandomStringUtils.randomNumeric(6);
45          sendVerificationCode(user, verificationCode, providerConfig, twoFaConfig);
46          cacheManager
47              .getCache(TWO_FA_VERIFICATION_CODE_CACHE)
48              .put(
49                  TWO_FA_VERIFICATION_CODE_CACHE + ":" + user.getUsername(),
50                  JsonUtils.toBytes(new Otp(System.currentTimeMillis(), verificationCode, twoFaConfig)));
51      }
52  
53      protected abstract void sendVerificationCode(
54          SecurityUser user, String verificationCode, C providerConfig, A accountConfig);
55  
56      @Override
57      public final boolean checkVerificationCode(SecurityUser user, String code, C providerConfig, A twoFaConfig) {
58          String correctVerificationCode = cacheManager
59              .getCache(TWO_FA_VERIFICATION_CODE_CACHE)
60              .get(TWO_FA_VERIFICATION_CODE_CACHE + ":" + user.getUsername())
61              .toString();
62          Otp otp = JsonUtils.fromJson(correctVerificationCode, Otp.class);
63          if (correctVerificationCode != null) {
64              if (System.currentTimeMillis() - otp.getTimestamp()
65                  > TimeUnit.SECONDS.toMillis(providerConfig.getVerificationCodeExpireTime())) {
66                  cacheManager
67                      .getCache(TWO_FA_VERIFICATION_CODE_CACHE)
68                      .evict(TWO_FA_VERIFICATION_CODE_CACHE + ":" + user.getUsername());
69                  return false;
70              }
71              if (code.equals(otp.getValue()) && twoFaConfig.equals(otp.getTwoFaConfig())) {
72                  cacheManager
73                      .getCache(TWO_FA_VERIFICATION_CODE_CACHE)
74                      .evict(TWO_FA_VERIFICATION_CODE_CACHE + ":" + user.getUsername());
75                  return true;
76              }
77          }
78          return false;
79      }
80  
81      public static class Otp implements Serializable {
82  
83          private final long timestamp;
84  
85          private final String value;
86  
87          private final OtpBasedMfaConfig twoFaConfig;
88  
89          public Otp(long timestamp, String value, OtpBasedMfaConfig twoFaConfig) {
90              this.timestamp = timestamp;
91              this.value = value;
92              this.twoFaConfig = twoFaConfig;
93          }
94  
95          public long getTimestamp() {
96              return timestamp;
97          }
98  
99          public String getValue() {
100             return value;
101         }
102 
103         public OtpBasedMfaConfig getTwoFaConfig() {
104             return twoFaConfig;
105         }
106     }
107 }