spring security4 添加驗證碼的示例代碼
spring security是一個很大的模塊,本文中只涉及到了自定義參數(shù)的認(rèn)證。spring security默認(rèn)的驗證參數(shù)只有username和password,一般來說都是不夠用的。由于時間過太久,有些忘,可能有少許遺漏。好了,不廢話。
spring以及spring security配置采用javaConfig,版本依次為4.2.5,4.0.4
總體思路:自定義EntryPoint,添加自定義參數(shù)擴(kuò)展AuthenticationToken以及AuthenticationProvider進(jìn)行驗證。
首先定義EntryPoint:
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class MyAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {
public MyAuthenticationEntryPoint(String loginFormUrl) {
super(loginFormUrl);
}
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
super.commence(request, response, authException);
}
}
接下來是token,validCode是驗證碼參數(shù):
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
public class MyUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
private String validCode;
public MyUsernamePasswordAuthenticationToken(String principal, String credentials, String validCode) {
super(principal, credentials);
this.validCode = validCode;
}
public String getValidCode() {
return validCode;
}
public void setValidCode(String validCode) {
this.validCode = validCode;
}
}
繼續(xù)ProcessingFilter,
import com.core.shared.ValidateCodeHandle;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
public class MyValidCodeProcessingFilter extends AbstractAuthenticationProcessingFilter {
private String usernameParam = "username";
private String passwordParam = "password";
private String validCodeParam = "validateCode";
public MyValidCodeProcessingFilter() {
super(new AntPathRequestMatcher("/user/login", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
String username = request.getParameter(usernameParam);
String password = request.getParameter(passwordParam);
String validCode = request.getParameter(validCodeParam);
valid(validCode, request.getSession());
MyUsernamePasswordAuthenticationToken token = new MyUsernamePasswordAuthenticationToken(username, password, validCode);
return this.getAuthenticationManager().authenticate(token);
}
public void valid(String validCode, HttpSession session) {
if (validCode == null) {
throw new ValidCodeErrorException("驗證碼為空!");
}
if (!ValidateCodeHandle.matchCode(session.getId(), validCode)) {
throw new ValidCodeErrorException("驗證碼錯誤!");
}
}
}
分別定義三個參數(shù),用于接收login表單過來的參數(shù),構(gòu)造方法給出了login的url以及需要post方式
接下來就是認(rèn)證了,此處還沒到認(rèn)證用戶名和密碼的時候,只是認(rèn)證了驗證碼
下面是ValidateCodeHandle一個工具類以及ValidCodeErrorException:
import java.util.concurrent.ConcurrentHashMap;
public class ValidateCodeHandle {
private static ConcurrentHashMap validateCode = new ConcurrentHashMap<>();
public static ConcurrentHashMap getCode() {
return validateCode;
}
public static void save(String sessionId, String code) {
validateCode.put(sessionId, code);
}
public static String getValidateCode(String sessionId) {
Object obj = validateCode.get(sessionId);
if (obj != null) {
return String.valueOf(obj);
}
return null;
}
public static boolean matchCode(String sessionId, String inputCode) {
String saveCode = getValidateCode(sessionId);
if (saveCode.equals(inputCode)) {
return true;
}
return false;
}
}
這里需要繼承AuthenticationException以表明它是security的認(rèn)證失敗,這樣才會走后續(xù)的失敗流程
import org.springframework.security.core.AuthenticationException;
public class ValidCodeErrorException extends AuthenticationException {
public ValidCodeErrorException(String msg) {
super(msg);
}
public ValidCodeErrorException(String msg, Throwable t) {
super(msg, t);
}
}
接下來是Provider:
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
public class MyAuthenticationProvider extends DaoAuthenticationProvider {
@Override
public boolean supports(Class<?> authentication) {
return MyUsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
Object salt = null;
if (getSaltSource() != null) {
salt = getSaltSource().getSalt(userDetails);
}
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException("用戶名或密碼錯誤!");
}
String presentedPassword = authentication.getCredentials().toString();
if (!this.getPasswordEncoder().isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException("用戶名或密碼錯誤!");
}
}
}
其中supports方法指定使用自定義的token,additionalAuthenticationChecks方法和父類的邏輯一模一樣,我只是更改了異常返回的信息。
接下來是處理認(rèn)證成功和認(rèn)證失敗的handler
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class FrontAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
public FrontAuthenticationSuccessHandler(String defaultTargetUrl) {
super(defaultTargetUrl);
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
super.onAuthenticationSuccess(request, response, authentication);
}
}
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class FrontAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
public FrontAuthenticationFailureHandler(String defaultFailureUrl) {
super(defaultFailureUrl);
}
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
super.onAuthenticationFailure(request, response, exception);
}
}
最后就是最重要的security config 了:
import com.service.user.CustomerService;
import com.web.filter.SiteMeshFilter;
import com.web.mySecurity.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
import org.springframework.web.filter.CharacterEncodingFilter;
import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends AbstractSecurityWebApplicationInitializer {
@Bean
public PasswordEncoder passwordEncoder() {
return new StandardPasswordEncoder("MD5");
}
@Autowired
private CustomerService customerService;
@Configuration
@Order(1)
public static class FrontendWebSecurityConfigureAdapter extends WebSecurityConfigurerAdapter {
@Autowired
private MyValidCodeProcessingFilter myValidCodeProcessingFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/user/login", "/user/logout").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(myValidCodeProcessingFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin()
.loginPage("/user/login")
.and()
.logout()
.logoutUrl("/user/logout")
.logoutSuccessUrl("/user/login");
}
}
@Bean(name = "frontAuthenticationProvider")
public MyAuthenticationProvider frontAuthenticationProvider() {
MyAuthenticationProvider myAuthenticationProvider = new MyAuthenticationProvider();
myAuthenticationProvider.setUserDetailsService(customerService);
myAuthenticationProvider.setPasswordEncoder(passwordEncoder());
return myAuthenticationProvider;
}
@Bean
public AuthenticationManager authenticationManager() {
List<AuthenticationProvider> list = new ArrayList<>();
list.add(frontAuthenticationProvider());
AuthenticationManager authenticationManager = new ProviderManager(list);
return authenticationManager;
}
@Bean
public MyValidCodeProcessingFilter myValidCodeProcessingFilter(AuthenticationManager authenticationManager) {
MyValidCodeProcessingFilter filter = new MyValidCodeProcessingFilter();
filter.setAuthenticationManager(authenticationManager);
filter.setAuthenticationSuccessHandler(frontAuthenticationSuccessHandler());
filter.setAuthenticationFailureHandler(frontAuthenticationFailureHandler());
return filter;
}
@Bean
public FrontAuthenticationFailureHandler frontAuthenticationFailureHandler() {
return new FrontAuthenticationFailureHandler("/user/login");
}
@Bean
public FrontAuthenticationSuccessHandler frontAuthenticationSuccessHandler() {
return new FrontAuthenticationSuccessHandler("/front/test");
}
@Bean
public MyAuthenticationEntryPoint myAuthenticationEntryPoint() {
return new MyAuthenticationEntryPoint("/user/login");
}
}
首先是一個加密類的bean,customerService是一個簡單的查詢用戶
@Service("customerService")
public class CustomerServiceImpl implements CustomerService {
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userDao.findCustomerByUsername(username);
}
}
下來就是FrontendWebSecurityConfigureAdapter了,重寫了configure方法,先禁用csrf, 開啟授權(quán)請求authorizeRequests(),其中”/user/login”, “/user/logout”放過權(quán)限驗證, 其他請求需要進(jìn)行登錄認(rèn)證, 然后是addFilterBefore(),把我自定義的myValidCodeProcessingFilter添加到security默認(rèn)的UsernamePasswordAuthenticationFilter之前,也就是先進(jìn)行我的自定義參數(shù)認(rèn)證, 然后是formLogin(),配置登錄url以及登出url,登錄登出url都需要進(jìn)行controller映射,也就是要自己寫controller。
接下來就是AuthenticationProvider,AuthenticationManager,ProcessingFilter,AuthenticationFailureHandler,AuthenticationSuccessHandler,EntryPoint的bean顯示聲明。
下面是login.jsp
<body>
<div class="login_div">
<form:form autocomplete="false" commandName="userDTO" method="post">
<div>
<span class="error_tips"><b>${SPRING_SECURITY_LAST_EXCEPTION.message}</b></span>
</div>
username:<form:input path="username" cssClass="form-control"/><br/>
password:<form:password path="password" cssClass="form-control"/><br/>
validateCode:<form:input path="validateCode" cssClass="form-control"/>
<label>${validate_code}</label>
<div class="checkbox">
<label>
<input type="checkbox" name="remember-me"/>記住我
</label>
</div>
<input type="submit" class="btn btn-primary" value="submit"/>
</form:form>
</div>
</body>
驗證碼驗證失敗的時候拋出的是ValidCodeErrorException,由于它繼承AuthenticationException,security在驗證的時候遇到AuthenticationException就會觸發(fā)AuthenticationFailureHandler,上面的bean中聲明了認(rèn)證失敗跳轉(zhuǎn)到登錄url,所以login.jsp里面有${SPRING_SECURITY_LAST_EXCEPTION.message}獲取我認(rèn)證時拋出異常信息,能友好的提示用戶。
整個自定義security驗證流程就走完了
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Spring Security登錄添加驗證碼的實現(xiàn)過程
- SpringSecurity實現(xiàn)圖形驗證碼功能的實例代碼
- SpringBoot + SpringSecurity 短信驗證碼登錄功能實現(xiàn)
- SpringBoot結(jié)合SpringSecurity實現(xiàn)圖形驗證碼功能
- SpringBoot+Security 發(fā)送短信驗證碼的實現(xiàn)
- Spring Security OAuth2集成短信驗證碼登錄以及第三方登錄
- Spring Security 圖片驗證碼功能的實例代碼
- Spring Security Oauth2.0 實現(xiàn)短信驗證碼登錄示例
- Spring Security實現(xiàn)驗證碼登錄功能
相關(guān)文章
使用Java解析JSON數(shù)據(jù)并提取特定字段的實現(xiàn)步驟(以提取mailNo為例)
在現(xiàn)代軟件開發(fā)中,處理JSON數(shù)據(jù)是一項非常常見的任務(wù),無論是從API接口獲取數(shù)據(jù),還是將數(shù)據(jù)存儲為JSON格式,解析和提取JSON中的特定字段都是開發(fā)人員需要掌握的基本技能,本文將以一個實際案例為例,詳細(xì)介紹如何使用Java解析JSON數(shù)據(jù)并提取其中的mailNo字段2025-01-01
程序包org.springframework不存在的解決辦法
這篇文章主要介紹了程序包org.springframework不存在的解決辦法,在使用IDEA創(chuàng)建SpringBoot項目時,剛打開無法正常運行,本文通過圖文結(jié)合的方式給大家介紹的非常詳細(xì),具有一定參考價值,需要的朋友可以參考下2024-07-07
Springboot項目基于Devtools實現(xiàn)熱部署步驟詳解
這篇文章主要介紹了Springboot項目基于Devtools實現(xiàn)熱部署,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-06-06

