欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring Security添加二次認(rèn)證的項(xiàng)目實(shí)踐

 更新時(shí)間:2023年12月11日 10:18:19   作者:格林希爾  
在用戶自動(dòng)登錄后,可以通過(guò)對(duì)密碼進(jìn)行二次校驗(yàn)進(jìn)而確保用戶的真實(shí)性,本文就來(lái)介紹一下Spring Security添加二次認(rèn)證的項(xiàng)目實(shí)踐,具有一定的參考價(jià)值,感興趣的可以了解一下

一、 簡(jiǎn)介

1 Spring Security概述

Spring Security是一個(gè)基于Spring框架的安全框架,用于為Java應(yīng)用程序提供身份驗(yàn)證和授權(quán)服務(wù)。

2 二次認(rèn)證的必要性

傳統(tǒng)的用戶名和密碼驗(yàn)證方式存在被破解的風(fēng)險(xiǎn),因此在用戶登錄后需要進(jìn)行二次認(rèn)證,增強(qiáng)身份驗(yàn)證的安全性。

二、 Spring Security實(shí)現(xiàn)二次認(rèn)證的方式

1 使用已有二次認(rèn)證服務(wù)

1.1 集成Google Authenticator

Google Authenticator是一款基于TOTP算法的開(kāi)源軟件,用戶可以將其安裝在智能手機(jī)上,以便進(jìn)行二次認(rèn)證。Spring Security可以通過(guò)Google Authenticator進(jìn)行二次認(rèn)證。下面是一個(gè)簡(jiǎn)單的示例。

1.1.1 在pom.xml中添加依賴

<dependency>
    <groupId>com.warrenstrange</groupId>
    <artifactId>googleauth</artifactId>
    <version>1.0.0</version>
</dependency>

1.1.2 實(shí)現(xiàn)Google Authenticator

@Service
public class GoogleAuthenticatorService {

    private final GoogleAuthenticator gAuth = new GoogleAuthenticator();

    /** 獲取密鑰 **/
    public String createSecret() {
        final GoogleAuthenticatorKey gak = gAuth.createCredentials();
        return gak.getKey();
    }

    /** 驗(yàn)證身份 **/
    public boolean authorize(final String secret, final int otp) {
        return gAuth.authorize(secret, otp);
    }

    /** 獲取二維碼 **/
    public String getQR(final String secret, final String account) {
        final String format = "otpauth://totp/%s?secret=%s&issuer=%s";
        return String.format(format, account, secret, account);
    }

}

在上述代碼中創(chuàng)建了一個(gè)GoogleAuthenticatorService類,用于實(shí)現(xiàn)Google Authenticator的相關(guān)功能。其中,createSecret()方法用于創(chuàng)建一個(gè)新的密鑰,authorize()方法用于驗(yàn)證一個(gè)TOTP是否有效,getQR()方法用于生成一個(gè)TOTP的二維碼。

1.1.3 在Spring Security中配置Google Authenticator

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private GoogleAuthenticatorService googleAuthenticatorService;

    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                    .antMatchers("/user/**").authenticated()
                    .antMatchers("/admin/**").hasAnyRole("ADMIN")
                    .and()
                .formLogin()
                    .and()
                .addFilterBefore(buildGoogleAuthFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    private GoogleAuthFilter buildGoogleAuthFilter() throws Exception {
        final GoogleAuthFilter filter = new GoogleAuthFilter("/check-google-auth");
        filter.setSecretProvider((request, username) -> {
            final String secret = userService.getSecret(username); // 獲取用戶的密鑰
            return secret != null ? secret : "";
        });
        filter.setGoogleAuthenticator(googleAuthenticatorService.getGoogleAuthenticator());
        return filter;
    }

}

在上述代碼中創(chuàng)建了一個(gè)SecurityConfig類,并在其中定義了一個(gè)GoogleAuthFilter過(guò)濾器用于添加二次認(rèn)證功能。在該過(guò)濾器中,我們通過(guò)setSecretProvider()方法獲取用戶的密鑰,然后通過(guò)setGoogleAuthenticator()方法設(shè)置Google Authenticator的實(shí)例。

1.2 集成Authy

Authy是一款基于TOTP算法的二次認(rèn)證服務(wù)提供商,可以為Spring Security提供二次認(rèn)證服務(wù)。下面是一個(gè)簡(jiǎn)單的示例。

1.2.1 在pom.xml中添加依賴

<dependency>
    <groupId>com.authy</groupId>
    <artifactId>authy-client</artifactId>
    <version>1.2</version>
</dependency>

1.2.2 實(shí)現(xiàn)Authy

@Service
public class AuthyService {

    /** Authy API Key **/
    private final static String AUTHY_API_KEY = "your-authy-api-key";

    /** Authy客戶端 **/
    private final AuthyApiClient authyApiClient = new AuthyApiClient(AUTHY_API_KEY);

    /** 注冊(cè)用戶 **/
    public User createUser(final String email, final String countryCode, final String phone) throws Exception {
        final Users users = authyApiClient.getUsers();
        final User user = users.createUser(email, phone, countryCode);
        return user;
    }

    /** 發(fā)送驗(yàn)證碼 **/
    public void sendVerification(final String userId, final String via) throws Exception {
        final Tokens tokens = authyApiClient.getTokens();
        tokens.requestSms(Integer.valueOf(userId), via);
    }

    /** 驗(yàn)證驗(yàn)證碼 **/
    public boolean verifyToken(final String userId, final int token) throws Exception {
        final Tokens tokens = authyApiClient.getTokens();
        final TokenVerification tokenVerification = tokens.verify(Integer.valueOf(userId), token);
        return tokenVerification.isOk();
    }

}

