手把手带你撸一把springsecurity框架源码中的认证流程
提springsecurity之前,不得不說一下另外一個輕量級的安全框架Shiro,在springboot未出世之前,Shiro可謂是頗有統一J2EE的安全領域的趨勢。
有關shiro的技術點
1、shiro之權限管理的概念
2、shiro之第一個程序認證
3、shiro之自定義realm
4、shiro認證+授權(使用MD5+salt加密)
5、shiro+springboot 圖解分析思路
6、Shiro+springboot+mybatis(md5+salt+散列)認證與授權-01
7、Shiro+springboot+mybatis(md5+salt+散列)認證與授權-02
8、Shiro+springboot+mybatis+EhCache(md5+salt+散列)認證與授權-03
一、springsecurity因springboot而火
1.1.
Spring Security 并非一個新生的事物,它最早不叫 Spring Security ,叫 Acegi Security,叫 Acegi Security 并不是說它和 Spring 就沒有關系了,它依然是為 Spring 框架提供安全支持的。事實上,Java 領域的框架,很少有框架能夠脫離 Spring 框架獨立存在。(spring真的是碉堡了呀)
1.2.
當 Spring Security 還叫 Acegi Security 的時候,流傳著這樣一句話,“每當有人要使用 Acegi Security,就會有一個精靈死去”,從這你就可以感覺到,其中的配置是多繁瑣;
1.3.
之后Acegi Security投入了spring的窩里,(呵,這波操作可以呀),然后你懂的,研發團隊的小凸凸們開始精簡繁雜的xml配置;雖然比之前簡化了很多,但一直沒火起來,(這一波spring只能在心里默默的說:你是真的帶不動,啥也不是;)
1.4.
直到有一天,springboot這個二愣子突然出現在了封建社會中,徹底顛覆了J2EE的世界,“約定大于配置 ”成為springboot的代名詞。一人得道,雞犬升天,連帶著把spring家族的產品都帶了一把,springsecurity就是其中之一。
1.5.
當前springboot/springcloud是J2EE中主流的技術棧(springboot不是一個新的框架,他是對spring的擴展,是為了高效開發而生)
spring是對Java代碼的封裝,springboot可以說又對spring進行了封裝,屏蔽了內部的細節,讓開發人員專注于業務邏輯;缺點就是封裝太深,學習成本高。
推薦兩種搭配
1.springboot+springcloud+springsecurity
2.SSM+shiro
二、代碼準備工作
2.1.使用初始化向導快速搭建springboot項目
2.2.編寫mapper、dao、文件
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.itz.security.mapper.UserMapper"><select id="findPasswordByUsername" resultType="Users">select * from users where username=#{usernaem}</select> </mapper>UserMapper接口
public interface UserMapper {Users findPasswordByUsername(String username); }2.3.service層編寫
至于為什么要實現UserDetailService這個接口,后面會詳細說
package com.itz.security.service;import com.itz.security.entity.Users; import com.itz.security.mapper.UserMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service;/*** @author:抱著魚睡覺的喵喵* @date:2021/3/24* @description:*/ @Service(value = "userDetailService") @Slf4j public class MyUserDetailService implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate PasswordEncoder passwordEncoder;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {log.info("用戶名:"+username);Users user = userMapper.findPasswordByUsername(username);if (user == null) {throw new UsernameNotFoundException("沒有該賬戶!");}String password = passwordEncoder.encode(user.getPassword());log.info("加密后的密碼為:"+password);return new User(user.getUsername(),password, AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRole()));} }2.4.實體類編寫
只是簡單的模擬,就沒必要太復雜。
注意:不要使用User這個名字,security中有這個類,防止沖突。
2.5.controller層編寫
@RestController public class HelloController {@GetMapping("/test")public String hello() {return "HELLO";} }2.6.login.html
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title> </head> <body><form action="login.html" method="post">username: <input type="text" name="username"> <br>password: <input type="password" name="password"> <br><input type="submit" value="提交"></form> </body> </html>2.7.編寫SecurityConfig文件
至于為什么是這樣,后面源碼分析時會說
package com.itz.security.controller;import lombok.NoArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;/*** @author:抱著魚睡覺的喵喵* @date:2021/3/24* @description:*/ @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsService userDetailsService;@Beanpublic PasswordEncoder getPasswordEncoder() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(getPasswordEncoder());}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin().loginPage("/login.html").and().csrf().disable();//關閉csrf(一種web攻擊手段)}@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/js/**","/css/**","/images/**");} }2.8.application.yml編寫
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverpassword: hao20001010username: rooturl: jdbc:mysql://localhost:3306/crud?serverTimezone=UTCmybatis:mapper-locations: /mapper/**type-aliases-package: com.itz.security.entity2.9.數據庫表設計
注意掃描mapper接口
三、源碼認證流程分析
首先會使用debug調試,最基本的F7和F8以及F9,當然鼠標點擊也一樣
F7:調試的時候遇到方法體會進入到方法體內部執行
F8:遇到方法體不會進入到方法體內部,只會依次執行
F9:只會執行打斷點的地方
3.1. ctrl+N 查看UsernamePasswordAuthenticationFilter類的源碼,在attemptAuthentication方法上打上斷點,bebug模式下啟動
當走到82行,這個令牌類UsernamePasswordAuthenticationToken
點擊F7 debug進入該類中查看執行情況
你可以把這個令牌類UsernamePasswordAuthenticationToken當作一個實體類,用來將前端傳來的變量賦值給本地變量;簡單了說就是將其封裝
繼續F8之后,就會返回到UsernamePasswordAuthenticationFilter類中
點擊F7,進入查看setDetails方法的源碼
擊F7查看buildDetails方法
擊F7查看WebAuthenticationDetails類的具體細節
原來這個setDetails方法主要是為了將請求中的額外信息保存起來。
下面要進入AbstractUserDetailsAuthenticationProvider類了
點擊F7查看authenticate方法的具體實現細節
通過AuthenticationProvicer接口的實現類獲取用戶的登錄方式,然后通過for循環,查看是否支持該登錄方式;(一般的登錄方式有vx,qq,表單等)
如果不支持支持該登錄方式你會發現
從表面意思我們也可以猜到,調用父級提供Provider,重新執行該authenticate方法,看是否支持該登錄方式
當支持該登錄方式之后,
然后父類會調用authenticate對用戶的身份進行認證(也就是那個支持登錄方式的父類)
F7查看具體的認證細節
重點來了
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);retrieveUser是AbstractUserDetailsAuthenticationProvider抽象類的繼承類DaoAuthenticationProvider類中的方法
作用是從數據庫或者緩存中獲取用戶信息
F7查看源碼
可以發現它加載了我自定義的MyUserDetailService類,為什么呢?當然是因為我實現了UserDetailService接口,接下來的debug到哪個類你也因該明白了(多態)
沒錯就是我自定義的MyUserDetailService類
還是自己寫的代碼香哈😀
在39行F7進入查看源碼,然后一直F8返回到上一級的調用處
return loadedUser執行之后就會返回到AbstractUserDetailsAuthenticationProvider類中的authenticate方法中
this.preAuthenticationChecks.check(user);這個方法主要是對用戶狀態進行檢測,看是否可用,過期,鎖定等
下面是它的方法
matches方法具體源碼如下,其中BCrypt.checkpw中的方法不再展示,主要是干嘛的,下面已說明
放行
自此認證流程就分析結束了
總結認證流程
1.首先進入到UsernamePasswordAuthenticationFilter類中的attemptAuthentication方法
1.1.將用戶登錄信息封裝到UsernamePasswordToken令牌中
1.2.setDetails將請求中額外的信息封裝到WebAuthenticaitonDetails
1.3.調用AuthenticationManager接口的實現類ProviderManager中的authenticate方法
2.在ProviderManager類中的authenticate進行執行
2.1.匹配支持的登錄方式
2.2.匹配成功后,進入到AuthenticationProvider接口的實現類AbstractUserDetailsAuthenticationProvider中的authenticate方法
2.3.在authenticate方法中,獲取用戶認證信息,然后進行校驗是否過期,最后進行密碼的匹配
簡單了說就上面兩點,但是細節還是有很多的😁😁
希望有服務端大佬指點迷津😙
總結
以上是生活随笔為你收集整理的手把手带你撸一把springsecurity框架源码中的认证流程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springboot+maven实现模块
- 下一篇: IDEA-2020版本 Gradle项目