首先,希望還對 spring-security框架完全不懂的新手 下載下Git源碼。 引入到項目中。這個短文就是邊看源碼邊聊的。也會啟動下項目驗證自己的推想。
一、登陸認證的登陸配置項
<form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?error=true" default-target-url="/index.ht" username-parameter="username" password-parameter="password" login-processing-url="/j_spring_security_check"/> <logout logout-url="/logout.ht"/>
看到這個配置,其實就大略明白了。 這就像配置了一個control/Servlet, userName 參數名字為 ”name“,password 為”password“
然后校驗用戶密碼,通過就跳轉的頁面為 index.ht 。
spring-security框架維護了一個過濾器鏈來提供服務, 而<form-login/> 這個登陸配置項其實創建了一個名為UsernamePasswordAuthenticationFilter的過濾器 。
框架提供的這些過濾器,也包括<custom-filter/>配置的過濾器。都是通過假名有嚴格順序來執行的。稍后詳細介紹自定義過濾器。
UsernamePasswordAuthenticationFilter :
正如我們配置的這些參數,也會有默認配置的 比如
usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY =“j_username”,
passwordParameter=“j_password”
默認接受form請求地址 :j_spring_security_check ,
這些可配置的參數,都會存在默認參數。這些參數的讀取是在 Initializing Spring root WebApplicationContext 后,加載並且解析xml配置文件。然后初始化ioc 容器。形成上面所提的過濾器鏈。
二、簡單說一下解析xml 過程
HttpSecurityBeanDefinitionParser.parse() { filterChains.add(createFilterChain(element, pc)); }
createFilterChain方法會調用 AuthenticationConfigBuilder的構造方法 初始化各種filter createFormLoginFilter(sessionStrategy, authenticationManager); 即為登錄配置信息xml的解析處理方法:

SecurityNamespaceHandler.parse(Element element, ParserContext pc) //
關鍵代碼:
String name = pc.getDelegate().getLocalName(element);
BeanDefinitionParser parser = parsers.get(name);
通過配置項的名字。以策略模式獲取到專用解析器 都實現自BeanDefinitionParser 接口, 通過父類的引用執行子類的具體實現。調用這些子類的parse()方法eg:RememberMeBeanDefinitionParser,LogoutBeanDefinitionParser等、、
<form-login/> 的解析在 FormLoginBeanDefinitionParser, 解析后拿到配置的參數,然后初始化一個filter 。
不知道這個解析方法為啥沒有實現BeanDefinitionParser 。 本來不想貼代碼的。更想願意讀的人自己下載源碼自己看。
三、獲取登陸獲取賬號密碼
切面走到這個登陸過濾器的時候 UsernamePasswordAuthenticationFilter.attemptAuthentication() 的方法 會從request中,通過配置的userNameParam ,passwordParam 獲取 name password
然后構造一個包裝了密碼賬號的對象: new UsernamePasswordAuthenticationToken(password,username)
然后調用接口 AuthenticationManager.authenticate() (認證管理類中的一個實現類 ProviderManager的認證方法)
四、配置驗證密碼的認證管理類
配置認證管理類 AuthenticationManager 需要 給它提供了一個 user-service bean 來通過用戶名獲取用戶
這個userDetailProvider bean 需要實現 UserDetailsService接口 提供一個 loadUserByUsername()方法。
配置項:
<security:authentication-manager alias="authenticationManager"> <security:authentication-provider user-service-ref="userDetailProvider"/> </security:authentication-manager>
<bean id="userDetailProvider" class="com.hotent.web.security.provider.UserAuthProvider"/>
然后 從ProviderManager 中的 List<AuthenticationProvider> providers認證策略都 拿出來 進行認證(虛)
AbstractUserDetailsAuthenticationProvider .authenticate()
retrieveUser() // 調用子類DaoAuthenticationProvider的實現方法
DaoAuthenticationProvider.retrieveUser() 會通過我之前配置的userDetailProvider.loadUserByUsername(username),獲取用戶,
然后preAuthenticationChecks.check(user); 校驗用戶是否可用、鎖定、過期
然后調用additionalAuthenticationChecks()方法驗證密碼。
五、配置 密碼加密方式
接着我登陸不上才發現沒有配置密碼的加密類型。隨便找了 個文檔。配了下、居然發現啟動不了,妹的。
還好我比較機智,找到了xsd校驗文件
順利找到了正確配置方法,在authentication-provider element下、 有一個password-encoder xs:element
這個element 有個attribute<xs:attributeGroup ref="security:password-encoder.attlist"/>,這個想必就是spring-security所支持的所有加密類型了。那xml 就改成了 這樣
最終authenticationManager的配置 如下
<security:authentication-manager alias="authenticationManager"><!-- 鑒定管理類 --> <security:authentication-provider user-service-ref="userDetailProvider"> <security:password-encoder hash="sha-256"/> </security:authentication-provider> </security:authentication-manager>
其實很少有人這么傻着從校驗文件 去查屬性的。 除了像我這種機智到二的人。 其實官方文檔說的很清楚,而且xml也會有提示。但是我抱着探究的態度還是直接看xsd。
這樣密碼加密校驗就通過。
接着將成功登陸用戶,和用戶信息發布出一個事件
applicationEventPublisher.publishEvent(new AuthenticationSuccessEvent(authentication));
///
六、登陸的擴展
很多時候,我們希望做更多的擴展、比如加一些U盾之類的口令啦、短信校驗啦。驗證碼啦。 那么要實現、可以加些自定義的過濾器,也可以重寫一些方法,等等、初次探究,我現在還不夠清楚。 不過這些都略有些麻煩。
其實如果你自己去校驗用戶。然后將用戶登錄信息放入SecurityContext 里面 也就可以隨心所欲了。
如下圖 驗證賬號密碼、驗證碼 的截圖略過 !直接是驗證密碼后的操作。
關鍵代碼
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, encrptPassword); authRequest.setDetails(new WebAuthenticationDetails(request)); SecurityContext securityContext = SecurityContextHolder.getContext(); Authentication auth = authenticationManager.authenticate(authRequest); securityContext.setAuthentication(auth); sessionStrategy.onAuthentication(auth, request, response);
注入要使用的類
@Resource(name = "authenticationManager") private AuthenticationManager authenticationManager = null; @Resource UserDetailsService userDetailsService; @Resource private SessionAuthenticationStrategy sessionStrategy= new NullAuthenticatedSessionStrategy();
注入的authenticationManager 其實就是之前寫到的 ProviderManager
我們直接使用用戶名,密碼(加密后) 構建了UsernamePasswordAuthenticationToken(為存放密碼賬號信息的一個令牌) 是Authentication接口的一個實例。然后把構建好的 authentication 發送給authenticationManager 進行校驗 。
authenticationManager 成功校驗后,返回一個完全的 Authentication 實例。
SecurityContextHolder.getContext().setAuthentication(...)
Spring Security 並不知道你怎么把Authentication 對象 放到了SecurityContextHolder 里面,唯一關鍵點就是 “SecurityContextHolder 已經包含了一個 Authentication 標識了一個主體”。 這樣就能滿足AbstractSecurityInterceptor 之前的驗證一個用戶需要。
所以、這個authentication放入SecurityContextHolder的過程可以有很多種方式、一個過濾器、一個control方法、就能達到目的,自然就實現了登陸。
有空繼續