在上述代碼中創(chuàng)建了一個(gè)AuthyService類,用于與Authy客戶端進(jìn)行交互。其中,createUser()方法用于注冊(cè)一個(gè)新用戶,sendVerification()方法用于向用戶發(fā)送驗(yàn)證碼,verifyToken()方法用于驗(yàn)證驗(yàn)證碼是否有效。

1.2.3 在Spring Security中配置Authy

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthyService authyService;

    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                    .antMatchers("/user/**").authenticated()
                    .antMatchers("/admin/**").hasAnyRole("ADMIN")
                    .and()
                .formLogin()
                    .and()
                .addFilterBefore(buildAuthyFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    private AuthyFilter buildAuthyFilter() throws Exception {
        final AuthyFilter filter = new AuthyFilter("/check-authy");
        filter.setAuthyService(authyService);
        return filter;
    }

}

在上述代碼中,創(chuàng)建了一個(gè)SecurityConfig類,并在其中定義了一個(gè)AuthyFilter過(guò)濾器用于添加二次認(rèn)證功能。在該過(guò)濾器中,我們通過(guò)setAuthyService()方法設(shè)置AuthyService的實(shí)例。

2 自己開(kāi)發(fā)二次認(rèn)證模塊

2.1 實(shí)現(xiàn)二次認(rèn)證功能

自己開(kāi)發(fā)二次認(rèn)證模塊意味著我們需要實(shí)現(xiàn)自己的TOTP算法。下面是一個(gè)簡(jiǎn)單的例子。

@Service
public class TotpService {

    private final static int WINDOW_SIZE = 3;
    private final static int CODE_DIGITS = 6;
    private final static String HMAC_ALGORITHM = "HmacSHA1";

    private static final int[] DIGITS_POWER
            = // 0 1  2   3    4     5      6       7        8
            {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };

    /** 獲取密鑰 **/
    public String createSecret() {
        final SecureRandom random = new SecureRandom();
        final byte[] bytes = new byte[64];
        random.nextBytes(bytes);
        return Base32Utils.encode(bytes);
    }

    /** 生成驗(yàn)證碼 **/
    public int generateCode(final String secret) throws Exception {
        final long timeIndex = System.currentTimeMillis() / 30000L; // 30秒
        final byte[] keyBytes = Base32Utils.decode(secret);
        final byte[] data = new byte[8];
        for (int i = 7; i >= 0; i--) {
            data[i] = (byte) (timeIndex & 0xff);
            timeIndex >>= 8;
        }
        final SecretKeySpec signingKey = new SecretKeySpec(keyBytes, HMAC_ALGORITHM);
        final Mac mac = Mac.getInstance(HMAC_ALGORITHM);
        mac.init(signingKey);
        final byte[] hmac = mac.doFinal(data);
        int offset = hmac[hmac.length - 1] & 0xf;
        int binCode = ((hmac[offset] & 0x7f) << 24)
                | ((hmac[offset + 1] & 0xff) << 16)
                | ((hmac[offset + 2] & 0xff) << 8)
                | (hmac[offset + 3] & 0xff);
        return binCode % DIGITS_POWER[CODE_DIGITS];
    }

    /** 驗(yàn)證身份 **/
    public boolean authorize(final String secret, final int otp, final int tolerance) throws Exception {
        final long timeIndex = System.currentTimeMillis() / 30000L; // 30秒
        final byte[] keyBytes = Base32Utils.decode(secret);
        for (int i = -tolerance; i <= tolerance; i++) {
            final long ti = timeIndex + i;
            final byte[] data = new byte[8];
            for (int j = 7; j >= 0; j--) {
                data[j] = (byte) (ti & 0xff);
                ti >>= 8;
            }
            final SecretKeySpec signingKey = new SecretKeySpec(keyBytes, HMAC_ALGORITHM);
            final Mac mac = Mac.getInstance(HMAC_ALGORITHM);
            mac.init(signingKey);
            final byte[] hmac = mac.doFinal(data);
            int offset = hmac[hmac.length - 1] & 0xf;
            int binCode = ((hmac[offset] & 0x7f) << 24)
                    | ((hmac[offset + 1] & 0xff) << 16)
                    | ((hmac[offset + 2] & 0xff) << 8)
                    | (hmac[offset + 3] & 0xff);
            if (binCode % DIGITS_POWER[CODE_DIGITS] == otp) {
                return true;
            }
        }
        return false;
    }

}

在上述代碼中創(chuàng)建了一個(gè)TotpService類,用于實(shí)現(xiàn)二次認(rèn)證功能。其中,createSecret()方法用于創(chuàng)建一個(gè)新的密鑰,generateCode()方法用于生成一個(gè)TOTP驗(yàn)證碼,authorize()方法用于驗(yàn)證TOTP是否有效。

2.2 在Spring Security中配置自己的二次認(rèn)證模塊

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private TotpService totpService;

    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                    .antMatchers("/user/**").authenticated()
                    .antMatchers("/admin/**").hasAnyRole("ADMIN")
                    .and()
                .formLogin()
                    .and()
                .addFilterBefore(buildTotpFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    private TotpFilter buildTotpFilter() throws Exception {
        final TotpFilter filter = new TotpFilter("/check-totp");
        filter.setTotpService(totpService);
        return filter;
    }

}

在上述代碼中創(chuàng)建了一個(gè)SecurityConfig類,并在其中定義了一個(gè)TotpFilter過(guò)濾器用于添加二次認(rèn)證功能。在該過(guò)濾器中,我們通過(guò)setTotpService()方法設(shè)置TotpService的實(shí)例。

