SpringSecurity(五):RememberMe以及源码分析

来源:互联网 发布:sn ty gm js是什么 编辑:程序博客网 时间:2024/05/19 02:40

1.原理:参考博文:http://blog.csdn.net/fangchao2061/article/details/51179393


2.Security的实现

添加remember功能之前,先启动项目进行测试。

授权登录后,访问localhost:8080/user1可以得到用户信息,关闭浏览器,再次访问localhost:8080/user1会跳到鉴权页面。

修改项目,添加rememberMe功能。


login.html添加

<tr><td><input type="checkbox" name="remember-me" value="true" />记住我</td></tr>
name默认是remember-me


SecurityConfig:

启动项目测试:

报错

注入自己实现的userDetailsService,再次重复之前测试,勾选记住我,发现关闭浏览器之后再开,访问localhost:8080/user1可以正常访问,不勾选记住我,结果同之前一样,说明功能已经实现


在第二节,我们讲了,登录功能实现以后,会调用 this.rememberMeServices.loginSuccess(request, response, authResult); 

RememberMeService接口:

/** * 实现也决定了记住我的Cookie的有效期。 * 这个接口被设计为适应任何这些记忆我的模型. * 这个接口没有定义如何记住我的服务应该提供一个“取消所有记住我的令牌”类型的能力,因为这将是具体的实现,不需要挂钩到Spring Security. *  * 你可以自己实现该接口,来实现记住我功能的持久化 *  * 默认采用的存放在cookie中 * * @author Ben Alex */public interface RememberMeServices {// ~ Methods// ========================================================================================================/** * 退出浏览器再次访问的时候就会调用该方法 * @return */Authentication autoLogin(HttpServletRequest request, HttpServletResponse response);/** * 当进行交互式身份验证尝试时调用,但用户提供的凭据丢失或无效。 * 实现方法应该使任何和所有记住我的令牌无效 */void loginFail(HttpServletRequest request, HttpServletResponse response);/** * 登录成功以后调用该方法 */void loginSuccess(HttpServletRequest request, HttpServletResponse response,Authentication successfulAuthentication);}


首次认证登录时候:

实现类AbstractRememberMeServices:

主要代码:

public final void loginSuccess(HttpServletRequest request,HttpServletResponse response, Authentication successfulAuthentication) {if (!rememberMeRequested(request, parameter)) {logger.debug("Remember-me login not requested.");return;}onLoginSuccess(request, response, successfulAuthentication);}
该类默认定义请求传入的参数名为:
public static final String DEFAULT_PARAMETER = "remember-me";
这就是为什么页面name为rember-me的原因。

onLoginSuccess(.....)方法的默认是实现是TokenBasedRememberMeServices

主要代码:

public void onLoginSuccess(HttpServletRequest request, HttpServletResponse response,Authentication successfulAuthentication) {/** * 获取用户名密码 */String username = retrieveUserName(successfulAuthentication);String password = retrievePassword(successfulAuthentication);// 如果找不到用户名和密码,就要中止TokenBasedRememberMeServices在这种情况下无法构造有效的令牌.if (!StringUtils.hasLength(username)) {logger.debug("Unable to retrieve username");return;}if (!StringUtils.hasLength(password)) {UserDetails user = getUserDetailsService().loadUserByUsername(username);password = user.getPassword();if (!StringUtils.hasLength(password)) {logger.debug("Unable to obtain password for user: " + username);return;}}/** * token的生命周期,默认是两周 */int tokenLifetime = calculateLoginLifetime(request, successfulAuthentication);long expiryTime = System.currentTimeMillis();// SEC-949expiryTime += 1000L * (tokenLifetime < 0 ? TWO_WEEKS_S : tokenLifetime);//给token签名String signatureValue = makeTokenSignature(expiryTime, username, password);//设置进cookiesetCookie(new String[] { username, Long.toString(expiryTime), signatureValue },tokenLifetime, request, response);if (logger.isDebugEnabled()) {logger.debug("Added remember-me cookie for user '" + username+ "', expiry: '" + new Date(expiryTime) + "'");}}

退出浏览器以后,再次请求localhost:8080/user1,就会请求autoLogin(....)方法

AbstractRememberMeServices.autoLogin(....)方法:

public final Authentication autoLogin(HttpServletRequest request,HttpServletResponse response) {/** * 找到Spring Security在请求中记住我的cookie并返回其值。 *  通过名称搜索cookie,并通过将上下文路径匹配到cookie路径。 */String rememberMeCookie = extractRememberMeCookie(request);if (rememberMeCookie == null) {return null;}logger.debug("Remember-me cookie detected");if (rememberMeCookie.length() == 0) {logger.debug("Cookie was empty");cancelCookie(request, response);return null;}UserDetails user = null;try {//解码cookieString[] cookieTokens = decodeCookie(rememberMeCookie);//从cookie中获取用户名,使用UserDetailsService.loadUserByUsername(username)获得用户信息user = processAutoLoginCookie(cookieTokens, request, response);//检查用户信息,是否可用,过期等等,如果是抛出对应异常userDetailsChecker.check(user);logger.debug("Remember-me cookie accepted");//认证成功,将用户信息放入tokenreturn 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());}//如果失败就清楚cookiecancelCookie(request, response);return null;}

RememberMe的持久化

下面看看onLoginSuccess(request, response, successfulAuthentication);方法的持久化实现。

PersistentTokenBasedRememberMeServices类主要代码:

protected void onLoginSuccess(HttpServletRequest request,HttpServletResponse response, Authentication successfulAuthentication) {String username = successfulAuthentication.getName();logger.debug("Creating new persistent login for user " + username);//创建PersistentRememberMeTokenPersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, generateSeriesData(), generateTokenData(), new Date());try {//调用tokenRepository,默认是InMemoryTokenRepositoryImpl//基于数据库的需要改成JdbcTokenRepositoryImpltokenRepository.createNewToken(persistentToken);//添加至cookieaddCookie(persistentToken, request, response);}catch (Exception e) {logger.error("Failed to save persistent token ", e);}}


PersistentRememberMeToken类

public class PersistentRememberMeToken {private final String username;private final String series;private final String tokenValue;private final Date date;//get /set 略}

接下来是JdbcTokenRepositoryImpl

/** * 基于JDBC的持久性登录令牌库实现 * * @author Luke Taylor * @since 2.0 */public class JdbcTokenRepositoryImpl extends JdbcDaoSupport implementsPersistentTokenRepository {// ~ Static fields/initializers// =====================================================================================/** 用于创建数据库表以存储令牌的默认SQL */public static final String CREATE_TABLE_SQL = "create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, "+ "token varchar(64) not null, last_used timestamp not null)";/** The default SQL used by the <tt>getTokenBySeries</tt> query */public static final String DEF_TOKEN_BY_SERIES_SQL = "select username,series,token,last_used from persistent_logins where series = ?";/** The default SQL used by <tt>createNewToken</tt> */public static final String DEF_INSERT_TOKEN_SQL = "insert into persistent_logins (username, series, token, last_used) values(?,?,?,?)";/** The default SQL used by <tt>updateToken</tt> */public static final String DEF_UPDATE_TOKEN_SQL = "update persistent_logins set token = ?, last_used = ? where series = ?";/** The default SQL used by <tt>removeUserTokens</tt> */public static final String DEF_REMOVE_USER_TOKENS_SQL = "delete from persistent_logins where username = ?";// ~ Instance fields// ================================================================================================private String tokensBySeriesSql = DEF_TOKEN_BY_SERIES_SQL;private String insertTokenSql = DEF_INSERT_TOKEN_SQL;private String updateTokenSql = DEF_UPDATE_TOKEN_SQL;private String removeUserTokensSql = DEF_REMOVE_USER_TOKENS_SQL;private boolean createTableOnStartup;/** * 如果createTableOnStartup为true * 初始化数据库 */protected void initDao() {if (createTableOnStartup) {getJdbcTemplate().execute(CREATE_TABLE_SQL);}}public void createNewToken(PersistentRememberMeToken token) {getJdbcTemplate().update(insertTokenSql, token.getUsername(), token.getSeries(),token.getTokenValue(), token.getDate());}public void updateToken(String series, String tokenValue, Date lastUsed) {getJdbcTemplate().update(updateTokenSql, tokenValue, lastUsed, series);}/** * 加载所提供的系列标识符的令牌数据. */public PersistentRememberMeToken getTokenForSeries(String seriesId) {try {return getJdbcTemplate().queryForObject(tokensBySeriesSql,new RowMapper<PersistentRememberMeToken>() {public PersistentRememberMeToken mapRow(ResultSet rs, int rowNum)throws SQLException {return new PersistentRememberMeToken(rs.getString(1), rs.getString(2), rs.getString(3), rs.getTimestamp(4));}}, seriesId);}catch (EmptyResultDataAccessException zeroResults) {if (logger.isDebugEnabled()) {logger.debug("Querying token for series '" + seriesId+ "' returned no results.", zeroResults);}}catch (IncorrectResultSizeDataAccessException moreThanOne) {logger.error("Querying token for series '" + seriesId+ "' returned more than one value. Series" + " should be unique");}catch (DataAccessException e) {logger.error("Failed to load token for series " + seriesId, e);}return null;}public void removeUserTokens(String username) {getJdbcTemplate().update(removeUserTokensSql, username);}public void setCreateTableOnStartup(boolean createTableOnStartup) {this.createTableOnStartup = createTableOnStartup;}}

ok,原理知道了,接下来写自己的实现



启动项目测试

新建表如下:


登陆以后:


重启项目,重启浏览器,继续访问localhost:8080/user1,访问成功。

源码地址:

https://gitee.com/mengcan/SpringSecurity.git

阅读全文
0 0