轉載:https://blog.csdn.net/qwe86314/article/details/89509765
如何使用SpringSecurity安全框架
一、首先在我們項目的pom.xml中引入SpringSecurity的核心依賴,本教程使用的是SpringBoot最新版本2.1.4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
當你引入這兩個依賴的時候,SpringSecurity已經幫你進行了簡單的配置,你重新運行項目的時候會發現在控制台有一串隨機的密碼
在項目中引入了SpringSecurity的依賴,默認會給你自動配置,默認的用戶名是user,密碼是每次啟動控制台的一串字符串,每次啟動這個密碼都不一樣。並且還會給你生成一個默認login登錄頁面
關於這個頁面,這個頁面是SpringSecurity官方提供的,但是沒有人會使用這個頁面來進行身份認證,都是自定義登錄的頁面來進行身份認證,但是處理登錄邏輯是使用的這個接口
二、創建包開始寫代碼,項目結構如下
要想使用它,我們要完成 SpringSecurity最基本的配置,首先我們創建一個類WebSecurityConfg來繼WebSecurityConfigurerAdapter這個適配器,關於這個適配器我不做深入的探討,繼承之后實現http的configure方法。
public class WebSecurityConfg extends WebSecurityConfigurerAdapter
這里注意的是,由於是一個配置類,我們需要在類上面加入配置的注解
@Configuration
實現HttpSecurity的configure方法
注意這里實現的是configure方法是HttpSecurity的方法
只是默認實現的話,我們還不能使用,我們還需要進行簡單的配置才能夠讓SpringSecurity發揮強大的作用。
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/500").permitAll() .antMatchers("/403").permitAll() .antMatchers("/404").permitAll() .anyRequest() //任何其它請求 .authenticated() //都需要身份認證 .and() .formLogin() //使用表單認證方式 .loginProcessingUrl("/login")//配置默認登錄入口 .and() .csrf().disable(); }
authorizeRequests定義那些url需要被保護,那些不需要進行保護,通常這個出來在配置的第一行,其中 antMatchers是設置路徑,通常這里設置的是控制器(controller)上的請求路徑,后面的permitAll是允許任何人訪問,沒有任何限制,上面的500、403、404我都設置的是任何人可以訪問,通常對公共的頁面我們都設置的permitAll來進行訪問的。
下面的anyRequest意思除了以上的請求,authenticated的意思是需要身份認證,那么anyRequest和authenticated合起來就是除了上面的500、403、404 請求不需要進行身份認證,其它的請求全部都需要進行身份認證后才能訪問。
and()方法類似於xml配置中的結束標簽,and()方法返回的對象還是HttpSecurity,當我們配置完成一段配置就要使用and來結束
formLogin是采用的是表單認證方式,還有一個httpBasic認證,你應用應該不會用httpBasic進行認證吧,現在都是采用的formLogin來進行認證的。
csrf.disable是 關閉跨站請求偽造保護
loginProcessingUrl是配置默認的登錄入口(這里強調一遍Spring security默認的處理登錄接口是/login這個自帶的接口)
我已經准備了404、403、500等頁面
@GetMapping("/404") public String notFoundPage() { return "404"; } @GetMapping("/403") public String accessError() { return "403"; } @GetMapping("/500") public String internalError() { return "500"; } @GetMapping("/success") @ResponseBody public String success(){ return "認證成功,進入success成功"; } @GetMapping(value = "/user/login") private String loginPage(){ return "login"; } @GetMapping(value = "/person") public String personPage(){ return "person"; } @GetMapping(value = "/admin/index") public String adminPage(){ return "admin/admin"; }
在resource目錄下的templates目錄中放置這些頁面,並適當的加入一些內容
當我們訪問500、403、404頁面的時候由於設置了permitAll,任何人都可以訪問,所以訪問是沒有任何問題。
但是當我們訪問一個除500、404、403的請求,那么會被重定向登錄頁,要求進行身份認證,身份認證成功后才能訪問
當我們訪問success這個請求的時候,發現被重定向到了login頁面要求進行身份認證,這正是 anyRequest和authenticated起了作用,我們在表單中輸入用戶user和密碼(控制台隨機生成的)填寫后 就訪問成功了
這樣就認證成功了,那么在很多的時候我們可能某些頁面只能管理員才能訪問,普通的用戶是不能訪問,那么就要在配置文件中配置
我們在配置中加入新的配置項
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/500").permitAll() .antMatchers("/404").permitAll() .antMatchers("/403").permitAll() .antMatchers("/admin/index").hasRole("ADMIN")//指定權限為ADMIN才能訪問 .antMatchers("/person").hasAnyRole("ADMIN","USER")//指定多個權限都能訪問 .anyRequest() .authenticated() .and() .formLogin()//使用表單認證方式 .loginProcessingUrl("/login")//配置默認登錄入口 .and() .csrf().disable(); }
這配置中我們新加了一個url配置/admin/index 並且用了hasRole, hasRole指定了特定的角色權限才能訪問該url,並且hasRole默認截取了前面部分的ROLE_ 所以只需要填寫ADMIN即可,無需像這樣ROLE_ADMIN這樣,除了這樣,我們還需要配置內存認證用戶等配置
hasAnyRole是指定了多個角色都能訪問這個url,角色已逗號隔開,這里不管是user或者admin賬戶登錄后都能訪問這個person頁面
/** * 自定義認證策略 * @return */ @Autowired public void configGlobal(AuthenticationManagerBuilder auth) throws Exception { String password = passwordEncoder().encode("123456"); logger.info("加密后的密碼:" + password); // auth.inMemoryAuthentication().withUser("admin").password(password) // .roles("ADMIN").and(); auth.inMemoryAuthentication().withUser("user").password(password) .roles("USER").and(); } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); }
我們需要實現configGlobal 安全策略認證方法,AuthenticationManagerBuilder用於通過允許AuthenticationProvider容易地添加來建立認證機制。以下定義了內置認證與內置的“用戶”和“管理”登錄,這里不深入去了解。
這里使用了對登錄的密碼進行加密處理用到了spring security最強大的 加密器 BCryptPasswordEncoder 隨機生成鹽(用戶的密碼和鹽混合在一起),每次生成的密碼鹽都不一樣,這也是spring security BCryptPasswordEncoder強大之處。我們對密碼進行了加密,使用了passwordEncoder中的encode進行了密碼的加密,這里我強調一次passwordEncoder有2個方法一個用於加密,一個用於解密,其中passwordEncoder.encode是用來加密的,passwordEncoder.matches是用來解密的。
//auth.inMemoryAuthentication().withUser("admin").password(password).roles("ADMIN").and(); auth.inMemoryAuthentication().withUser("user").password(password).roles("USER").and();
這里添加了2個不同角色權限的內存用戶,注意一個內存中只能同時有一個用戶,很多教程都同時添加了多個用戶
auth.inMemoryAuthentication().withUser("user").password(password) .roles("USER").and().withUser("admin").roles("ADMIN").password(password);
不推薦這樣的配置,這種配置系統運行時候內存同時有2個用戶,這樣是會出問題的,推薦添加內存用戶的時候一次添加一個,多個的時候只留一個其它的都注釋掉。
這樣的話我們就能使用內存的用戶進行權限的測試認證了
當我們運行項目,控制台就會打印加密后的密碼
注意的是這里的這個密碼代表的是123456,我們只需要在頁面上輸入123456就行,千萬別把這串加密的串復制到表單密碼框里
我們用這個user用戶 去訪問 admin/index頁面看看會發生什么情況
此時會到我自定義的403頁面,並且狀態碼是403,沒有權限訪問這個頁面,那么我們用admin賬號去訪問看看有什么效果
admin賬戶登錄,成功的進入了管理員權限的頁面。
自定義表單登錄頁面
如何自定義表單登錄?我們不可能用官方那個頁面,那個頁面屬實不好看,所以我們自定義登錄頁面,我寫了個簡單的html的登錄頁面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>標准登錄頁</title> </head> <body> <h2>標准登錄頁</h2> <h3>表單登錄</h3> <form action="/authentication/form" method="post"> <table> <tr> <td>用戶:</td> <td><input type="text" name="username"></td> </tr> <tr> <td>密碼:</td> <td><input type="password" name="password"></td> </tr> <tr> <td colspan="2"><button type="submit">登錄</button></td> </tr> </table> </form> </body> </html>
頁面寫好后就在controller配置訪問url就行了,下面我們在security配置
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/500").permitAll() .antMatchers("/404").permitAll() .antMatchers("/403").permitAll() .antMatchers("/user/login").permitAll()//自定義登錄頁面的url .antMatchers("/admin/index").hasRole("ADMIN")//指定權限為ADMIN才能訪問 .antMatchers("/person").hasAnyRole("ADMIN","USER") .anyRequest() .authenticated() .and() .formLogin()//使用表單認證方式 .loginProcessingUrl("/authentication/form")//配置默認登錄入口 .and() .csrf().disable(); }
需要注意的是loginProcessingUrl的url是登錄頁面中form action 的地址,這樣設置好后我們重啟服務,訪問/user/login就能看見自己自定義的登錄頁面並且也能登錄了。
這樣就可以通過寫css樣式來美化自定義的表單登錄頁,特別的方便。
如何處理登錄失敗、成功
如果用戶登錄失敗該怎么給用戶提示信息,登錄成功了又給什么信息,其實非常的簡單,只需要繼承2個類就行,一個是成功結果處理器,一個是失敗結果處理器
這個就是成功處理器,這里我沒有做任何邏輯處理,如果登錄成功了那么會進入到這個方法,並且會在控制台輸出登錄成功的日志。
@Component("myLoginSuccessHandler") public class MyLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { private Logger logger = LoggerFactory.getLogger(getClass()); @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { response.setContentType("application/json;charset=UTF-8"); logger.info("登錄成功"); //這里寫你登錄成功后的邏輯 } }
下面是登錄失敗的處理器
這個就是失敗處理器,這里我沒有做任何邏輯處理,如果登錄失敗了那么會進入到這個方法,並且會在控制台輸出登錄失敗的日志。
@Component("myLoginFailureHandler") public class MyLoginFailureHandler extends SimpleUrlAuthenticationFailureHandler { private Logger logger = LoggerFactory.getLogger(getClass()); @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { response.setContentType("application/json;charset=UTF-8"); logger.info("登錄失敗"); //這里寫你登錄失敗的邏輯 } }
那么寫好了這兩個類,我們就得在security中進行配置,最后的配置如下
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/500").permitAll() .antMatchers("/404").permitAll() .antMatchers("/403").permitAll() .antMatchers("/user/login").permitAll() .antMatchers("/admin/index").hasRole("ADMIN")//指定權限為ADMIN才能訪問 .antMatchers("/person").hasAnyRole("ADMIN","USER") .anyRequest() .authenticated() .and() .formLogin()//使用表單認證方式 .loginProcessingUrl("/authentication/form")//配置默認登錄入口 .successHandler(myLoginSuccessHandler)//使用自定義的成功結果處理器 .failureHandler(myLoginFailureHandler)//使用自定義失敗的結果處理器 .and() .csrf().disable(); } /** * 自定義認證策略 * * @return */ @Autowired public void configGlobal(AuthenticationManagerBuilder auth) throws Exception { String password = passwordEncoder().encode("123456"); logger.info("加密后的密碼:" + password); auth.inMemoryAuthentication().withUser("admin").password(password) .roles("ADMIN").and(); // auth.inMemoryAuthentication().withUser("user").password(password) // .roles("USER").and(); } @Autowired private AuthenticationSuccessHandler myLoginSuccessHandler; //認證成功結果處理器 @Autowired private AuthenticationFailureHandler myLoginFailureHandler; //認證失敗結果處理器 @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
注意這里注入成功、失敗處理器一個是AuthenticationSuccessHandler,一個是AuthenticationFailureHandler,並且名字要和@Component的名字 一致
那么我們來進行測試,輸入一個錯誤的密碼或者不存在的賬戶,會發生什么錯誤啊,那么我們來進行測試
可以看到這里登錄失敗了,進入了失敗結果處理器
我們來用一個正確的密碼用戶測試一下
可以看到這里登錄成功,進入了成功結果處理器,就能訪問相關的服務了。那么講完了基本的使用和認證的處理,那么我們來講一下注解權限@PreAuthorize() 這個注解設置了就可以不用在security中配置.hasRole,hasAnyRole
列如如下配置
@PreAuthorize("hasAnyAuthority('ROLE_ADMIN')") @GetMapping(value = "/admin/index") public String adminPage(){ return "admin/admin"; }
這樣設置了一樣可以進行身份認證,但是如果security中配置了同一個url,那么security中配置要優先於使用@PreAuthorize配置的,這兩種方式都能實現身份認證,看自己怎么選擇。
@PreAuthorize的其它參數
hasRole (當前用戶是否擁有指定角色)
hasAnyRole(多個角色是一個以逗號進行分隔的字符串。如果當前用戶擁有指定角色中的任意一個則返回true)
hasAuthority(和hasRole一樣的效果)
hasAnyAuthority(和hasAnyRole一樣的效果)
hasPermission(不常用)里面有2個參數的,有三個參數的如下
@PreAuthorize(“hasPermission(‘user’, ‘ROLE_USER’)”)
@PreAuthorize(“hasPermission(‘targetId’,‘targetType’,‘permission’)”)
安全框架的原理
本文的最后給大家講一下SpringSecurity的基本原理,便於加深對這個安全框架的理解,日后便於學習Shiro框架做對比
SpringSecurity其實是一組filter,所有訪問的請求都會經過spring security的過濾器,然后返回、
其中圖片上最核心的是綠色的過濾器,是用來認證用戶的身份,每一塊代表一個過濾器,UsernamePasswordAuthenticationFilter是用來處理表單登錄認證的,而BasicAuthenticationFilter是用來處理HttpBasic認證登錄的。
這些綠色的過濾器主要是工作是檢查你當前的請求是否有過濾器需要的信息,比如UsernamePasswordAuthenticationFilter這個過濾器,如果你當前的請求是登錄的請求,並且帶了用戶名和密碼,那么就會進這個過濾器進行處理,如果當前的請求是登錄請求但是沒有帶用戶名和密碼,那么就會放過去給下一個過濾器,比如下一個過濾器是BasicAuthenticationFilter,那么它就會檢查當前的請求頭有沒有Basic開頭的信息,如果有的話會嘗試拿出來做Basic64解碼,然后取出用戶名和密碼嘗試去登錄,在SpringSecurity中還有很多的過濾器,它會嘗試一個一個的往下走,任何一個過濾器成功的完成了用戶認證,它會做一個標記,經過這些綠色的過濾器之后,最終會到一個橙色的過濾(FilterSecurityInterceptor),這個過濾器是SpringSecurity過濾器鏈最后一環,它是最后的守門人,在它身后就是我們自己寫的controller rest 服務了,這個過濾器決定了你能不能訪問后面的rest 服務了,那么它依據什么來判斷呢?它是依據我們security中的配置來判斷的,如果通過了就能訪問服務,沒有通過就拋出不同的異常,比如只有vip用戶能夠訪問頁面,用普通用戶雖然認證成功了,但是你沒有vip用戶的權限,那么就會拋出權限的異常。在異常拋出之后,在FilterSecurityInterceptor的前面還有一個ExceptionTranslationFilter過濾器,這個過濾器就是用來捕獲FilterSecurityInterceptor拋出的異常,這個過濾器會根據拋出的異常來進行處理器。
這里注意的圖片上綠色過濾器我們是可以通過配置來覺得是否生效,其它顏色的過濾器我們都是不能控制的,並且也不能交換位置。
最后希望大家越來越牛逼,早日成為大神。
本文demo的地址:https://github.com/zhoubiao188/spring-security-auth
————————————————
原文鏈接:https://blog.csdn.net/qwe86314/java/article/details/89509765