三、實(shí)現(xiàn)過(guò)程

1 集成Google Authenticator

1.1 安裝并配置Google Authenticator

首先需要在服務(wù)器端安裝和配置Google Authenticator。具體步驟可以按照以下操作進(jìn)行:

  • 在Linux系統(tǒng)下,使用以下命令安裝Google Authenticator:

    sudo apt-get install libpam-google-authenticator -y
    
  • 創(chuàng)建Google Authenticator配置文件,使用以下命令:

    google-authenticator
    

    在進(jìn)行配置時(shí)需要解答一系列問(wèn)題,例如:

    • 你是否要對(duì)已映射的用戶強(qiáng)制啟用谷歌身份驗(yàn)證?
    • 你是否要對(duì)所有新用戶強(qiáng)制啟用谷歌身份驗(yàn)證?
    • 是否允許谷歌身份驗(yàn)證用于SSH登錄?
    • 是否允許網(wǎng)頁(yè)版的Google Authenticator?
    • 是否在身份驗(yàn)證時(shí)使用時(shí)間戳窗口?

    一般情況下可以選擇默認(rèn)值。最后會(huì)得到一個(gè)二維碼和一個(gè)密鑰,需要記錄下來(lái)用于后面的配置。

  • 配置PAM服務(wù),編輯/etc/pam.d/sshd文件,添加以下命令:

    auth required pam_google_authenticator.so nullok
    

    這個(gè)命令將強(qiáng)制啟用Google Authenticator身份驗(yàn)證并防止用戶跳過(guò)身份驗(yàn)證。如果你只想讓一部分用戶使用Google Authenticator進(jìn)行身份驗(yàn)證,可以使用下面的命令:

    auth [success=done new_authtok_reqd=done default=ignore] pam_google_authenticator.so nullok
    

1.2 在Spring Security中配置Google Authenticator

在將Google Authenticator與Spring Security集成時(shí)需要完成以下步驟:

  • 添加依賴

    我們需要在項(xiàng)目中添加以下依賴項(xiàng):

    <dependency>
        <groupId>com.warrenstrange</groupId>
        <artifactId>googleauth</artifactId>
        <version>0.8.1</version>
    </dependency>
    

    這是一個(gè)開(kāi)源的Google Authenticator實(shí)現(xiàn)庫(kù),我們將使用它來(lái)生成TOTP(基于時(shí)間的一次性密碼)。

  • 實(shí)現(xiàn)自定義的Spring Security過(guò)濾器

    我們需要自定義一個(gè)Spring Security過(guò)濾器,用于驗(yàn)證用戶的TOTP是否合法。過(guò)濾器的核心代碼如下所示:

    public class TotpFilter extends OncePerRequestFilter {
    
        // 處理Totp認(rèn)證請(qǐng)求的Handler
        private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler("/login?error");
    
        // 用于獲取用戶通過(guò)表單提交的Totp碼
        private String totpParameter = "totpCode";
    
        // 處理Totp認(rèn)證請(qǐng)求的URL
        private String authenticationUrl = "/authenticate/totp";
    
        // 用于生成Totp驗(yàn)證碼
        private GoogleAuthenticator gAuth;
    
        // 構(gòu)造函數(shù),傳入處理Totp認(rèn)證請(qǐng)求的URL
        public TotpFilter(String authenticationUrl) {
            gAuth = new GoogleAuthenticator();
            setFilterProcessesUrl(authenticationUrl);
        }
    
        @Override
        public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
            try {
                // 獲取表單中提交的Totp碼
                String totpCode = request.getParameter(totpParameter);
                if (StringUtils.isBlank(totpCode)) {
                    throw new TotpException("Totp code is missing.");
                }
    
                // 獲取當(dāng)前用戶
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                if (authentication == null || !(authentication.getPrincipal() instanceof User)) {
                    throw new TotpException("User not authenticated.");
                }
    
                // 生成Totp驗(yàn)證碼
                User user = (User) authentication.getPrincipal();
                int code = Integer.parseInt(totpCode);
                boolean isCodeValid = gAuth.authorize(user.getTotpSecret(), code);
                if (isCodeValid) {
                    // 驗(yàn)證通過(guò),繼續(xù)執(zhí)行過(guò)濾鏈
                    chain.doFilter(request, response);
                } else {
                    // 驗(yàn)證失敗,跳轉(zhuǎn)到錯(cuò)誤頁(yè)面
                    failureHandler.onAuthenticationFailure(request, response, new BadCredentialsException("Invalid totp code."));
                }
            } catch (TotpException e) {
                // 驗(yàn)證失敗,跳轉(zhuǎn)到錯(cuò)誤頁(yè)面
                failureHandler.onAuthenticationFailure(request, response, new BadCredentialsException(e.getMessage()));
            }
        }
    }
    

    這個(gè)過(guò)濾器的核心邏輯是從請(qǐng)求中獲取用戶提交的TOTP碼,并使用Google Authenticator庫(kù)驗(yàn)證TOTP碼是否合法。如果TOTP碼合法,則繼續(xù)執(zhí)行過(guò)濾器鏈,否則跳轉(zhuǎn)到錯(cuò)誤頁(yè)面。

  • 在Spring Security配置中注冊(cè)自定義過(guò)濾器

    我們需要在Spring Security配置中注冊(cè)自定義過(guò)濾器:

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .addFilterBefore(new TotpFilter("/authenticate/totp"), UsernamePasswordAuthenticationFilter.class)
                    .authorizeRequests()
                    .antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
                    .antMatchers("/admin/**").hasRole("ADMIN")
                    .anyRequest().authenticated()
                    .and()
                    .formLogin().defaultSuccessUrl("/home")
                    .and()
                    .logout().logoutSuccessUrl("/login?logout");
        }
    
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                    .withUser("user").password("password").roles("USER")
                    .and()
                    .withUser("admin").password("password").roles("ADMIN");
        }
    }
    

    在上述代碼中通過(guò)addFilterBefore()方法注冊(cè)了我們所實(shí)現(xiàn)的自定義Totp過(guò)濾器。這個(gè)過(guò)濾器將在Spring Security的UsernamePasswordAuthenticationFilter之前執(zhí)行,用于驗(yàn)證用戶輸入的TOTP碼是否合法。

