新聞中心
上篇文章跟大家聊了如何使用更加優(yōu)雅的方式自定義 Spring Security 登錄邏輯,更加優(yōu)雅的方式可以有效避免掉自定義過濾器帶來的低效,建議大家一定閱讀一下,也可以順便理解 Spring Security 中的認(rèn)證邏輯。

網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)!專注于網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、成都小程序開發(fā)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了平山免費(fèi)建站歡迎大家使用!
本文將在上文的基礎(chǔ)上,繼續(xù)和大家探討如何存儲(chǔ)登錄用戶詳細(xì)信息的問題。
1.Authentication
Authentication 這個(gè)接口前面和大家聊過多次,今天還要再來聊一聊。
Authentication 接口用來保存我們的登錄用戶信息,實(shí)際上,它是對主體(java.security.Principal)做了進(jìn)一步的封裝。
我們來看下 Authentication 的一個(gè)定義:
- public interface Authentication extends Principal, Serializable {
- Collection extends GrantedAuthority> getAuthorities();
- Object getCredentials();
- Object getDetails();
- Object getPrincipal();
- boolean isAuthenticated();
- void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
- }
接口的解釋如下:
- getAuthorities 方法用來獲取用戶的權(quán)限。
- getCredentials 方法用來獲取用戶憑證,一般來說就是密碼。
- getDetails 方法用來獲取用戶攜帶的詳細(xì)信息,可能是當(dāng)前請求之類的東西。
- getPrincipal 方法用來獲取當(dāng)前用戶,可能是一個(gè)用戶名,也可能是一個(gè)用戶對象。
- isAuthenticated 當(dāng)前用戶是否認(rèn)證成功。
這里有一個(gè)比較好玩的方法,叫做 getDetails。關(guān)于這個(gè)方法,源碼的解釋如下:
Stores additional details about the authentication request. These might be an IP address, certificate serial number etc.
從這段解釋中,我們可以看出,該方法實(shí)際上就是用來存儲(chǔ)有關(guān)身份認(rèn)證的其他信息的,例如 IP 地址、證書信息等等。
實(shí)際上,在默認(rèn)情況下,這里存儲(chǔ)的就是用戶登錄的 IP 地址和 sessionId。我們從源碼角度來看下。
2.源碼分析
松哥的 SpringSecurity 系列已經(jīng)寫到第 12 篇了,看了前面的文章,相信大家已經(jīng)明白用戶登錄必經(jīng)的一個(gè)過濾器就是 UsernamePasswordAuthenticationFilter,在該類的 attemptAuthentication 方法中,對請求參數(shù)做提取,在 attemptAuthentication 方法中,會(huì)調(diào)用到一個(gè)方法,就是 setDetails。
我們一起來看下 setDetails 方法:
- protected void setDetails(HttpServletRequest request,
- UsernamePasswordAuthenticationToken authRequest) {
- authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
- }
UsernamePasswordAuthenticationToken 是 Authentication 的具體實(shí)現(xiàn),所以這里實(shí)際上就是在設(shè)置 details,至于 details 的值,則是通過 authenticationDetailsSource 來構(gòu)建的,我們來看下:
- public class WebAuthenticationDetailsSource implements
- AuthenticationDetailsSource
{ - public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
- return new WebAuthenticationDetails(context);
- }
- }
- public class WebAuthenticationDetails implements Serializable {
- private final String remoteAddress;
- private final String sessionId;
- public WebAuthenticationDetails(HttpServletRequest request) {
- this.remoteAddress = request.getRemoteAddr();
- HttpSession session = request.getSession(false);
- this.sessionId = (session != null) ? session.getId() : null;
- }
- //省略其他方法
- }
默認(rèn)通過 WebAuthenticationDetailsSource 來構(gòu)建 WebAuthenticationDetails,并將結(jié)果設(shè)置到 Authentication 的 details 屬性中去。而 WebAuthenticationDetails 中定義的屬性,大家看一下基本上就明白,這就是保存了用戶登錄地址和 sessionId。
那么看到這里,大家基本上就明白了,用戶登錄的 IP 地址實(shí)際上我們可以直接從 WebAuthenticationDetails 中獲取到。
我舉一個(gè)簡單例子,例如我們登錄成功后,可以通過如下方式隨時(shí)隨地拿到用戶 IP:
- @Service
- public class HelloService {
- public void hello() {
- Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
- WebAuthenticationDetails details = (WebAuthenticationDetails) authentication.getDetails();
- System.out.println(details);
- }
- }
這個(gè)獲取過程之所以放在 service 來做,就是為了演示隨時(shí)隨地這個(gè)特性。然后我們在 controller 中調(diào)用該方法,當(dāng)訪問接口時(shí),可以看到如下日志:
- WebAuthenticationDetails@fffc7f0c: RemoteIpAddress: 127.0.0.1; SessionId: 303C7F254DF8B86667A2B20AA0667160
可以看到,用戶的 IP 地址和 SessionId 都給出來了。這兩個(gè)屬性在 WebAuthenticationDetails 中都有對應(yīng)的 get 方法,也可以單獨(dú)獲取屬性值。
3.定制
當(dāng)然,WebAuthenticationDetails 也可以自己定制,因?yàn)槟J(rèn)它只提供了 IP 和 sessionid 兩個(gè)信息,如果我們想保存關(guān)于 Http 請求的更多信息,就可以通過自定義 WebAuthenticationDetails 來實(shí)現(xiàn)。
如果我們要定制 WebAuthenticationDetails,還要連同 WebAuthenticationDetailsSource 一起重新定義。
結(jié)合上篇文章的驗(yàn)證碼登錄,我跟大家演示一個(gè)自定義 WebAuthenticationDetails 的例子。
上篇文章我們是在 MyAuthenticationProvider 類中進(jìn)行驗(yàn)證碼判斷的,回顧一下上篇文章的代碼:
- public class MyAuthenticationProvider extends DaoAuthenticationProvider {
- @Override
- protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
- HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
- String code = req.getParameter("code");
- String verify_code = (String) req.getSession().getAttribute("verify_code");
- if (code == null || verify_code == null || !code.equals(verify_code)) {
- throw new AuthenticationServiceException("驗(yàn)證碼錯(cuò)誤");
- }
- super.additionalAuthenticationChecks(userDetails, authentication);
- }
- }
不過這個(gè)驗(yàn)證操作,我們也可以放在自定義的 WebAuthenticationDetails 中來做,我們定義如下兩個(gè)類:
- public class MyWebAuthenticationDetails extends WebAuthenticationDetails {
- private boolean isPassed;
- public MyWebAuthenticationDetails(HttpServletRequest req) {
- super(req);
- String code = req.getParameter("code");
- String verify_code = (String) req.getSession().getAttribute("verify_code");
- if (code != null && verify_code != null && code.equals(verify_code)) {
- isPassed = true;
- }
- }
- public boolean isPassed() {
- return isPassed;
- }
- }
- @Component
- public class MyWebAuthenticationDetailsSource implements AuthenticationDetailsSource
{ - @Override
- public MyWebAuthenticationDetails buildDetails(HttpServletRequest context) {
- return new MyWebAuthenticationDetails(context);
- }
- }
首先我們定義 MyWebAuthenticationDetails,由于它的構(gòu)造方法中,剛好就提供了 HttpServletRequest 對象,所以我們可以直接利用該對象進(jìn)行驗(yàn)證碼判斷,并將判斷結(jié)果交給 isPassed 變量保存。如果我們想擴(kuò)展屬性,只需要在 MyWebAuthenticationDetails 中再去定義更多屬性,然后從 HttpServletRequest 中提取出來設(shè)置給對應(yīng)的屬性即可,這樣,在登錄成功后就可以隨時(shí)隨地獲取這些屬性了。
最后在 MyWebAuthenticationDetailsSource 中構(gòu)造 MyWebAuthenticationDetails 并返回。
定義完成后,接下來,我們就可以直接在 MyAuthenticationProvider 中進(jìn)行調(diào)用了:
- public class MyAuthenticationProvider extends DaoAuthenticationProvider {
- @Override
- protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
- if (!((MyWebAuthenticationDetails) authentication.getDetails()).isPassed()) {
- throw new AuthenticationServiceException("驗(yàn)證碼錯(cuò)誤");
- }
- super.additionalAuthenticationChecks(userDetails, authentication);
- }
- }
直接從 authentication 中獲取到 details 并調(diào)用 isPassed 方法,有問題就拋出異常即可。
最后的問題就是如何用自定義的 MyWebAuthenticationDetailsSource 代替系統(tǒng)默認(rèn)的 WebAuthenticationDetailsSource,很簡單,我們只需要在 SecurityConfig 中稍作定義即可:
- @Autowired
- MyWebAuthenticationDetailsSource myWebAuthenticationDetailsSource;
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http.authorizeRequests()
- ...
- .and()
- .formLogin()
- .authenticationDetailsSource(myWebAuthenticationDetailsSource)
- ...
- }
將 MyWebAuthenticationDetailsSource 注入到 SecurityConfig 中,并在 formLogin 中配置 authenticationDetailsSource 即可成功使用我們自定義的 WebAuthenticationDetails。
這樣自定義完成后,WebAuthenticationDetails 中原有的功能依然保留,也就是我們還可以利用老辦法繼續(xù)獲取用戶 IP 以及 sessionId 等信息,如下:
- @Service
- public class HelloService {
- public void hello() {
- Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
- MyWebAuthenticationDetails details = (MyWebAuthenticationDetails) authentication.getDetails();
- System.out.println(details);
- }
- }
這里類型強(qiáng)轉(zhuǎn)的時(shí)候,轉(zhuǎn)為 MyWebAuthenticationDetails 即可。
本文案例大家可以從 GitHub 上下載:https://github.com/lenve/spring-security-samples
本文轉(zhuǎn)載自微信公眾號「江南一點(diǎn)雨」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系江南一點(diǎn)雨公眾號。
當(dāng)前題目:SpringSecurity系列之查看登錄詳情
標(biāo)題URL:http://www.dlmjj.cn/article/cciiehp.html


咨詢
建站咨詢
