Spring Security認證是由 AuthenticationManager 來管理的,可是真正進行認證的是 AuthenticationManager 中定義的 AuthenticationProvider。AuthenticationManager 中能夠定義有多個 AuthenticationProvider。當咱們使用 authentication-provider 元素來定義一個 AuthenticationProvider 時,若是沒有指定對應關聯的 AuthenticationProvider 對象,Spring Security 默認會使用 DaoAuthenticationProvider。DaoAuthenticationProvider 在進行認證的時候須要一個 UserDetailsService 來獲取用戶的信息 UserDetails,其中包括用戶名、密碼和所擁有的權限等。因此若是咱們須要改變認證的方式,咱們能夠實現本身的 AuthenticationProvider;若是須要改變認證的用戶信息來源,咱們能夠實現 UserDetailsService。css
1.實現UserDetailsService 接口java
CustomUserDetailsService類: web
package cn.lger.security; import cn.lger.dao.UserDao; import cn.lger.entity.User; import org.springframework.beans.factory.annotation.Autowired; 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; import java.util.ArrayList; import java.util.List; public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserDao userDao; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userDao.findByUsername(username); if(user == null){ throw new UsernameNotFoundException("not found"); } List<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>(); authorities.add(new SimpleGrantedAuthority(user.getRole())); System.err.println("username is " + username + ", " + user.getRole()); return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities); } }
在下面的configure(AuthenticationManagerBuilder auth) 方法中添加這個UserDetailsService spring
SecurityConfig類:數組
package cn.lger.config; import cn.lger.security.CustomUserDetailsService; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 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.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override @Bean public UserDetailsService userDetailsService() { return new CustomUserDetailsService(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth .userDetailsService(userDetailsService()) .passwordEncoder(new BCryptPasswordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/home").permitAll() .antMatchers("/css/**","/js/**","/img/**","/vendors/**").permitAll() .anyRequest().permitAll() .and() .formLogin() .defaultSuccessUrl("/user/list") .permitAll() .and() .logout() .permitAll() .and() .csrf().disable(); } }
這裏雖然是給AuthenticationManagerBuilder這個構造類來添加的UserDetailsService 而不是真正的AuthenticationManager這個類,可是通過我嘗試過斷點調試,在啓動階段就會進入斷點,斷點的調試結果以下:安全
剛開始進入咱們本身寫的安全配置類SecurityConfig的父類WebSecurityConfigurerAdapter的init(final WebSecurity web)這個方法app
斷點的位置會調用getHttp()這個方法,而後咱們下一步進入這個方法的內部看看,以下圖:ide
這裏斷點位置我已經看到AuthenticationManager這個類的一個對象的構建了,而後繼續進入authenticationManager()這個方法內部看看,這個AuthenticationManager對象是如何構建的:函數
進來先判斷AuthenticationManager這個類是否是已經初始化一個對象了了,若是初始化了就直接返回AuthenticationManager的對象,不然就調用configure(AuthenticationManagerBuilder auth)這個方法,由於SecurityConfig這個類重寫了WebSecurityConfigurerAdapter這個類本來的configure(AuthenticationManagerBuilder auth)的這個方法,因此運行到下一個斷點會到SecurityConfig類的configure(AuthenticationManagerBuilder auth)方法上:學習
這樣就在構建AuthenticationManager類的實例前給它提供了一個UserDetailsService實例,這樣DaoAuthenticationProvider 就能夠經過這個實例裏面的loadUserByUsername(String username)(這個方法須要咱們本身實現)獲取一個UserDetails的實例,而且我實驗過,這個loadUserByUsername(String username)方法會在咱們登錄認證的時候起做用,若果咱們登錄成功就會返回一個UserDetails的實例,這個UserDetails的實例中包含了用戶的用戶名,密碼和權限(權限是一個set(集合),說明能夠支持一個用戶多個角色)
下面我說一下springsecurity中靜態資源加載時我遇到的問題和解決方案
首先用了springsecurity咱們能夠繼承WebSecurityConfigurerAdapter這個類,而後個根據我的須要來重寫這個類的方法,下面的例子我用SecurityConfig這個類來繼承它,若是重寫了configure(HttpSecurity http)這個方法,下面的第二句代碼是攔截全部的請求,因此咱們請求的靜態資源也會一樣被攔截,因此咱們須要告訴springsecurity個人靜態資源是能夠放行的,這個時候就須要用到紅色框中的第一行代碼來實現對靜態資源放行,咱們不須要在路徑前面加/static,由於默認狀況下它會認爲這些路徑下的文件,這幾個路徑分別是:「classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"(這個我會面會說爲何)
這裏我對剛纔說下爲何默認狀況下springsecurity認爲靜態資源會在這幾個目錄下(「classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/"),這個也是有理有據的,不是我瞎掰的,其實我原本也不清楚緣由,都是網上說的,後來個人一個好基友告訴個人我才知道,這裏就謝謝這位好基友,好了說了這麼多,如今就看看哪裏指定了這些路徑,咱們點開application.properties這個文件在上面輸入以下圖同樣的配置
而後按着Ctrl鍵點擊這個配置,而後會進入下面的這個類
根據圖中的標記就看得出來,爲何默認狀況下springsecurity會認爲靜態資源在這幾個目錄下了吧。
除了上面的那種方式外還有一種能夠放行靜態資源的方法,不過這個方法仍是須要繼承WebSecurityConfigurerAdapter這個類,此次不須要重寫configure(HttpSecurity http)這個方法,緣由是雖然仍是要有攔截全部請求這幾代碼,可是WebSecurityConfigurerAdapter這個類中的configure(HttpSecurity http)這個類中本來就有着句代碼,以下圖
可是咱們此次須要重寫configure(WebSecurity web)這個方法來實現對靜態資源的放行,具體的操做以下圖:
那麼着兩種方式的配置有什麼區別呢,此次自能是我的感受了,畢竟技術有限,有些東西證實不了,只能猜測,那麼我感受第一種方式的話是須要通過攔截器的攔截,而後再根據咱們的配置,看到須要放行才放行,就是這個過程仍是須要通過攔截器的,可是第二種方法看英文就知道(ignoring忽視的意識),就是這個攔截器直接忽視這些文件夾下的資源(默認狀況下仍是上面說的那幾個路徑「classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/"下的文件夾)因此它們的區別是一個通過攔截器一個不通過攔截器。
這裏須要注意的是若是繼承了WebMvcConfigurationSupport這個類,而且將在子類上面添加了@Configuration(將這個類看着配置類),那麼上面說的默認狀況下資源文件的路徑在(「classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/"「classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/")這就幾個目錄下的狀況會無效,個人好基友和我說是啓動了這個配置類後就在application.properties文件下配置的spring.resources.static-locations不起做用了,具體爲何不起做用了英文及技術問題我也沒法證實,感受是WebMvcConfigurationSupport這個類內部有方法影響所形成的。下面的例子我用ResourcesConfig這個類來繼承WebMvcConfigurationSupport這個而且在類的上面加了@Configuration這個註解,聲明它爲配置類:代碼以下
而後我在SecurityConfig類中用第二種配置方式,代碼以下:
結果訪問頁面的時候全部的靜態資源都加載不到,狀態碼都是404(找不到資源路徑)
若是把ResourcesConfig類上的@Configuration這個註解去掉就能夠正常的找到這些靜態資源文件,因此我就以爲就是這個父類裏面的方法影響的問題致使不會去默認的那幾個路徑下找資源文件
去掉@Configuration這個註解後的結果以下:
若是你你使用ResourcesConfig類繼承WebMvcConfigurationSupport這個類而且在類的上面加了個@Configuration註解,那麼就不會去上面說的默認路徑下找靜態資源文件,因此咱們須要本身告訴它去哪裏找這些靜態資源,這裏我告訴它要去classpath:/static/下面去找這些資源文件,代碼以下:
這樣就告訴它全部的靜態資源/**都在classpath:/static/下面找
我下面對這個上面的代碼作了簡單的代碼分析
在WebMvcConfigurationSupport中有個@Bean的方法,該方法具體以下:
紅框裏面的第一行代碼上面addResourceHandlers(registry)在父類裏面是一個空函數,由於咱們繼承了WebMvcConfigurationSupport這個父類而且重寫了這個方法,因此這裏會調用咱們的子類裏面寫的方法
下面看看父類中的addResourceHandlers(registry)這個空方法:
而咱們重寫了該方法後是這樣的,重點在紅框中的那段代碼,代碼以下:
按着Ctrl點擊addResourceHandler("/**")這段,而後會跳轉到WebMvcConfigurationSupport中的這段代碼中:
這裏咱們建立了一個ResourceHandlerRegistration(資源處理定位器)實例,而後將這個ResourceHandlerRegistration添加到registrations(一個List<ResourceHandlerRegistration>),注意這裏返回的是一個ResourceHandlerRegistration實例的引用,因此addResourceLocations("classpath:/static/")是ResourceHandlerRegistration這個類中的一個方法,下面看看這個方法究竟是幹嗎的,仍是按着Ctrl點擊這個方法,點擊去會看到:
這個方法只是將傳過來的多個字符串存到locationValues這個字符串列表裏面
第一句代碼可能會有點繞暈,這裏主要記住那個添加了@Bean註解的HandlerMapping resourceHandlerMapping()方法中咱們建立了一個ResourceHandlerRegistry類的一個實例,而後調用了咱們本身寫的ResourcesConfig類(繼承WebMvcConfigurationSupport類並重寫protected void addResourceHandlers(ResourceHandlerRegistry registry)方法)的protected void addResourceHandlers(ResourceHandlerRegistry registry)方法,方法裏面咱們爲ResourceHandlerRegistry類的實例中的registrations(一個ResourceHandlerRegistration列表)添加了一個ResourceHandlerRegistration類實例,而且這個爲ResourceHandlerRegistration類實例中的locationValues賦值
第二句代碼的是爲了獲取一個HandlerMapping的實例,按着Ctrl點擊進去看看registry.getHandlerMapping()這個方法內部是怎麼樣的,裏面的代碼以下:
這裏面有兩個for循環,第一個for循環遍歷registry中的ResourceHandlerRegistration列表registrations,而記得咱們上面第一句代碼內部實現就有給這個registrations初始化,因此裏面放置了一ResourceHandlerRegistration實例registration,而在建立這個ResourceHandlerRegistration實例的時候咱們給他傳入了多個String參數,這些參數就是咱們請求靜態資源的時候可能出現的路徑(好比http://localhost:8080/js/test.js,http://localhost:8080/css/style.css在8080後面的這一段咱們能夠用/**請求時表示全部的靜態資源)由於在ResourceHandlerRegistration類中這多個String 參數是以String 數組的方式存起來,因此咱們在這裏能夠獲取這個String 數組而且遍歷這個數組,而後看registration.getRequestHandler()這句的內部,主要看紅色框中的那句話,給handler的locationValues屬性賦值。
而後看看第三句handler.afterPropertiesSet(),若是點看裏面看具體的代碼比較複雜,因此我就只是看看執行后里面的變化
執行後是這樣的:
執行前locations這個Resource列表長度爲0,resourceResolvers這個ResourceResolver(資源解析器)的列表也是長度爲0,執行後這兩個列表都有了元素,有多少個元素這個根據狀況來決定(這個我也說的不是很清楚,能夠改變
紅框中的參數的個數來改變列表中的元素個數,具體狀況須要看handler.afterPropertiesSet()這個方法內部是怎麼實現的。
)
而後四句就是把請求的路徑/**(端口後面的全部靜態資源請求)和這個handler用map的鍵值對方式對應起來,最後兩句就是把咱們最後的urlMap放入這個handlerMapping中,而後返回給上一層
具體過程大概是這樣的,這裏面有寫說不清楚的多是由於我本人的語文很差,組織能力有點難,還有可能理解錯誤了,或者湖忽略了某些細節,若是有什麼問題或者糾正的,請在評論中告訴我,讓我和給位學習學習!!