1.3 測(cè)試Google Authenticator的集成

在進(jìn)行Google Authenticator集成測(cè)試時(shí)需要完成以下步驟:

  • 使用Google Authenticator手機(jī)應(yīng)用程序掃描生成二維碼的圖像。掃描后,你將獲得一個(gè)六位數(shù)字的二次認(rèn)證Totp碼。

  • 在登錄時(shí),除了用戶名和密碼外,還需要輸入TOTP碼。

  • 如果TOTP碼是有效的,將會(huì)自動(dòng)跳轉(zhuǎn)到指定的首頁(yè)。

2 集成Authy

2.1 注冊(cè)并配置Authy

首先需要在Authy網(wǎng)站上注冊(cè)一個(gè)賬戶。然后需要?jiǎng)?chuàng)建一個(gè)新的應(yīng)用程序,獲取應(yīng)用程序的Api Key和App Id,這兩個(gè)值后面需要在代碼中使用。

2.2 在Spring Security中配置Authy

在沒(méi)有使用Google Authenticator身份驗(yàn)證的情況下,Authy可以實(shí)現(xiàn)二次身份驗(yàn)證。我們可以按照以下步驟來(lái)將Authy與Spring Security集成:

  • 添加Authy SDK

    我們需要添加Authy的java SDK作為項(xiàng)目的依賴:

    <dependency>
        <groupId>com.authy</groupId>
        <artifactId>authy-java</artifactId>
        <version>3.0.0</version>
    </dependency>
    
  • 自定義Spring Security過(guò)濾器

    我們需要自定義一個(gè)Spring Security過(guò)濾器,用于驗(yàn)證用戶的Authy TOTP是否合法。過(guò)濾器的核心代碼如下所示:

     /**
      * 過(guò)濾器,用于校驗(yàn)Authy TOTP驗(yàn)證碼是否合法。
      */
     public class AuthyTotpFilter extends OncePerRequestFilter {
    
         // 處理Totp認(rèn)證請(qǐng)求的Handler
         private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler("/login?error");
    
         // 用于獲取用戶通過(guò)表單提交的Totp碼
         private String totpParameter = "totpCode";
    
         // 處理Authy認(rèn)證請(qǐng)求的URL
         private String authenticationUrl = "/authenticate/authy";
    
         // Authy SDK的實(shí)例
         private AuthyApiClient authyApiClient = new AuthyApiClient(
                 System.getProperty("authy.api.key"), System.getProperty("authy.api.url"));
    
         /**
          * 構(gòu)造函數(shù),需要傳入處理Totp認(rèn)證請(qǐng)求的URL。
          */
         public AuthyTotpFilter(String authenticationUrl) {
             setFilterProcessesUrl(authenticationUrl);
         }
    
         @Override
         protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
             try {
                 // 獲取表單中提交的Authy TOTP碼
                 String token = request.getParameter(totpParameter);
                 if (StringUtils.isBlank(token)) {
                     throw new AuthyException("Authy token is missing.");
                 }
    
                 // 獲取當(dāng)前用戶
                 Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                 if (authentication == null || !(authentication.getPrincipal() instanceof User)) {
                     throw new AuthyException("User not authenticated.");
                 }
    
                 // 獲取用戶綁定的Authy設(shè)備
                 User user = (User) authentication.getPrincipal();
                 String authyId = user.getAuthyId();
                 if (StringUtils.isBlank(authyId)) {
                     throw new AuthyException("Authy ID not found.");
                 }
    
                 // 驗(yàn)證Authy TOTP碼
                 StartTokenVerificationResponse response1 = authyApiClient.getTokens().verify(authyId, token, true);
                 if (response1.isValid()) {
                     // 認(rèn)證通過(guò),繼續(xù)執(zhí)行過(guò)濾鏈
                     filterChain.doFilter(request, response);
                 } else {
                     // 認(rèn)證失敗,跳轉(zhuǎn)到錯(cuò)誤頁(yè)面
                     failureHandler.onAuthenticationFailure(request, response, new BadCredentialsException("Invalid authy token."));
                 }
             } catch (AuthyException e) {
                 // 認(rèn)證失敗,跳轉(zhuǎn)到錯(cuò)誤頁(yè)面
                 failureHandler.onAuthenticationFailure(request, response, new BadCredentialsException(e.getMessage()));
             }
         }
     }
    

    這個(gè)過(guò)濾器的核心邏輯是從表單中獲取用戶的Authy的TOTP碼,并通過(guò)AuthyApiClient類進(jìn)行驗(yàn)證。如果驗(yàn)證通過(guò),則繼續(xù)執(zhí)行過(guò)濾器鏈,否則跳轉(zhuǎn)到錯(cuò)誤頁(yè)面。

  • 在Spring Security配置中注冊(cè)自定義過(guò)濾器

