springSecurity的两种rememberMe方式 springSecurity 使用RememberMeAuthenticationFilter处理记住我功能,代码逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public void doFilter (ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; if (SecurityContextHolder.getContext().getAuthentication() == null ) { Authentication rememberMeAuth = rememberMeServices.autoLogin(request,response); if (rememberMeAuth != null ) { try { rememberMeAuth = authenticationManager.authenticate(rememberMeAuth); SecurityContextHolder.getContext().setAuthentication(rememberMeAuth); onSuccessfulAuthentication(request, response, rememberMeAuth); if (successHandler != null ) { successHandler.onAuthenticationSuccess(request, response, rememberMeAuth); return ; } } catch (AuthenticationException authenticationException) { } chain.doFilter(request, response); } else { chain.doFilter(request, response); } }
springSeciruity提供的实现 上面的filter使用RememberMeServices来处理自动登录,
TokenBasedRememberMeServices和PersistentTokenBasedRememberMeServices是springSecurity提供的两种处理remenberMe的实现类
他俩都继承自 AbstractRememberMeServices
0.AbstractRememberMeServices 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public final Authentication autoLogin (HttpServletRequest request , HttpServletResponse response ) { String rememberMeCookie = extractRememberMeCookie (request ); UserDetails user = null ; try { String [] cookieTokens = decodeCookie (rememberMeCookie ); user = processAutoLoginCookie (cookieTokens , request , response ); userDetailsChecker .check (user ); return createSuccessfulAuthentication (request , user ); } catch (CookieTheftException cte ) { cancelCookie (request , response ); throw cte ; } catch (UsernameNotFoundException noUser ) { logger .debug ("Remember-me login was valid but corresponding user not found." , noUser ); } catch (InvalidCookieException invalidCookie) { logger .debug ("Invalid remember-me cookie: " + invalidCookie.getMessage ()); } catch (AccountStatusException statusInvalid ) { logger .debug ("Invalid UserDetails: " + statusInvalid .getMessage ()); } catch (RememberMeAuthenticationException e ) { logger .debug (e .getMessage ()); } cancelCookie (request , response ); return null ; }
1. 使用TokenBasedRememberMeServices如何实现processAutoLoginCookie方法的 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 protected UserDetails processAutoLoginCookie (String [] cookieTokens, HttpServletRequest request, HttpServletResponse response ) { if (cookieTokens.length != 3 ) { throw new InvalidCookieException ("Cookie token did not contain 3" + " tokens, but contained '" + Arrays .asList (cookieTokens) + "'" ); } long tokenExpiryTime; try { tokenExpiryTime = new Long (cookieTokens[1 ]).longValue (); } catch (NumberFormatException nfe) { throw new InvalidCookieException ( "Cookie token[1] did not contain a valid number (contained '" + cookieTokens[1 ] + "')" ); } if (isTokenExpired (tokenExpiryTime)) { throw new InvalidCookieException ("Cookie token[1] has expired (expired on '" + new Date (tokenExpiryTime) + "'; current time is '" + new Date () + "')" ); } UserDetails userDetails = getUserDetailsService ().loadUserByUsername ( cookieTokens[0 ]); String expectedTokenSignature = makeTokenSignature (tokenExpiryTime, userDetails.getUsername (), userDetails.getPassword ()); if (!equals (expectedTokenSignature, cookieTokens[2 ])) { throw new InvalidCookieException ("Cookie token[2] contained signature '" + cookieTokens[2 ] + "' but expected '" + expectedTokenSignature + "'" ); } return userDetails; }
上面的方法就是根据cookie中查询的用户名到数据库中查询出真实用户信息。根据真实的信息,构造出签名与cookie中的签名进行比较。
这样可以防止cookie伪造,但不能防止cookie被别人窃取使用。
2. 使用PersistentTokenBasedRememberMeServices如何实现processAutoLoginCookie方法的 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 protected UserDetails processAutoLoginCookie (String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) { if (cookieTokens.length != 2 ) { throw new InvalidCookieException ("Cookie token did not contain " + 2 + " tokens, but contained '" + Arrays.asList(cookieTokens) + "'" ); } final String presentedSeries = cookieTokens[0 ]; final String presentedToken = cookieTokens[1 ]; PersistentRememberMeToken token = tokenRepository .getTokenForSeries(presentedSeries); if (token == null ) { throw new RememberMeAuthenticationException ( "No persistent token found for series id: " + presentedSeries); } if (!presentedToken.equals(token.getTokenValue())) { tokenRepository.removeUserTokens(token.getUsername()); throw new CookieTheftException ( messages.getMessage( "PersistentTokenBasedRememberMeServices.cookieStolen" , "Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack." )); } if (token.getDate().getTime() + getTokenValiditySeconds() * 1000L < System .currentTimeMillis()) { throw new RememberMeAuthenticationException ("Remember-me login has expired" ); } PersistentRememberMeToken newToken = new PersistentRememberMeToken ( token.getUsername(), token.getSeries(), generateTokenData(), new Date ()); try { tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(), newToken.getDate()); addCookie(newToken, request, response); } catch (Exception e) { logger.error("Failed to update token: " , e); throw new RememberMeAuthenticationException ( "Autologin failed due to data access problem" ); } return getUserDetailsService().loadUserByUsername(token.getUsername()); }
这个rememberMeServices的处理逻辑是,每次自动登录成功后将cookie中的某个随机值和数据库同步更新,假设cookie别别人盗用,自动登录后盗用者的cookie被更新了。
主人的cookie就会变无效。下次主人会自动登录失败,系统就能发现cookie被盗用,此时删除数据库中的对应cookie验证,通知用户改密码等.