spring security 学习三-rememberMe
spring security 學習三-rememberMe
?
功能:登錄時的“記住我”功能
原理:
?
rememberMeAuthenticationFilter在security過濾器鏈中的位置,在請求走認證流程是,當前邊的filter都不通過時,會走rememberMeAuthenticationFilter
?
?代碼:
html:
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>自定義登錄頁面</title> </head> <body><h2>自定義登錄頁面</h2><form action="/authentication/form" method="POST"><table><tr><td>用戶名:</td><td><input type="text" name="username"></td></tr><tr><td>密碼:</td><td><input type="password" name="password"></td></tr><tr><td>圖形驗證碼:</td><td><input type="text" name="imageCode"><img src="/code/image?width=200" alt=""></td></tr><tr><td colspan="2"><!-- 其中的name值 remember-me 是固定不變的,security默認 --><input type="checkbox" name="remember-me" value="true">記住我</td></tr><tr><td colspan="2"><button type="submit">登錄</button></td></tr></table></form></body> </html>security配置:
@Autowiredprivate DataSource dataSource;//datasource 用的是springboot默認的application.yml中的配置@Autowiredprivate UserDetailsService userDetailsService;@Beanpublic PersistentTokenRepository persistentTokenRepository() {JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();jdbcTokenRepository.setDataSource(dataSource);//自動創建相關的token表jdbcTokenRepository.setCreateTableOnStartup(true);return jdbcTokenRepository;}@Overrideprotected void configure(HttpSecurity http) throws Exception {//用戶可自定義、亦可以使用mysecurity默認的登錄頁String loginPage = securityProperties.getBrowser().getLoginPage();http.formLogin().loginPage("/authentication/require").loginProcessingUrl("/authentication/form").successHandler(custAuthenticationSuccessHandler).failureHandler(custAuthenticationFailerHandler).and().rememberMe().tokenRepository(persistentTokenRepository())//用于將token信息存儲到數據庫中.tokenValiditySeconds(3600).userDetailsService(userDetailsService)//用于登錄.and().authorizeRequests().antMatchers("/authentication/require", loginPage, "/code/image").permitAll().anyRequest().authenticated();http.csrf().disable();//暫時設為disable 防止跨站請求偽造的功能}啟動項目:(因配置了dbcTokenRepository.setCreateTableOnStartup(true)? 所以會自動創建下表用于存儲用戶token信息)
?
?
頁面:點擊記住我后登錄
登錄成功之后會在persistent_login表中插入一條數據:
?
重啟項目,直接訪問資源路徑:
http://localhost:8080/user/1
不需要跳轉到登錄頁面,直接返回具體信息
?
?源碼:
UserNamePasswordAuthenticationFilter.class文件中的attempAuthentication方法
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {if (this.postOnly && !request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());} else {String username = this.obtainUsername(request);String password = this.obtainPassword(request);if (username == null) {username = "";}if (password == null) {password = "";}username = username.trim();UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);this.setDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}}查看其父類:
AbstractAuthenticationProcessingFilter類,this.attempAuthentication方法使用的是子類方法,也就是usernamepasswordauthenticationFilter類中的方法(上邊的)
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest)req;HttpServletResponse response = (HttpServletResponse)res;if (!this.requiresAuthentication(request, response)) {chain.doFilter(request, response);} else {if (this.logger.isDebugEnabled()) {this.logger.debug("Request is to process authentication");}Authentication authResult;try {authResult = this.attemptAuthentication(request, response);if (authResult == null) {return;}this.sessionStrategy.onAuthentication(authResult, request, response);} catch (InternalAuthenticationServiceException var8) {this.logger.error("An internal error occurred while trying to authenticate the user.", var8);this.unsuccessfulAuthentication(request, response, var8);return;} catch (AuthenticationException var9) {this.unsuccessfulAuthentication(request, response, var9);return;}if (this.continueChainBeforeSuccessfulAuthentication) {chain.doFilter(request, response);}this.successfulAuthentication(request, response, chain, authResult);}}attempauthentication方法執行完之后,回到successfulAuthentication方法(也是AbstractAuthenticationProcessingFilter類),會有remberMeService類
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {if (this.logger.isDebugEnabled()) {this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);}SecurityContextHolder.getContext().setAuthentication(authResult);this.rememberMeServices.loginSuccess(request, response, authResult);if (this.eventPublisher != null) {this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));}this.successHandler.onAuthenticationSuccess(request, response, authResult);}loginSuccess方法會進入AbstractRememberMeService抽象類中:
public final void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {if (!this.rememberMeRequested(request, this.parameter)) {this.logger.debug("Remember-me login not requested.");} else {this.onLoginSuccess(request, response, successfulAuthentication);}}?
onLoginSuccess方法會進入PersistentTokenBaseRememberMeService類中(AbstractRememberMEService的子類)
protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {String username = successfulAuthentication.getName();this.logger.debug("Creating new persistent login for user " + username);PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, this.generateSeriesData(), this.generateTokenData(), new Date());try {this.tokenRepository.createNewToken(persistentToken);this.addCookie(persistentToken, request, response);} catch (Exception var7) {this.logger.error("Failed to save persistent token ", var7);}}這里的這個tokenRepository就是在配置文件中定義的persistentTokenRepository,addCookie方法會將token寫到瀏覽器的cookie中,等下次請求的時候回自動帶著token
?
下一次訪問會進入到RememberAuthenticationFilter過濾器中:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest)req;HttpServletResponse response = (HttpServletResponse)res;//判斷context中是否已經有了一個認證的Authentication對象if (SecurityContextHolder.getContext().getAuthentication() == null) {//是否可以自動登錄Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);if (rememberMeAuth != null) {try {rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);//將一個已經認證的authentication對象放到context中SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);this.onSuccessfulAuthentication(request, response, rememberMeAuth);if (this.logger.isDebugEnabled()) {this.logger.debug("SecurityContextHolder populated with remember-me token: '" + SecurityContextHolder.getContext().getAuthentication() + "'");}if (this.eventPublisher != null) {this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(SecurityContextHolder.getContext().getAuthentication(), this.getClass()));}if (this.successHandler != null) {this.successHandler.onAuthenticationSuccess(request, response, rememberMeAuth);return;}} catch (AuthenticationException var8) {if (this.logger.isDebugEnabled()) {this.logger.debug("SecurityContextHolder not populated with remember-me token, as AuthenticationManager rejected Authentication returned by RememberMeServices: '" + rememberMeAuth + "'; invalidating remember-me token", var8);}this.rememberMeServices.loginFail(request, response);this.onUnsuccessfulAuthentication(request, response, var8);}}chain.doFilter(request, response);} else {if (this.logger.isDebugEnabled()) {this.logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '" + SecurityContextHolder.getContext().getAuthentication() + "'");}chain.doFilter(request, response);}}autoLogin方法會進入到AbstractRememberMeService抽象類中:
public final Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {String rememberMeCookie = this.extractRememberMeCookie(request);if (rememberMeCookie == null) {return null;} else {this.logger.debug("Remember-me cookie detected");if (rememberMeCookie.length() == 0) {this.logger.debug("Cookie was empty");this.cancelCookie(request, response);return null;} else {UserDetails user = null;try {String[] cookieTokens = this.decodeCookie(rememberMeCookie);user = this.processAutoLoginCookie(cookieTokens, request, response);this.userDetailsChecker.check(user);this.logger.debug("Remember-me cookie accepted");return this.createSuccessfulAuthentication(request, user);} catch (CookieTheftException var6) {。。。。、。。。this.cancelCookie(request, response);return null;}}}processAutoLoginCookie方法會走實現類PersistentTokenBasedRememberMeServices類中的方法:
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) + "'");} else {String presentedSeries = cookieTokens[0];String presentedToken = cookieTokens[1];PersistentRememberMeToken token = this.tokenRepository.getTokenForSeries(presentedSeries);if (token == null) {throw new RememberMeAuthenticationException("No persistent token found for series id: " + presentedSeries);} else if (!presentedToken.equals(token.getTokenValue())) {this.tokenRepository.removeUserTokens(token.getUsername());throw new CookieTheftException(this.messages.getMessage("PersistentTokenBasedRememberMeServices.cookieStolen", "Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));} else if (token.getDate().getTime() + (long)this.getTokenValiditySeconds() * 1000L < System.currentTimeMillis()) {throw new RememberMeAuthenticationException("Remember-me login has expired");} else {if (this.logger.isDebugEnabled()) {this.logger.debug("Refreshing persistent login token for user '" + token.getUsername() + "', series '" + token.getSeries() + "'");}PersistentRememberMeToken newToken = new PersistentRememberMeToken(token.getUsername(), token.getSeries(), this.generateTokenData(), new Date());try {this.tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(), newToken.getDate());this.addCookie(newToken, request, response);} catch (Exception var9) {this.logger.error("Failed to update token: ", var9);throw new RememberMeAuthenticationException("Autologin failed due to data access problem");}return this.getUserDetailsService().loadUserByUsername(token.getUsername());}}}?
posted @ 2019-05-04 19:32 巡山小妖N 閱讀(...) 評論(...) 編輯 收藏總結
以上是生活随笔為你收集整理的spring security 学习三-rememberMe的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: bootstrap学习(五)代码
- 下一篇: idea部署tomcat项目时,在项目里