我們需要在Spring Security配置中注冊(cè)自定義的過(guò)濾器:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterBefore(new AuthyTotpFilter("/authenticate/authy"), UsernamePasswordAuthenticationFilter.class)
                .authorizeRequests()
                    .antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
                    .antMatchers("/admin/**").hasRole("ADMIN")
                    .anyRequest().authenticated()
                    .and()
                .formLogin().defaultSuccessUrl("/home")
                    .and()
                .logout().logoutSuccessUrl("/login?logout");
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("user").password("password").roles("USER")
                .and()
                .withUser("admin").password("password").roles("ADMIN");
    }
}

在上述代碼中通過(guò)addFilterBefore()方法注冊(cè)了我們所實(shí)現(xiàn)的自定義AuthyTotp過(guò)濾器。這個(gè)過(guò)濾器將在Spring Security的UsernamePasswordAuthenticationFilter之前執(zhí)行,用于驗(yàn)證用戶輸入的Authy TOTP碼是否合法。

2.3 測(cè)試Authy的集成

在進(jìn)行Authy集成測(cè)試時(shí)需要完成以下步驟:

  • 首先需要獲取用戶的Authy ID,這個(gè)ID是在用戶注冊(cè)時(shí)與Authy相關(guān)的。你可以通過(guò)短信、電話、電話回?fù)芎虯uthy手機(jī)應(yīng)用等途徑獲取這個(gè)ID。

  • 在Authy手機(jī)應(yīng)用中可以看到一個(gè)數(shù)字碼,用于確認(rèn)設(shè)備。輸入這個(gè)數(shù)字碼后,你可以得到一個(gè)六位數(shù)字的二次認(rèn)證Totp碼。

  • 在登錄時(shí)除了用戶名和密碼外還需要輸入Authy TOTP碼。

  • 如果Authy TOTP碼是有效的,將會(huì)自動(dòng)跳轉(zhuǎn)到指定的首頁(yè)。

3 自己開(kāi)發(fā)二次認(rèn)證模塊

當(dāng)系統(tǒng)需求與現(xiàn)有的二次認(rèn)證方式不匹配時(shí),我們可以自己開(kāi)發(fā)二次認(rèn)證模塊?,F(xiàn)在,我們打算實(shí)現(xiàn)一個(gè)基于手機(jī)短信的二次認(rèn)證模塊,并將其集成到一個(gè)基于Spring Security的Java Web應(yīng)用程序中。

3.1 實(shí)現(xiàn)基于手機(jī)短信的二次認(rèn)證

我們的基于手機(jī)短信的二次認(rèn)證模塊,需要完成以下任務(wù):

  • 接收用戶提交的手機(jī)號(hào)碼和用戶ID
  • 對(duì)用戶的手機(jī)號(hào)碼進(jìn)行驗(yàn)證,并向該手機(jī)號(hào)碼發(fā)送短信驗(yàn)證碼
  • 用戶提交短信驗(yàn)證碼,并進(jìn)行驗(yàn)證

我們使用Twilio API實(shí)現(xiàn)短信驗(yàn)證碼的發(fā)送和驗(yàn)證過(guò)程。Twilio是一個(gè)使用簡(jiǎn)單的云通訊平臺(tái),可以方便地發(fā)送短信和語(yǔ)音信息。我們可以使用Twilio提供的REST API來(lái)發(fā)送和驗(yàn)證短信驗(yàn)證碼,并在Java Web應(yīng)用程序中進(jìn)行封裝。

首先在網(wǎng)站上注冊(cè)賬號(hào)獲取API Key和API Secret,用于進(jìn)行短信驗(yàn)證碼的發(fā)送和驗(yàn)證。

其次在Java Web應(yīng)用程序中需要使用Twilio提供的Java庫(kù),引入以下代碼:

import com.twilio.Twilio;
import com.twilio.rest.api.v2010.account.Message;
import com.twilio.type.PhoneNumber;

public class TwilioSMSVerifier {
  private static final String TWILIO_ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
  private static final String TWILIO_AUTH_TOKEN = "your_auth_token";
  private static final String TWILIO_PHONE_NUMBER = "+1415XXXXXXX";

  public static void sendMessage(String toPhoneNumber, String message) {
    Twilio.init(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN);
    Message twilioMessage = Message.creator(
            new PhoneNumber(toPhoneNumber),
            new PhoneNumber(TWILIO_PHONE_NUMBER),
            message
    ).create();
  }

  public static boolean verifyCode(String toPhoneNumber, String code) {
    // 進(jìn)行驗(yàn)證碼驗(yàn)證...
    return true;
  }
}

這里我們引入了Twilio庫(kù),并定義了發(fā)送短信和驗(yàn)證驗(yàn)證碼的方法。請(qǐng)注意,這里的TWILIO_ACCOUNT_SIDTWILIO_AUTH_TOKEN需要替換為您自己的Twilio賬戶信息。TWILIO_PHONE_NUMBER是您的Twilio賬戶綁定的電話號(hào)碼。

接下來(lái)實(shí)現(xiàn)sendMessage方法,調(diào)用Twilio API發(fā)送短信驗(yàn)證碼:

public static void sendMessage(String toPhoneNumber, String message) {
    Twilio.init(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN);
    Message twilioMessage = Message.creator(
            new PhoneNumber(toPhoneNumber),
            new PhoneNumber(TWILIO_PHONE_NUMBER),
            message
    ).create();

    System.out.println("Twilio message SID: " + twilioMessage.getSid());
}

這里使用Twilio提供的Message對(duì)象進(jìn)行短信驗(yàn)證碼的發(fā)送。我們使用PhoneNumber對(duì)象來(lái)表示電話號(hào)碼,在Message構(gòu)造函數(shù)中指定了tofrom電話號(hào)碼,以及需發(fā)送的消息內(nèi)容message。發(fā)送后,我們通過(guò)System.out輸出Twilio消息的SID。

