介紹
Spring Security 是一個高度自定義的安全框架。利用 Spring IoC/DI 和 AOP 功能,為系統提供了聲明式安全訪問控制功能,減少了為系 統安全而編寫大量重復代碼的工作。主要實現兩個功能:
- 用戶登錄的控制
- 登錄后權限的控制
使用
引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
引入了依賴,其實就已經完成了配置,當我請求我們所要進入的頁面時,都會先進行登錄的驗證,才會跳轉到我們訪問的頁面。但是在實際的使用中,我們一般會跳轉到我們請求的頁面做安全的控制以及權限的控制,因此我們需要自定義對應的類。
UserDetailsService接口
當什么也沒有配置的時候,賬號和密碼是由 Spring Security 定義 生成的。而在實際項目中賬號和密碼都是從數據庫中查詢出來的。所 以我們要通過自定義邏輯控制認證邏輯。
三個參數具體解釋:
username:用戶名,這里默認提交表單中的 name 必須叫username
password:密碼
authorities:用戶具有的權限。此處不允許為 null
此處的用戶名應該是客戶端傳遞過來的用戶名。而密碼應該是從 數據庫中查詢出來的密碼。Spring Security 會根據 User 中的 password 和客戶端傳遞過來的 password 進行比較。如果相同則表示認證通過, 如果不相同表示認證失敗。authorities 里面包含的所有內容為此用戶具有的權限,如有里面沒有包含某個權限,而在做 某個事情時必須包含某個權限則會出現 403。通常都是通過 AuthorityUtils.commaSeparatedStringToAuthorityList(“”) 來 創 建 authorities 集合對象的。參數時一個字符串,多個權限使用逗號分隔。
如果需要自定義邏輯時,只需要實現 UserDetailsService 接口即可,就可以進行對應的登錄配置。
BCryptPasswordEncoder
BCryptPasswordEncoder 是 Spring Security 官方推薦的密碼解析器,平時多使用這個解析器。BCryptPasswordEncoder 是對 bcrypt 強散列方法的具體實現。是 基於 Hash 算法實現的單向加密。
encode():把參數按照特定的解析規則進行解析。
matches()驗證從存儲中獲取的編碼密碼與編碼后提交的原始密碼是否匹配。如果密碼匹配,則返回 true;如果不匹配,則返回 false。 第一個參數表示需要被解析的密碼。第二個參數表示存儲的密碼。
//加密的使用
public void test(){
BCryptPasswordEncoder pe = new BCryptPasswordEncoder();
String encode = pe.encode("123");//加密
System.out.println(encode);
boolean matches = pe.matches("1234", encode);//原數據和加密匹配
System.out.println(matches);//false
}
自定義登錄邏輯
當進行自定義登錄邏輯時需要用到之前講解的 UserDetailsService 和 PasswordEncoder。但是 Spring Security 要求:當進行自定義登錄邏輯時容器內必須有 PasswordEncoder 實例。因此通過創建配置類注入該對象
- PasswordEncoder配置類
@Configuration
public class SecurityConfig{
@Bean
public PasswordEncoder getPe() {
return new BCryptPasswordEncoder();
}
}
- 編寫登錄用戶邏輯,需要實現UserDetailsService
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder encoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1.查詢數據庫,查詢用戶名是否存在,如果不存在拋出UsernameNotFoundException
if (!username.equals("admin")){
throw new UsernameNotFoundException("用戶名不存在");
}
//2.把查詢出來的密碼進行解析,或直接把構造方法放到構造方法中
//password 就是數據庫中查詢出來的密碼,查詢內容不是 123
String password = encoder.encode("123");//加密后的數據,這里是模擬數據庫中的密碼
//三個參數:用戶名,數據庫加密后的密碼,實現的權限,角色,可訪問的頁面
return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_adminN,/main.html,/main1.html"));
}
}
自定義頁面邏輯
簡單說就是,你要設置那個頁面做主頁,那些頁面不用驗證,那些頁面需要認證,那個角色或者權限能訪問那個頁面
該。配置類需要繼承 WebSecurityConfigurerAdapte,並重寫 configure 方法。
-
常用的配置中調用的方法
successForwardUrl()登錄成功后跳轉地址,使用 successForwardUrl()時表示成功后轉發請求到地址。內部是通過 successHandler()方法進行控制成功后交給哪個類進行處理。ForwardAuthenticationSuccessHandler 內部就是最簡單的請求轉發。由於是請求轉發,當遇到需要跳轉到站外或在前后端分離的項目中就無法使用了。
當需要控制登錄成功后去做一些事情時,可以進行自定義認證成功控制器。這里以成功控制器為例,自定義認證失敗控制器只需要繼承AuthenticationFailureHandler即可
//自定義登錄成功處理器 public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private String url; public MyAuthenticationSuccessHandler(String url) { this.url = url; } @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { System.out.println(httpServletRequest.getRemoteAddr()); User user = (User) authentication.getPrincipal(); System.out.println(user.getPassword());//默認 null System.out.println(user.getAuthorities());//權限 httpServletResponse.sendRedirect(url); } }
loginPage() 登錄頁面
loginProcessingUrl 登錄頁面表單提交地址,此地址可以不真實存在。
antMatchers():匹配內容 permitAll():允許
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MyAccessDeniedHandler myAccessDeniedHandler; @Autowired private UserDetailServiceImpl userDetailService; @Autowired private DataSource dataSource; @Autowired private PersistentTokenRepository repository; @Override protected void configure(HttpSecurity http) throws Exception { System.out.println("執行UserDetailServiceImpl"); //表單認證(登錄) http.formLogin() .loginProcessingUrl("/login")//當發現/login時認為是登錄,需要執行UserDetailServiceImpl .successForwardUrl("/toMain")//此處是一個 post 請求不能寫.html 頁面,靜態資源只能走 get 請求 //自定義跳轉 // .successHandler(new MyAuthenticationSuccessHandler("/main.html")) .failureHandler(new MyAuthenticationFailHandler("/fail.html")) //.failureForwardUrl("/fail") .loginPage("/showLogin");//登錄頁面 //url 攔截(授權) http.authorizeRequests() .antMatchers("/showLogin", "/fail.html").permitAll()//login.html 不需要被認證 // .mvcMatchers("/login.html").servletPath("/hello").permitAll() // .antMatchers("/main1.html").hasIpAddress("0:0:0:0:0:0:0:1")//具有其中一個就能訪問 //.anyRequest().access("@myServiceImpl.hasPermission(request,authentication)");//所有的請求都必須被認證,也就是必須登錄后才能訪問 .anyRequest().authenticated(); //關閉 csrf // http.csrf().disable(); //異常 http.exceptionHandling() .accessDeniedHandler(myAccessDeniedHandler); http.rememberMe() //.tokenValiditySeconds()設置有效時間默認 2 周 .userDetailsService(userDetailService)//用戶登錄邏輯寫在那個對象中 .tokenRepository(repository); //退出 http.logout() .logoutSuccessUrl("/showLogin"); } @Bean public PersistentTokenRepository getPer(){ JdbcTokenRepositoryImpl jt = new JdbcTokenRepositoryImpl(); jt.setDataSource(dataSource);//需要一個數據源 //jt.setCreateTableOnStartup(true);//第一次需要使用 return jt; }