Spring Security添加二次認證的項目實踐
一、 簡介
1 Spring Security概述
Spring Security是一個基于Spring框架的安全框架,用于為Java應(yīng)用程序提供身份驗證和授權(quán)服務(wù)。
2 二次認證的必要性
傳統(tǒng)的用戶名和密碼驗證方式存在被破解的風(fēng)險,因此在用戶登錄后需要進行二次認證,增強身份驗證的安全性。
二、 Spring Security實現(xiàn)二次認證的方式
1 使用已有二次認證服務(wù)
1.1 集成Google Authenticator
Google Authenticator是一款基于TOTP算法的開源軟件,用戶可以將其安裝在智能手機上,以便進行二次認證。Spring Security可以通過Google Authenticator進行二次認證。下面是一個簡單的示例。
1.1.1 在pom.xml中添加依賴
<dependency> <groupId>com.warrenstrange</groupId> <artifactId>googleauth</artifactId> <version>1.0.0</version> </dependency>
1.1.2 實現(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(); } /** 驗證身份 **/ 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)建了一個GoogleAuthenticatorService類,用于實現(xiàn)Google Authenticator的相關(guān)功能。其中,createSecret()方法用于創(chuàng)建一個新的密鑰,authorize()方法用于驗證一個TOTP是否有效,getQR()方法用于生成一個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)建了一個SecurityConfig類,并在其中定義了一個GoogleAuthFilter過濾器用于添加二次認證功能。在該過濾器中,我們通過setSecretProvider()方法獲取用戶的密鑰,然后通過setGoogleAuthenticator()方法設(shè)置Google Authenticator的實例。
1.2 集成Authy
Authy是一款基于TOTP算法的二次認證服務(wù)提供商,可以為Spring Security提供二次認證服務(wù)。下面是一個簡單的示例。
1.2.1 在pom.xml中添加依賴
<dependency> <groupId>com.authy</groupId> <artifactId>authy-client</artifactId> <version>1.2</version> </dependency>
1.2.2 實現(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); /** 注冊用戶 **/ 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ā)送驗證碼 **/ public void sendVerification(final String userId, final String via) throws Exception { final Tokens tokens = authyApiClient.getTokens(); tokens.requestSms(Integer.valueOf(userId), via); } /** 驗證驗證碼 **/ 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)建了一個AuthyService類,用于與Authy客戶端進行交互。其中,createUser()方法用于注冊一個新用戶,sendVerification()方法用于向用戶發(fā)送驗證碼,verifyToken()方法用于驗證驗證碼是否有效。
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)建了一個SecurityConfig類,并在其中定義了一個AuthyFilter過濾器用于添加二次認證功能。在該過濾器中,我們通過setAuthyService()方法設(shè)置AuthyService的實例。
2 自己開發(fā)二次認證模塊
2.1 實現(xiàn)二次認證功能
自己開發(fā)二次認證模塊意味著我們需要實現(xiàn)自己的TOTP算法。下面是一個簡單的例子。
@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); } /** 生成驗證碼 **/ 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]; } /** 驗證身份 **/ 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)建了一個TotpService類,用于實現(xiàn)二次認證功能。其中,createSecret()方法用于創(chuàng)建一個新的密鑰,generateCode()方法用于生成一個TOTP驗證碼,authorize()方法用于驗證TOTP是否有效。
2.2 在Spring Security中配置自己的二次認證模塊
@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)建了一個SecurityConfig類,并在其中定義了一個TotpFilter過濾器用于添加二次認證功能。在該過濾器中,我們通過setTotpService()方法設(shè)置TotpService的實例。
三、實現(xiàn)過程
1 集成Google Authenticator
1.1 安裝并配置Google Authenticator
首先需要在服務(wù)器端安裝和配置Google Authenticator。具體步驟可以按照以下操作進行:
在Linux系統(tǒng)下,使用以下命令安裝Google Authenticator:
sudo apt-get install libpam-google-authenticator -y
創(chuàng)建Google Authenticator配置文件,使用以下命令:
google-authenticator
在進行配置時需要解答一系列問題,例如:
- 你是否要對已映射的用戶強制啟用谷歌身份驗證?
- 你是否要對所有新用戶強制啟用谷歌身份驗證?
- 是否允許谷歌身份驗證用于SSH登錄?
- 是否允許網(wǎng)頁版的Google Authenticator?
- 是否在身份驗證時使用時間戳窗口?
一般情況下可以選擇默認值。最后會得到一個二維碼和一個密鑰,需要記錄下來用于后面的配置。
配置PAM服務(wù),編輯/etc/pam.d/sshd文件,添加以下命令:
auth required pam_google_authenticator.so nullok
這個命令將強制啟用Google Authenticator身份驗證并防止用戶跳過身份驗證。如果你只想讓一部分用戶使用Google Authenticator進行身份驗證,可以使用下面的命令:
auth [success=done new_authtok_reqd=done default=ignore] pam_google_authenticator.so nullok
1.2 在Spring Security中配置Google Authenticator
在將Google Authenticator與Spring Security集成時需要完成以下步驟:
添加依賴
我們需要在項目中添加以下依賴項:
<dependency> <groupId>com.warrenstrange</groupId> <artifactId>googleauth</artifactId> <version>0.8.1</version> </dependency>
這是一個開源的Google Authenticator實現(xiàn)庫,我們將使用它來生成TOTP(基于時間的一次性密碼)。
實現(xiàn)自定義的Spring Security過濾器
我們需要自定義一個Spring Security過濾器,用于驗證用戶的TOTP是否合法。過濾器的核心代碼如下所示:
public class TotpFilter extends OncePerRequestFilter { // 處理Totp認證請求的Handler private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler("/login?error"); // 用于獲取用戶通過表單提交的Totp碼 private String totpParameter = "totpCode"; // 處理Totp認證請求的URL private String authenticationUrl = "/authenticate/totp"; // 用于生成Totp驗證碼 private GoogleAuthenticator gAuth; // 構(gòu)造函數(shù),傳入處理Totp認證請求的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驗證碼 User user = (User) authentication.getPrincipal(); int code = Integer.parseInt(totpCode); boolean isCodeValid = gAuth.authorize(user.getTotpSecret(), code); if (isCodeValid) { // 驗證通過,繼續(xù)執(zhí)行過濾鏈 chain.doFilter(request, response); } else { // 驗證失敗,跳轉(zhuǎn)到錯誤頁面 failureHandler.onAuthenticationFailure(request, response, new BadCredentialsException("Invalid totp code.")); } } catch (TotpException e) { // 驗證失敗,跳轉(zhuǎn)到錯誤頁面 failureHandler.onAuthenticationFailure(request, response, new BadCredentialsException(e.getMessage())); } } }
這個過濾器的核心邏輯是從請求中獲取用戶提交的TOTP碼,并使用Google Authenticator庫驗證TOTP碼是否合法。如果TOTP碼合法,則繼續(xù)執(zhí)行過濾器鏈,否則跳轉(zhuǎn)到錯誤頁面。
在Spring Security配置中注冊自定義過濾器
我們需要在Spring Security配置中注冊自定義過濾器:
@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"); } }
在上述代碼中通過addFilterBefore()方法注冊了我們所實現(xiàn)的自定義Totp過濾器。這個過濾器將在Spring Security的UsernamePasswordAuthenticationFilter之前執(zhí)行,用于驗證用戶輸入的TOTP碼是否合法。
1.3 測試Google Authenticator的集成
在進行Google Authenticator集成測試時需要完成以下步驟:
使用Google Authenticator手機應(yīng)用程序掃描生成二維碼的圖像。掃描后,你將獲得一個六位數(shù)字的二次認證Totp碼。
在登錄時,除了用戶名和密碼外,還需要輸入TOTP碼。
如果TOTP碼是有效的,將會自動跳轉(zhuǎn)到指定的首頁。
2 集成Authy
2.1 注冊并配置Authy
首先需要在Authy網(wǎng)站上注冊一個賬戶。然后需要創(chuàng)建一個新的應(yīng)用程序,獲取應(yīng)用程序的Api Key和App Id,這兩個值后面需要在代碼中使用。
2.2 在Spring Security中配置Authy
在沒有使用Google Authenticator身份驗證的情況下,Authy可以實現(xiàn)二次身份驗證。我們可以按照以下步驟來將Authy與Spring Security集成:
添加Authy SDK
我們需要添加Authy的java SDK作為項目的依賴:
<dependency> <groupId>com.authy</groupId> <artifactId>authy-java</artifactId> <version>3.0.0</version> </dependency>
自定義Spring Security過濾器
我們需要自定義一個Spring Security過濾器,用于驗證用戶的Authy TOTP是否合法。過濾器的核心代碼如下所示:
/** * 過濾器,用于校驗Authy TOTP驗證碼是否合法。 */ public class AuthyTotpFilter extends OncePerRequestFilter { // 處理Totp認證請求的Handler private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler("/login?error"); // 用于獲取用戶通過表單提交的Totp碼 private String totpParameter = "totpCode"; // 處理Authy認證請求的URL private String authenticationUrl = "/authenticate/authy"; // Authy SDK的實例 private AuthyApiClient authyApiClient = new AuthyApiClient( System.getProperty("authy.api.key"), System.getProperty("authy.api.url")); /** * 構(gòu)造函數(shù),需要傳入處理Totp認證請求的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."); } // 驗證Authy TOTP碼 StartTokenVerificationResponse response1 = authyApiClient.getTokens().verify(authyId, token, true); if (response1.isValid()) { // 認證通過,繼續(xù)執(zhí)行過濾鏈 filterChain.doFilter(request, response); } else { // 認證失敗,跳轉(zhuǎn)到錯誤頁面 failureHandler.onAuthenticationFailure(request, response, new BadCredentialsException("Invalid authy token.")); } } catch (AuthyException e) { // 認證失敗,跳轉(zhuǎn)到錯誤頁面 failureHandler.onAuthenticationFailure(request, response, new BadCredentialsException(e.getMessage())); } } }
這個過濾器的核心邏輯是從表單中獲取用戶的Authy的TOTP碼,并通過AuthyApiClient類進行驗證。如果驗證通過,則繼續(xù)執(zhí)行過濾器鏈,否則跳轉(zhuǎn)到錯誤頁面。
在Spring Security配置中注冊自定義過濾器
我們需要在Spring Security配置中注冊自定義的過濾器:
@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"); } }
在上述代碼中通過addFilterBefore()方法注冊了我們所實現(xiàn)的自定義AuthyTotp過濾器。這個過濾器將在Spring Security的UsernamePasswordAuthenticationFilter之前執(zhí)行,用于驗證用戶輸入的Authy TOTP碼是否合法。
2.3 測試Authy的集成
在進行Authy集成測試時需要完成以下步驟:
首先需要獲取用戶的Authy ID,這個ID是在用戶注冊時與Authy相關(guān)的。你可以通過短信、電話、電話回撥和Authy手機應(yīng)用等途徑獲取這個ID。
在Authy手機應(yīng)用中可以看到一個數(shù)字碼,用于確認設(shè)備。輸入這個數(shù)字碼后,你可以得到一個六位數(shù)字的二次認證Totp碼。
在登錄時除了用戶名和密碼外還需要輸入Authy TOTP碼。
如果Authy TOTP碼是有效的,將會自動跳轉(zhuǎn)到指定的首頁。
3 自己開發(fā)二次認證模塊
當(dāng)系統(tǒng)需求與現(xiàn)有的二次認證方式不匹配時,我們可以自己開發(fā)二次認證模塊。現(xiàn)在,我們打算實現(xiàn)一個基于手機短信的二次認證模塊,并將其集成到一個基于Spring Security的Java Web應(yīng)用程序中。
3.1 實現(xiàn)基于手機短信的二次認證
我們的基于手機短信的二次認證模塊,需要完成以下任務(wù):
- 接收用戶提交的手機號碼和用戶ID
- 對用戶的手機號碼進行驗證,并向該手機號碼發(fā)送短信驗證碼
- 用戶提交短信驗證碼,并進行驗證
我們使用Twilio API實現(xiàn)短信驗證碼的發(fā)送和驗證過程。Twilio是一個使用簡單的云通訊平臺,可以方便地發(fā)送短信和語音信息。我們可以使用Twilio提供的REST API來發(fā)送和驗證短信驗證碼,并在Java Web應(yīng)用程序中進行封裝。
首先在網(wǎng)站上注冊賬號獲取API Key和API Secret,用于進行短信驗證碼的發(fā)送和驗證。
其次在Java Web應(yīng)用程序中需要使用Twilio提供的Java庫,引入以下代碼:
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) { // 進行驗證碼驗證... return true; } }
這里我們引入了Twilio庫,并定義了發(fā)送短信和驗證驗證碼的方法。請注意,這里的TWILIO_ACCOUNT_SID
和TWILIO_AUTH_TOKEN
需要替換為您自己的Twilio賬戶信息。TWILIO_PHONE_NUMBER
是您的Twilio賬戶綁定的電話號碼。
接下來實現(xiàn)sendMessage
方法,調(diào)用Twilio API發(fā)送短信驗證碼:
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
對象進行短信驗證碼的發(fā)送。我們使用PhoneNumber
對象來表示電話號碼,在Message
構(gòu)造函數(shù)中指定了to
和from
電話號碼,以及需發(fā)送的消息內(nèi)容message
。發(fā)送后,我們通過System.out
輸出Twilio消息的SID。
接下來實現(xiàn)verifyCode
方法,驗證用戶提交的短信驗證碼:
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
對象,查詢該手機號碼的短信信息。我們遍歷查詢結(jié)果,判斷消息正文是否包含提交的驗證碼。如果存在,則返回true
,否則返回false
。
3.2 在Spring Security中配置自己開發(fā)的二次認證模塊
現(xiàn)在已經(jīng)完成了基于手機短信的二次認證模塊的開發(fā)。接下來,我們將其集成到基于Spring Security的Java Web應(yīng)用程序中。為此,我們需要在Spring Security中添加一個新的認證提供程序,并將我們的驗證碼驗證邏輯集成在其中。
我們可以通過繼承AbstractUserDetailsAuthenticationProvider
,實現(xiàn)自己的認證方式。我們的認證提供程序,需要完成以下任務(wù):
- 在進行認證時,獲取用戶提交的手機號碼和用戶ID,并向該手機號碼發(fā)送驗證碼
- 接收用戶提交的短信驗證碼,并驗證其正確性
下面是我們自己開發(fā)的二次認證提供程序的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; // 驗證碼長度 private int codeExpiration; // 驗證碼有效期 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; } } }
首先定義了一個描述用戶詳情的內(nèi)部類SMSUserDetails
,用于存儲用戶ID、密碼、手機號碼和權(quán)限等信息。在retrieveUser
方法中,我們首先獲取用戶提交的手機號碼,然后調(diào)用userDetailsService
獲取用戶信息。最后,我們返回SMSUserDetails
對象,封裝用戶詳情。需要注意的是,SMSUserDetails
必須實現(xiàn)UserDetails
接口,以保證在之后的認證過程中能夠正確地獲取用戶信息。
在additionalAuthenticationChecks
中,我們使用TwilioSMSVerifier
對象驗證用戶提交的驗證碼。在這里,我們獲取authentication.getCredentials()
,即用戶提交的驗證碼,進行驗證。如果認證不通過,我們則拋出一個BadCredentialsException
,表示認證失敗。
最后在supports
方法中返回支持的認證類型。這里,我們只支持UsernamePasswordAuthenticationToken
類型的認證。如果使用其他類型的認證對象,Spring Security將會拋出異常。
3.3 測試自己開發(fā)的二次認證模塊的集成
現(xiàn)在已經(jīng)完成了基于手機短信的二次認證模塊的開發(fā),并將其集成到了基于Spring Security的Java Web應(yīng)用程序中。接下來,我們將會對這個模塊進行測試,驗證其是否可以正常工作。
首先需要使用Twilio API向我們自己的手機號碼發(fā)送一條測試短信。我們可以使用以下代碼:
TwilioSMSVerifier.sendMessage("+8612345678901", "Your verification code is: 123456");
在發(fā)送短信后可以使用以下代碼,在Spring Security中進行認證:
@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"; }
這里首先獲取用戶提交的認證信息,包括用戶名、密碼、手機號碼和短信驗證碼。接下來,我們創(chuàng)建一個UsernamePasswordAuthenticationToken
對象,并設(shè)置其類型為ROLE_USER
。我們將手機號碼設(shè)置為該對象的詳情信息。最后,我們使用authManager
進行認證,并將認證結(jié)果保存在SecurityContextHolder
中。
現(xiàn)在已經(jīng)完成了自己開發(fā)的二次認證模塊的集成測試。需要注意的是需要使用真實手機號碼進行測試,以確保短信驗證碼可以正常發(fā)送和驗證。如果您沒有真實手機號碼,可以注冊一個Twilio賬號,并使用Twilio提供的測試號碼進行測試。
四、小結(jié)回顧
在本篇文章中詳細介紹了二次認證的概念以及其優(yōu)缺點。二次認證可以通過多種方式實現(xiàn)包括基于硬件令牌、短信驗證碼和移動應(yīng)用等。不同的實現(xiàn)方式各有優(yōu)缺點,需要按照實際情況進行選擇。
其次使用Spring Security演示了如何在一個基于Web的應(yīng)用程序中添加二次認證。通過使用Spring Security可以輕松地將一個二次認證模塊與我們的應(yīng)用程序集成。在這個過程中了解如何配置Spring Security以及如何使用Spring Security提供的安全特性。
最后還討論了一些可能的問題以及解決方案。例如,提到了可能會遇到與移動設(shè)備兼容性和惡意攻擊等問題。為了解決這些問題提供了一些方法和建議,例如使用較新的移動設(shè)備和實施防范措施等。
希望大家能夠了解二次認證的主要概念以及如何使用Spring Security實現(xiàn)二次認證。同時也希望大家能夠了解二次認證可能遇到的挑戰(zhàn),并能夠選擇適當(dāng)?shù)慕鉀Q方案。
4.1 二次認證的優(yōu)缺點
二次認證可以提供額外的安全保障,增強用戶賬號的安全性。通過二次認證,用戶需要在登錄時進行額外的驗證,使得其他人無法直接登錄到該賬號中。尤其是在一些關(guān)鍵應(yīng)用場景中,例如銀行賬戶和電子商務(wù)平臺,二次認證可以提供更加可靠的賬號保護。
雖然二次認證可以提供更高的安全保障,但它也可能會影響系統(tǒng)的便捷性和易用性。尤其是在一些需要頻繁登錄的應(yīng)用中,例如互聯(lián)網(wǎng)社交應(yīng)用和游戲應(yīng)用,過多的驗證步驟可能會導(dǎo)致用戶反感。此外二次認證實現(xiàn)的復(fù)雜度也可能會造成不必要的負擔(dān)和成本。
因此在實踐中需要根據(jù)實際情況進行選擇。我們可以在關(guān)鍵應(yīng)用場景中使用二次認證,同時在其他應(yīng)用場景中采用其他的驗證方式,以提供更好的用戶體驗和系統(tǒng)安全性的平衡。
4.2 Spring Security添加二次認證的效果
通過使用Spring Security可以輕松地將二次認證模塊集成到我們的應(yīng)用程序中。可以使用Spring Security提供的二次認證過濾器,并按照要求配置我們的認證方式。在進行認證時可以使用Spring Security提供的許多安全特性,以保護我們的應(yīng)用程序不受惡意攻擊。
同時通過Spring Security還可以輕松地實現(xiàn)其他的安全特性,例如用戶角色控制、會話管理和密碼加密等。這樣,我們可以構(gòu)建一個更加健壯和安全的應(yīng)用程序,并幫助用戶獲得更好的體驗和信任感。
4.3 可能的問題及解決方案
在實踐中能會遇到一些問題如移動設(shè)備兼容性、短信驗證碼實現(xiàn)和惡意攻擊等。為了解決這些問題,我們可以采取一些措施例如:
選擇較新版本設(shè)備。隨著移動設(shè)備技術(shù)的不斷更新,較新的設(shè)備可能會更加支持多種驗證方式,例如指紋識別和面部識別等。
使用多個驗證碼驗證方式。我們可以使用多種驗證碼驗證方式,例如郵件驗證碼、電話驗證碼和圖形驗證碼等,以提高安全性。
實施防范措施。我們可以通過使用Web防火墻和入侵檢測系統(tǒng)等安全技術(shù),來防范惡意攻擊和入侵行為。
總之通過有效地管理和保護我們的應(yīng)用程序可以提供更高質(zhì)量的用戶體驗和安全性。通過Spring Security,我們可以輕松地添加二次認證模塊,并讓我們的應(yīng)用程序更加可靠和安全。
到此這篇關(guān)于Spring Security添加二次認證的項目實踐的文章就介紹到這了,更多相關(guān)Spring Security二次認證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringSecurity?默認登錄認證的實現(xiàn)原理解析
- SpringSecurity實現(xiàn)權(quán)限認證與授權(quán)的使用示例
- Spring Security內(nèi)存中認證的實現(xiàn)
- SpringBoot整合SpringSecurity認證與授權(quán)
- SpringSecurity+jwt+redis基于數(shù)據(jù)庫登錄認證的實現(xiàn)
- SpringSecurity身份認證原理解析
- springsecurity第三方授權(quán)認證的項目實踐
- Spring Security實現(xiàn)身份認證和授權(quán)的示例代碼
- SpringSecurity實現(xiàn)前后端分離登錄token認證詳解
- Spring Security認證機制源碼層探究
- SpringBoot security安全認證登錄的實現(xiàn)方法
相關(guān)文章
在SpringBoot中實現(xiàn)一個訂單號生成系統(tǒng)的示例代碼
在Spring Boot中設(shè)計一個訂單號生成系統(tǒng),主要考慮到生成的訂單號需要滿足的幾個要求:唯一性、可擴展性、以及可能的業(yè)務(wù)相關(guān)性,本文給大家介紹了幾種常見的解決方案及相應(yīng)的示例代碼,需要的朋友可以參考下2024-02-02Kotlin中?StateFlow?或?SharedFlow?的區(qū)別解析
Kotlin協(xié)程中的StateFlow和SharedFlow是響應(yīng)式數(shù)據(jù)流,分別用于UI狀態(tài)管理和事件通知,StateFlow有初始值,只保留最新值,適用于UI狀態(tài)管理;SharedFlow沒有初始值,可以配置緩存大小,適用于事件通知,感興趣的朋友一起看看吧2025-03-03SpringSecurity微服務(wù)實戰(zhàn)之公共模塊詳解
這篇文章主要為大家介紹了SpringSecurity微服務(wù)實戰(zhàn)之公共模塊詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08詳解如何用spring Restdocs創(chuàng)建API文檔
這篇文章將帶你了解如何用spring官方推薦的restdoc去生成api文檔。具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05