接下來(lái)實(shí)現(xiàn)verifyCode方法,驗(yàn)證用戶提交的短信驗(yàn)證碼:

public static boolean verifyCode(String toPhoneNumber, String code) {
    Twilio.init(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN);

    List<Message> messages = Message.reader()
            .setTo(new PhoneNumber(toPhoneNumber))
            .read();

    for (Message message : messages) {
        String body = message.getBody();
        if (body.contains(code)) {
            return true;
        }
    }

    return false;
}

這里使用Twilio提供的Message.reader對(duì)象,查詢?cè)撌謾C(jī)號(hào)碼的短信信息。我們遍歷查詢結(jié)果,判斷消息正文是否包含提交的驗(yàn)證碼。如果存在,則返回true,否則返回false

3.2 在Spring Security中配置自己開(kāi)發(fā)的二次認(rèn)證模塊

現(xiàn)在已經(jīng)完成了基于手機(jī)短信的二次認(rèn)證模塊的開(kāi)發(fā)。接下來(lái),我們將其集成到基于Spring Security的Java Web應(yīng)用程序中。為此,我們需要在Spring Security中添加一個(gè)新的認(rèn)證提供程序,并將我們的驗(yàn)證碼驗(yàn)證邏輯集成在其中。

我們可以通過(guò)繼承AbstractUserDetailsAuthenticationProvider,實(shí)現(xiàn)自己的認(rèn)證方式。我們的認(rèn)證提供程序,需要完成以下任務(wù):

  • 在進(jìn)行認(rèn)證時(shí),獲取用戶提交的手機(jī)號(hào)碼和用戶ID,并向該手機(jī)號(hào)碼發(fā)送驗(yàn)證碼
  • 接收用戶提交的短信驗(yàn)證碼,并驗(yàn)證其正確性

下面是我們自己開(kāi)發(fā)的二次認(rèn)證提供程序的Java代碼:

import java.util.ArrayList;
import java.util.List;

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public class SMSAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
  private UserDetailsService userDetailsService;
  private int codeLength; // 驗(yàn)證碼長(zhǎng)度
  private int codeExpiration; // 驗(yàn)證碼有效期

  public SMSAuthenticationProvider(UserDetailsService userDetailsService, int codeLength,
      int codeExpiration) {
    this.userDetailsService = userDetailsService;
    this.codeLength = codeLength;
    this.codeExpiration = codeExpiration;
  }

  @Override
  protected void additionalAuthenticationChecks(UserDetails userDetails,
      UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    String code = authentication.getCredentials().toString();
    SMSUserDetails smsUser = (SMSUserDetails) userDetails;
    if (!TwilioSMSVerifier.verifyCode(smsUser.getMobile(), code)) {
      throw new BadCredentialsException("Invalid verification code");
    }
  }

  @Override
  protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
      throws AuthenticationException {
    String mobile = (String) authentication.getDetails();
    UserDetails loadedUser;
    try {
      loadedUser = userDetailsService.loadUserByUsername(username);
    } catch (UsernameNotFoundException notFound) {
      throw new BadCredentialsException("Account not found");
    }
    return new SMSUserDetails(loadedUser.getUsername(), loadedUser.getPassword(), mobile,
        loadedUser.getAuthorities());
  }

  @Override
  public boolean supports(Class<?> authentication) {
    return authentication.equals(UsernamePasswordAuthenticationToken.class);
  }

  public class SMSUserDetails implements UserDetails {
    private final String username;
    private final String password;
    private final String mobile;
    private final List<GrantedAuthority> authorities;

    public SMSUserDetails(String username, String password, String mobile, List<GrantedAuthority> authorities) {
      this.username = username;
      this.password = password;
      this.mobile = mobile;
      this.authorities = new ArrayList<>(authorities);
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
      return authorities;
    }

    @Override
    public String getPassword() {
      return password;
    }

    @Override
    public String getUsername() {
      return username;
    }

    public String getMobile() {
      return mobile;
    }

    @Override
    public boolean isAccountNonExpired() {
      return true;
    }

    @Override
    public boolean isAccountNonLocked() {
      return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
      return true;
    }

    @Override
    public boolean isEnabled() {
      return true;
    }
  }
}

首先定義了一個(gè)描述用戶詳情的內(nèi)部類SMSUserDetails,用于存儲(chǔ)用戶ID、密碼、手機(jī)號(hào)碼和權(quán)限等信息。在retrieveUser方法中,我們首先獲取用戶提交的手機(jī)號(hào)碼,然后調(diào)用userDetailsService獲取用戶信息。最后,我們返回SMSUserDetails對(duì)象,封裝用戶詳情。需要注意的是,SMSUserDetails必須實(shí)現(xiàn)UserDetails接口,以保證在之后的認(rèn)證過(guò)程中能夠正確地獲取用戶信息。

additionalAuthenticationChecks中,我們使用TwilioSMSVerifier對(duì)象驗(yàn)證用戶提交的驗(yàn)證碼。在這里,我們獲取authentication.getCredentials(),即用戶提交的驗(yàn)證碼,進(jìn)行驗(yàn)證。如果認(rèn)證不通過(guò),我們則拋出一個(gè)BadCredentialsException,表示認(rèn)證失敗。

最后在supports方法中返回支持的認(rèn)證類型。這里,我們只支持UsernamePasswordAuthenticationToken類型的認(rèn)證。如果使用其他類型的認(rèn)證對(duì)象,Spring Security將會(huì)拋出異常。

3.3 測(cè)試自己開(kāi)發(fā)的二次認(rèn)證模塊的集成

