一些基礎的知識能夠參考如下鏈接:https://www.cnkirito.moe/spring-security-1/ 講真,這老哥寫的很好,不過還有不少估計沒寫完,遺憾spring
本文章主要是一個大概的總結。安全
首先SpringSecurity是一個安全框架,它所作的事兒簡單歸納就是使用Filter對整個項目進行一個保護,框架
保護的內容就包括 認證 和 受權驗證 若是單針對Filter的話 他包括 登錄認證,和對URL的權限驗證, 登錄認證是在SpringSecurity的過濾器鏈中的AbstractAuthenticationProcessingFilter的子類來作的 具體步驟ide
請求進來->若干前置過濾器->AbstractAuthenticationProcessingFilter.doFilter()-> AbstractAuthenticationProcessingFilter.attemptAuthentication()->若是獲取到了Authentication則繼續往下執行,不然重定向到登錄頁面.... 從attemptAuthentication方法中的認證操做是調用了AuthenticationManager的authenticate方法來進行登錄認證,而後會把用戶提交的用戶信息封裝成AuthenticationToken的實例丟進去,在其中進行驗證,驗證成功則返回填充了相關信息的Token對象,不然是null,則說明認證失敗,會重定向到咱們設置好的loginUrl。ui
這個認證過程當中還涉及到 AuthenticationManager 的認證 是 AuthenticationManager的一個實現類ProviderManager,而ProviderManager會有一個List專門存放Provider,這些Provider都要實現AuthenticationProvider接口,只要有一個Provider支持放進來的Authentication則會由這個Provider進行處理,且這個方法能夠拋出異常,拋出也說明登錄失敗。this
因此若是要自定義本身的一套登錄認證的話,url
咱們涉及到三個地方 Filter - Provider - Token (例子採用上面連接的老哥的例子)code
咱們須要自定義本身的過濾器 經過 繼承 AbstractAuthenticationProcessingFilter 類 實現 attemptAuthentication方法orm
public class IpAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter { public IpAuthenticationProcessingFilter(String uri) { super(new AntPathRequestMatcher(uri)); } @Override public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException { //獲取host信息 String host = httpServletRequest.getRemoteHost(); //交給內部的AuthenticationManager去認證,實現解耦 return getAuthenticationManager().authenticate(new IpAuthenticationToken(host)); } }
咱們須要自定義本身的認證提供器 經過繼承 AuthenticationProvider 類對象
public class IpAuthenticationProvider implements AuthenticationProvider { final static Map<String, SimpleGrantedAuthority> ipAuthorityMap = new ConcurrentHashMap<>(); //維護一個ip白名單列表,每一個ip對應必定的權限 static { ipAuthorityMap.put("127.0.0.1", new SimpleGrantedAuthority("ADMIN")); ipAuthorityMap.put("10.0.0.8", new SimpleGrantedAuthority("FRIEND")); } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { if (authentication instanceof IpAuthenticationToken){ String ip = ((IpAuthenticationToken)authentication).getIp(); if (ipAuthorityMap.containsKey(ip)){ HashSet<SimpleGrantedAuthority> grants = new HashSet(); grants.add(ipAuthorityMap.get(ip)); return new IpAuthenticationToken(ip,grants); } } return null; } @Override public boolean supports(Class<?> aClass) { return IpAuthenticationToken.class.isAssignableFrom(aClass); } }
咱們須要自定義本身的 認證對象 通常就是咱們的領域對象 可是這個對象必須實現 Authentication 接口 咱們能夠直接繼AbstractAuthenticationToken類便可
public class IpAuthenticationToken extends AbstractAuthenticationToken { public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } private String ip; public IpAuthenticationToken(String ip) { super(null); this.ip = ip; super.setAuthenticated(false); } public IpAuthenticationToken(String ip, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.ip = ip; super.setAuthenticated(true); } @Override public Object getCredentials() { return null; } @Override public Object getPrincipal() { return null; } }
而後咱們須要將provider添加到ProviderManager的List中去
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(new IpAuthenticationProvider()); auth.userDetailsService(userDetailsService).passwordEncoder(NoOpPasswordEncoder.getInstance()); }
將咱們自定義的Filter註冊到過濾器鏈中去
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/iplogin","/login").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/iplogin"); //這個操做就是把咱們自定義的過濾器插入到UsernamePasswordAuthenticationFilter的前面,表示優先使用咱們的驗證,若是驗證成功則會將Authentication對象放入 //SecurityContext中,後面的過濾器在進行驗證時會檢測SecurityContext中是否有相應的對象,沒有則會 http.addFilterBefore(ipAuthenticationProcessingFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class); }
搞定 這個時候咱們的登錄驗證方式已經改爲了iplogin(雖然原始的login還在,可是因爲咱們設置了loginPage的url爲/iplogin因此那個頁面在登錄失敗的時候永遠也訪問不到,除非你直接訪問/login....)