說明
在 spring-security使用-登錄(一) 我們使用的是重寫了Spring-security的filter的方式來進行自定義,但是這樣的弊端,就是侵入太大。直接把spring-security的filter給替換掉了,
通過AuthenticationProvider的方式是在spring-security的filter內部留的擴展點進行擴展自定義登錄邏輯
接口定義
AuthenticationProvider
public interface AuthenticationProvider { /** * 驗證用戶身份 * @param var1 * @return * @throws AuthenticationException */ Authentication authenticate(Authentication var1) throws AuthenticationException; /** * supports 則用來判斷當前的 AuthenticationProvider 是否支持對應的 Authentication。 * @param var1 * @return */ boolean supports(Class<?> var1); }
Authentication
封裝用戶身份信息
public interface Authentication extends Principal, Serializable { //用來獲取用戶的權限。 Collection<? extends GrantedAuthority> getAuthorities(); //方法用來獲取用戶憑證,一般來說就是密碼。 Object getCredentials(); //方法用來獲取用戶攜帶的詳細信息,可能是當前請求之類的東西。 Object getDetails(); //方法用來獲取當前用戶,可能是一個用戶名,也可能是一個用戶對象。 Object getPrincipal(); //當前用戶是否認證成功。 boolean isAuthenticated(); void setAuthenticated(boolean var1) throws IllegalArgumentException; }
類圖
UsernamePasswordAuthenticationFilter 用的就是UserNamePasswordAuthenticationToken
使用AuthenticationProvider自定義登錄邏輯
1.增加自定義provider繼承DaoAuthenticationProvider
public class CodeAuthenticationProvider extends DaoAuthenticationProvider { @Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) { HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String code = req.getParameter("code"); String verify_code = (String) req.getSession().getAttribute("verify_code"); if (code == null || verify_code == null || !code.equals(verify_code)) { throw new AuthenticationServiceException("驗證碼錯誤"); } super.additionalAuthenticationChecks(userDetails, authentication); } }
2.在public class SecurityConfig extends WebSecurityConfigurerAdapter 類增加以下
/** * 對密碼進行加密的實例 * @return */ @Bean PasswordEncoder passwordEncoder() { /** * 不加密所以使用NoOpPasswordEncoder * 更多可以參考PasswordEncoder 的默認實現官方推薦使用: BCryptPasswordEncoder,BCryptPasswordEncoder */ return NoOpPasswordEncoder.getInstance(); } /** * 自定義provider * @return */ public CodeAuthenticationProvider codeAuthenticationProvider() { CodeAuthenticationProvider myAuthenticationProvider = new CodeAuthenticationProvider(); //設置passorderEncoder myAuthenticationProvider.setPasswordEncoder(passwordEncoder()); //設置UserDetailsService 可以參考第一篇登錄使用例子自定義userDetailServices myAuthenticationProvider.setUserDetailsService(userService); return myAuthenticationProvider; } /** * 重寫父類自定義AuthenticationManager 將provider注入進去 * 當然我們也可以考慮不重寫 在父類的manager里面注入provider * @return * @throws Exception */ @Override protected AuthenticationManager authenticationManager() throws Exception { ProviderManager manager = new ProviderManager(Arrays.asList(codeAuthenticationProvider())); return manager; }
原理
spring-security使用-登錄(一) 我們替換了默認的UsernamePasswordAuthenticationFilter
1.根據看這個類UsernamePasswordAuthenticationFilter我們可以看出他的父類實現了Servilet Fitler,所以spring-security是基於Filter的
org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#doFilter 父類實現
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { //省略部分代碼...... //調用子類的實現 也就是 UsernamePasswordAuthenticationFilter authResult = this.attemptAuthentication(request, response); if (authResult == null) { return; } } }
2.看子類實現
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#attemptAuthentication
public org.springframework.security.core.Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { //注意這里不支持post請求,如果我們要讓支持post請求,就要重寫filter吧這里去掉 if (this.postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } else { //獲得登錄用戶名 String username = this.obtainUsername(request); //用的登錄密碼 String password = this.obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); //通過UsernamePasswordAuthenticationToken 封裝 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); this.setDetails(request, authRequest); //委托給AuthenticationManager 執行 我們上面重寫用的是ProviderManager return this.getAuthenticationManager().authenticate(authRequest); } }
3.providerManger實現
org.springframework.security.authentication.ProviderManager#authenticate
public org.springframework.security.core.Authentication authenticate(org.springframework.security.core.Authentication authentication) throws AuthenticationException { //獲得Authentication的類型 Class<? extends org.springframework.security.core.Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; AuthenticationException parentException = null; org.springframework.security.core.Authentication result = null; org.springframework.security.core.Authentication parentResult = null; boolean debug = logger.isDebugEnabled(); //獲得所有的Providers 就是我們定義的CodeAuthenticationProvider Iterator var8 = this.getProviders().iterator(); while(var8.hasNext()) { //迭代器迭代獲取 AuthenticationProvider provider = (AuthenticationProvider)var8.next(); //判斷是否能處理 if (provider.supports(toTest)) { if (debug) { logger.debug("Authentication attempt using " + provider.getClass().getName()); } try { //調用provider的authenticate 執行身份認證 result = provider.authenticate(authentication); if (result != null) { this.copyDetails(authentication, result); break; } } catch (InternalAuthenticationServiceException | AccountStatusException var13) { this.prepareException(var13, authentication); throw var13; } catch (AuthenticationException var14) { lastException = var14; } } } //省略部分代碼....... }