現(xiàn)在已經(jīng)完成了基于手機(jī)短信的二次認(rèn)證模塊的開(kāi)發(fā),并將其集成到了基于Spring Security的Java Web應(yīng)用程序中。接下來(lái),我們將會(huì)對(duì)這個(gè)模塊進(jìn)行測(cè)試,驗(yàn)證其是否可以正常工作。

首先需要使用Twilio API向我們自己的手機(jī)號(hào)碼發(fā)送一條測(cè)試短信。我們可以使用以下代碼:

TwilioSMSVerifier.sendMessage("+8612345678901", "Your verification code is: 123456");

在發(fā)送短信后可以使用以下代碼,在Spring Security中進(jìn)行認(rèn)證:

@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(HttpServletRequest request) {
  String username = request.getParameter("username");
  String password = request.getParameter("password");
  String mobile = request.getParameter("mobile");
  String code = request.getParameter("code");

  UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
      username, password, Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")));
  authRequest.setDetails(mobile);

  Authentication authentication = authManager.authenticate(authRequest);
  SecurityContextHolder.getContext().setAuthentication(authentication);

  return "redirect:/home";
}

這里首先獲取用戶提交的認(rèn)證信息,包括用戶名、密碼、手機(jī)號(hào)碼和短信驗(yàn)證碼。接下來(lái),我們創(chuàng)建一個(gè)UsernamePasswordAuthenticationToken對(duì)象,并設(shè)置其類型為ROLE_USER。我們將手機(jī)號(hào)碼設(shè)置為該對(duì)象的詳情信息。最后,我們使用authManager進(jìn)行認(rèn)證,并將認(rèn)證結(jié)果保存在SecurityContextHolder中。

現(xiàn)在已經(jīng)完成了自己開(kāi)發(fā)的二次認(rèn)證模塊的集成測(cè)試。需要注意的是需要使用真實(shí)手機(jī)號(hào)碼進(jìn)行測(cè)試,以確保短信驗(yàn)證碼可以正常發(fā)送和驗(yàn)證。如果您沒(méi)有真實(shí)手機(jī)號(hào)碼,可以注冊(cè)一個(gè)Twilio賬號(hào),并使用Twilio提供的測(cè)試號(hào)碼進(jìn)行測(cè)試。

四、小結(jié)回顧

在本篇文章中詳細(xì)介紹了二次認(rèn)證的概念以及其優(yōu)缺點(diǎn)。二次認(rèn)證可以通過(guò)多種方式實(shí)現(xiàn)包括基于硬件令牌、短信驗(yàn)證碼和移動(dòng)應(yīng)用等。不同的實(shí)現(xiàn)方式各有優(yōu)缺點(diǎn),需要按照實(shí)際情況進(jìn)行選擇。

其次使用Spring Security演示了如何在一個(gè)基于Web的應(yīng)用程序中添加二次認(rèn)證。通過(guò)使用Spring Security可以輕松地將一個(gè)二次認(rèn)證模塊與我們的應(yīng)用程序集成。在這個(gè)過(guò)程中了解如何配置Spring Security以及如何使用Spring Security提供的安全特性。

最后還討論了一些可能的問(wèn)題以及解決方案。例如,提到了可能會(huì)遇到與移動(dòng)設(shè)備兼容性和惡意攻擊等問(wèn)題。為了解決這些問(wèn)題提供了一些方法和建議,例如使用較新的移動(dòng)設(shè)備和實(shí)施防范措施等。

希望大家能夠了解二次認(rèn)證的主要概念以及如何使用Spring Security實(shí)現(xiàn)二次認(rèn)證。同時(shí)也希望大家能夠了解二次認(rèn)證可能遇到的挑戰(zhàn),并能夠選擇適當(dāng)?shù)慕鉀Q方案。

4.1 二次認(rèn)證的優(yōu)缺點(diǎn)

二次認(rèn)證可以提供額外的安全保障,增強(qiáng)用戶賬號(hào)的安全性。通過(guò)二次認(rèn)證,用戶需要在登錄時(shí)進(jìn)行額外的驗(yàn)證,使得其他人無(wú)法直接登錄到該賬號(hào)中。尤其是在一些關(guān)鍵應(yīng)用場(chǎng)景中,例如銀行賬戶和電子商務(wù)平臺(tái),二次認(rèn)證可以提供更加可靠的賬號(hào)保護(hù)。

雖然二次認(rèn)證可以提供更高的安全保障,但它也可能會(huì)影響系統(tǒng)的便捷性和易用性。尤其是在一些需要頻繁登錄的應(yīng)用中,例如互聯(lián)網(wǎng)社交應(yīng)用和游戲應(yīng)用,過(guò)多的驗(yàn)證步驟可能會(huì)導(dǎo)致用戶反感。此外二次認(rèn)證實(shí)現(xiàn)的復(fù)雜度也可能會(huì)造成不必要的負(fù)擔(dān)和成本。

因此在實(shí)踐中需要根據(jù)實(shí)際情況進(jìn)行選擇。我們可以在關(guān)鍵應(yīng)用場(chǎng)景中使用二次認(rèn)證,同時(shí)在其他應(yīng)用場(chǎng)景中采用其他的驗(yàn)證方式,以提供更好的用戶體驗(yàn)和系統(tǒng)安全性的平衡。

4.2 Spring Security添加二次認(rèn)證的效果

通過(guò)使用Spring Security可以輕松地將二次認(rèn)證模塊集成到我們的應(yīng)用程序中。可以使用Spring Security提供的二次認(rèn)證過(guò)濾器,并按照要求配置我們的認(rèn)證方式。在進(jìn)行認(rèn)證時(shí)可以使用Spring Security提供的許多安全特性,以保護(hù)我們的應(yīng)用程序不受惡意攻擊。

