第四章 認證
使用數據庫保存/查詢用戶數據,完成認證功能
4.1 方式一:重寫jdbcAuthentication規則(不推薦)
- 基於數據庫的RBAC查詢出我們需要的用戶以及這些用戶的權限(權限標識、角色)
- 創建和SpringSecurity要求一模一樣的表,然後用默認jdbcAuthentication
- 更新jdbcAuthentication裏面所有我們需要實際運行的sql
- authoritiesByUsernameQuery:根據用戶名查詢他權限的sql
- usersByUsernameQuery:根據用戶名查詢用戶的sql
- .......:更多的sql均可定義
4.1.1 使用默認的查詢用戶語句
auth.jdbcAuthentication().usersByUsernameQuery("zhangsan"); |
4.1.2 使用默認的查詢權限語句
auth.jdbcAuthentication().authoritiesByUsernameQuery("zhangsan"); |
4.2 方式二:自定義UserDetailsService檢索用戶
4.2.1 實現UserDetailService接口loadUserByUsername(String username)方法
4.2.2 實驗步驟
1 創建表結構
security實驗\security.sql
2 配置 configure(AuthenticationManagerBuilder auth)
@Autowired UserDetailsService userDetailsService;//用戶詳情查詢服務組件的接口
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //根據用戶名查詢出用戶的詳細信息 auth.userDetailsService(userDetailsService); } |
3 編寫UserDetailService實現:
- 接口及已有實現類
- 實現UserDetailService接口,提供自定義實現類
org.springframework.security.core.userdetails.UserDetailsService
package com.atguigu.security.component;
import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.core.authority.*; import org.springframework.security.core.userdetails.User; import org.springframework.stereotype.Service;
//按照用戶名查詢用戶詳情的接口 @Service public class AppUserDetailsServiceImpl implements UserDetailsService { @Autowired JdbcTemplate jdbcTemplate;
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { String queryUser = "SELECT * FROM `t_admin` WHERE loginacct=?";
//1、查詢指定用戶的信息 Map<String, Object> map = jdbcTemplate.queryForMap(queryUser, username);
//2、將查詢到的用戶封裝到框架使用的UserDetails裏面 return new User(map.get("loginacct").toString(), map.get("userpswd").toString(), AuthorityUtils.createAuthorityList("ADMIN","USER"));//暫時寫死,過後數據庫中查 } } |
4 運行測試結果,密碼不一致,跳轉到登錄頁,並提示錯誤消息
4.2.3 debug測試登錄-斷點調試
1 斷點-方法棧
2 自定義UserDetailService實現類
3 Dao層認證提供者: DaoAuthenticationProvider
Dao層認證提供者DaoAuthenticationProvider,用於調用自定義的UserDetailService實現類方法
4 抽象層用戶認證提供者: AbstractUserDetailsAuthenticationProvider
抽象層用戶認證提供者,獲取dao層查找的認證用戶信息,被封裝成UserDetails對象,User類是UserDetails接口實現類
1)org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.DefaultPreAuthenticationChecks認證用戶賬號是否被鎖定,是否啓用,是否過期;用戶表中可以增加這些字段。
2)public interface Authentication extends Principal 封裝表單提交的認證信息
認證用戶名和密碼;鹽值爲null
採用org.springframework.security.authentication.encoding.BasePasswordEncoder默認加密器對錶單提交明文加密(其實並沒有進行任何加密,明文無變化)
- 總結
4.3 基於數據庫(MD5密碼)認證 (debug)
使用數據庫保存/查詢用戶數據,完成認證功能
4.3.1 配置 configure(AuthenticationManagerBuilder auth)
org.springframework.security.crypto.password.PasswordEncoder
//測試:分析源碼(驗證密碼不一致) auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); |
4.3.2 引入MD5加密工具類:MD5Util.java
4.3.3 PasswordEncoder接口實現類:AppPasswordEncoder
@Service public class AppPasswordEncoder implements PasswordEncoder {
/** * 密碼加密的算法 */ @Override public String encode(CharSequence rawPassword) { String digestPwd = MD5Util.digestPwd(rawPassword.toString()); return digestPwd; }
/** * 比較登錄密碼和數據庫存儲密碼是否一致 * rawPassword:頁面的明文密碼 * encodedPassword:數據庫的密文密碼 */ @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { //使用自己的工具類 String digestPwd = MD5Util.digestPwd(rawPassword.toString()); return digestPwd.equals(encodedPassword); } } |
4.3.4 Debug測試,主要測試matches方法的調用過程
- 表單提交密碼:rawPassword
- 數據庫存儲密碼 :encodePassword
- 調用自定義密碼驗證器
- 密碼不一致,拋異常:Bad credentials ;密碼一致,通過認證
- 創建UsernamePasswordAuthenticationToken 對象,封裝認證信息
4.3.5 源碼參考
protected Authentication createSuccessAuthentication(Object principal,Authentication authentication, UserDetails user) { // Ensure we return the original credentials the user supplied, // so subsequent attempts are successful even with encoded passwords. // Also ensure we return the original getDetails(), so that future // authentication events after cache expiry contain the details UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken( principal, authentication.getCredentials(), authoritiesMapper.mapAuthorities(user.getAuthorities()) );//封裝用戶權限信息 result.setDetails(authentication.getDetails()); //封裝用戶信息 return result; } |
1 principal 認證主體-數據庫中查詢User數據
2 authentication.getCredentials() 認證密碼(表單中密碼)
3 authoritiesMapper.mapAuthorities(user.getAuthorities()) 認證權限集合
該用戶擁有的權限,暫時寫死在代碼中的,後期要根據用戶查詢所擁有的權限
4 認證細節:包括客戶端ip和sessionid
org.springframework.security.web.authentication.WebAuthenticationDetails
5 result對象(UsernamePasswordAuthenticationToken)詳細描述
4.4 基於數據庫(BCryptPasswordEncoder)密碼加密認證
4.4.1 PasswordEncoder接口
4.4.2 使用BCryptPasswordEncoder進行密碼加密
//推薦密碼加密器用這個BCryptPasswordEncoder; 將一個字符串加密成一個永不重複的密文 //1、加鹽+加隨機數 auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder()); |
4.4.3 本地測試:main方法
public static void main(String[] args) { BCryptPasswordEncoder pe = new BCryptPasswordEncoder();
//$2a$10$WzKk37ncOPynOSxyFGkxWu3ys7xaf7L/9uUhfVYVOCFTqeHkgJvOq //$2a$10$VmWwIx/uxNQabCYl3I5mZ.U9sQvpiM/xAhX69Skg0EWyDm3twQfcO //$2a$10$2Ig1mxqlb033XcU7aB0Ck.OZouRLsHUkJyIl9Mzi40FIY6grcEUr6 //大致的規律:$2a$10$+"xxx"+"/"+"xxx" String encode = pe.encode("123456"); System.out.println(encode); } |
4.4.4 服務器運行測試
將main方法生成的密文存儲到數據庫中(注意:userpswd字段長度),重新啓動服務器進行測試。