spring security是一個很大的模塊,本文中只涉及到了自定義參數的認證。 spring security默認的驗證參數只有username和password,一般來說都是不夠用的。由於時間過太久,有些忘,可能有少許遺漏。好了,不廢話。
spring以及spring security配置採用javaConfig,版本依次為4.2.5,4.0.4
總體思路:自定義EntryPoint,添加自定義參數擴展AuthenticationToken以及AuthenticationProvider進行驗證。
首先定義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是驗證碼參數:
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; }}繼續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("驗證碼錯誤!"); } }}分別定義三個參數,用於接收login表單過來的參數,構造方法給出了login的url以及需要post方式
接下來就是認證了,此處還沒到認證用戶名和密碼的時候,只是認證了驗證碼
下面是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的認證失敗,這樣才會走後續的失敗流程
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方法和父類的邏輯一模一樣,我只是更改了異常返回的信息。
接下來是處理認證成功和認證失敗的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@EnableWebSecuritypublic 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, 開啟授權請求authorizeRequests(),其中”/user/login”, “/user/logout”放過權限驗證, 其他請求需要進行登錄認證, 然後是addFilterBefore(),把我自定義的myValidCodeProcessingFilter添加到security默認的UsernamePasswordAuthenticationFilter之前,也就是先進行我的自定義參數認證, 然後是formLogin(),配置登錄url以及登出url,登錄登出url都需要進行controller映射,也就是要自己寫controller。
接下來就是AuthenticationProvider,AuthenticationManager,ProcessingFilter,AuthenticationFailureHandler,AuthenticationSuccessHandler,EntryPoint的bean顯示聲明。
下面是login.jsp
<body><div> <form:form autocomplete="false" commandName="userDTO" method="post"> <div> <span><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> <label> <input type="checkbox" name="remember-me"/>記住我</label> </div> <input type="submit" value="submit"/> </form:form></div></body>驗證碼驗證失敗的時候拋出的是ValidCodeErrorException,由於它繼承AuthenticationException,security在驗證的時候遇到AuthenticationException就會觸發AuthenticationFailureHandler,上面的bean中聲明了認證失敗跳轉到登錄url,所以login.jsp裡面有${SPRING_SECURITY_LAST_EXCEPTION.message}獲取我認證時拋出異常信息,能友好的提示用戶。
整個自定義security驗證流程就走完了
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。