同時(shí)通過(guò)Spring Security還可以輕松地實(shí)現(xiàn)其他的安全特性,例如用戶角色控制、會(huì)話管理和密碼加密等。這樣,我們可以構(gòu)建一個(gè)更加健壯和安全的應(yīng)用程序,并幫助用戶獲得更好的體驗(yàn)和信任感。

4.3 可能的問(wèn)題及解決方案

在實(shí)踐中能會(huì)遇到一些問(wèn)題如移動(dòng)設(shè)備兼容性、短信驗(yàn)證碼實(shí)現(xiàn)和惡意攻擊等。為了解決這些問(wèn)題,我們可以采取一些措施例如:

  • 選擇較新版本設(shè)備。隨著移動(dòng)設(shè)備技術(shù)的不斷更新,較新的設(shè)備可能會(huì)更加支持多種驗(yàn)證方式,例如指紋識(shí)別和面部識(shí)別等。

  • 使用多個(gè)驗(yàn)證碼驗(yàn)證方式。我們可以使用多種驗(yàn)證碼驗(yàn)證方式,例如郵件驗(yàn)證碼、電話驗(yàn)證碼和圖形驗(yàn)證碼等,以提高安全性。

  • 實(shí)施防范措施。我們可以通過(guò)使用Web防火墻和入侵檢測(cè)系統(tǒng)等安全技術(shù),來(lái)防范惡意攻擊和入侵行為。

總之通過(guò)有效地管理和保護(hù)我們的應(yīng)用程序可以提供更高質(zhì)量的用戶體驗(yàn)和安全性。通過(guò)Spring Security,我們可以輕松地添加二次認(rèn)證模塊,并讓我們的應(yīng)用程序更加可靠和安全。

到此這篇關(guān)于Spring Security添加二次認(rèn)證的項(xiàng)目實(shí)踐的文章就介紹到這了,更多相關(guān)Spring Security二次認(rèn)證內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 在SpringBoot中實(shí)現(xiàn)一個(gè)訂單號(hào)生成系統(tǒng)的示例代碼

    在SpringBoot中實(shí)現(xiàn)一個(gè)訂單號(hào)生成系統(tǒng)的示例代碼

    在Spring Boot中設(shè)計(jì)一個(gè)訂單號(hào)生成系統(tǒng),主要考慮到生成的訂單號(hào)需要滿足的幾個(gè)要求:唯一性、可擴(kuò)展性、以及可能的業(yè)務(wù)相關(guān)性,本文給大家介紹了幾種常見(jiàn)的解決方案及相應(yīng)的示例代碼,需要的朋友可以參考下
    2024-02-02
  • java Socket簡(jiǎn)易聊天工具

    java Socket簡(jiǎn)易聊天工具

    這篇文章主要為大家詳細(xì)介紹了java Socket簡(jiǎn)易聊天工具,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • Kotlin中?StateFlow?或?SharedFlow?的區(qū)別解析

    Kotlin中?StateFlow?或?SharedFlow?的區(qū)別解析

    Kotlin協(xié)程中的StateFlow和SharedFlow是響應(yīng)式數(shù)據(jù)流,分別用于UI狀態(tài)管理和事件通知,StateFlow有初始值,只保留最新值,適用于UI狀態(tài)管理;SharedFlow沒(méi)有初始值,可以配置緩存大小,適用于事件通知,感興趣的朋友一起看看吧
    2025-03-03
  • SpringSecurity微服務(wù)實(shí)戰(zhàn)之公共模塊詳解

    SpringSecurity微服務(wù)實(shí)戰(zhàn)之公共模塊詳解

    這篇文章主要為大家介紹了SpringSecurity微服務(wù)實(shí)戰(zhàn)之公共模塊詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • java 中enum的使用方法詳解

    java 中enum的使用方法詳解

    這篇文章主要介紹了java 中enum的使用方法詳解的相關(guān)資料,希望通過(guò)本文能幫助到大家,理解掌握java 中enum的使用方法,需要的朋友可以參考下
    2017-09-09
  • 詳解MyBatis中column屬性的總結(jié)

    詳解MyBatis中column屬性的總結(jié)

    在MyBatis的映射中有column這么一個(gè)屬性,我一直以為它映射的是數(shù)據(jù)庫(kù)表中的列名,但經(jīng)過(guò)學(xué)習(xí)發(fā)現(xiàn)他似乎映射的是SQL語(yǔ)句中的列名,或者說(shuō)是查詢結(jié)果所得到的表的列名,這篇文章主要介紹了MyBatis中column屬性的總結(jié),需要的朋友可以參考下
    2022-09-09
  • 詳解如何用spring Restdocs創(chuàng)建API文檔

    詳解如何用spring Restdocs創(chuàng)建API文檔

    這篇文章將帶你了解如何用spring官方推薦的restdoc去生成api文檔。具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • Java通過(guò)Fork/Join優(yōu)化并行計(jì)算

    Java通過(guò)Fork/Join優(yōu)化并行計(jì)算

    這篇文章主要為大家詳細(xì)介紹了Java通過(guò)Fork、Join來(lái)優(yōu)化并行計(jì)算,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-04-04
  • maven 配置多個(gè)倉(cāng)庫(kù)的方法

    maven 配置多個(gè)倉(cāng)庫(kù)的方法

    這篇文章主要介紹了maven 配置多個(gè)倉(cāng)庫(kù)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • Java實(shí)現(xiàn)圖書(shū)借閱系統(tǒng)

    Java實(shí)現(xiàn)圖書(shū)借閱系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)圖書(shū)借閱系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03

最新評(píng)論