SpringSecurity 记住我和CSRF
本篇内容继续对SpringSecurity的基础功能进行学习,主要是【记住我】和【CSRF】相关的设置和源码。
记住我
源码
初次认证过程
//UsernamePasswordAuthenticationFilter认证成功会调用父类的successfulAuthentication
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", 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);
}
//然后到AbstractRememberMeServices的loginSuccess方法
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);
}
}
//PersistentTokenBasedRememberMeServices的onLoginSuccess方法 tokenRepository创建token
protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
String username = successfulAuthentication.getName();
this.logger.debug(LogMessage.format("Creating new persistent login for user %s", username));
PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, this.generateSeriesData(), this.generateTokenData(), new Date());
try {
this.tokenRepository.createNewToken(persistentToken);// 创建token
this.addCookie(persistentToken, request, response);//写入cookie
} catch (Exception var7) {
this.logger.error("Failed to save persistent token ", var7);
}
}
//JdbcTokenRepositoryImpl将token写入数据库
//数据库相关的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)";
public static final String DEF_TOKEN_BY_SERIES_SQL = "select username,series,token,last_used from persistent_logins where series = ?";
public static final String DEF_INSERT_TOKEN_SQL = "insert into persistent_logins (username, series, token, last_used) values(?,?,?,?)";
rememberme之后再次认证
//RememberMeAuthenticationFilter doFilter方法中 通过rememberMeServices 自动登录逻辑
Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);
//AbstractRememberMeServices autoLogin方法
public final Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
String rememberMeCookie = this.extractRememberMeCookie(request);//解析cookie
...
String[] cookieTokens = this.decodeCookie(rememberMeCookie);
UserDetails user = this.processAutoLoginCookie(cookieTokens, request, response);//获取用户
this.userDetailsChecker.check(user);//检查用户
this.logger.debug("Remember-me cookie accepted");
return this.createSuccessfulAuthentication(request, user);
}
//PersistentTokenBasedRememberMeServices processAutoLoginCookie方法
PersistentRememberMeToken token = this.tokenRepository.getTokenForSeries(presentedSeries);
return this.getUserDetailsService().loadUserByUsername(token.getUsername());
配置类
注入PersistentTokenRepository和DataSource
@Resource
private DataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
//自动创建表
// jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
//配置rememberme
@Override
protected void configure(HttpSecurity http) throws Exception {
...
.anyRequest().authenticated()//除上面定义之外的url全部需要鉴权
.and().rememberMe().tokenRepository(persistentTokenRepository())//设置tokenRepository的实现
.tokenValiditySeconds(60)//设置有效时长,单位秒
.and().userDetailsService(userDetailsService)//指定userDetailService
}
设置login页面上的记住我复选框
<form action="/user/login" method="post">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
<input type="text" name="username" />
<input type="text" name="password" />
<br/>记住我 <input type="checkbox" name="remember-me"/><!--设置记住我 注意name必须为'remember-me' -->
<input type="submit" />
</form>
CSRF
概念
跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
spring-security设置
.csrf().disable(); //关闭csrf防护 注释该代码打开csrf防护
前台页面设置csrf隐藏域:
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
完成配置。
原理
客户端在请求时需要带着服务端响应页面是提供的csrftoken,同时服务端会校验该token是否与响应式服务端带着的一致。如果没有带着则认证失败。
CsrfFilter
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
request.setAttribute(HttpServletResponse.class.getName(), response);
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
boolean missingToken = csrfToken == null;
if (missingToken) {
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);
if (!this.requireCsrfProtectionMatcher.matches(request)) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not protect against CSRF since request did not match " + this.requireCsrfProtectionMatcher);
}
filterChain.doFilter(request, response);
} else {
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
if (!csrfToken.getToken().equals(actualToken)) {
this.logger.debug(LogMessage.of(() -> {
return "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request);
}));
AccessDeniedException exception = !missingToken ? new InvalidCsrfTokenException(csrfToken, actualToken) : new MissingCsrfTokenException(actualToken);
this.accessDeniedHandler.handle(request, response, (AccessDeniedException)exception);
} else {
filterChain.doFilter(request, response);
}
}
}
这些都是基础版本的学习,相应的代码已上传码云:https://gitee.com/josekongng/spring-security-learn,各位请参考。
原文始发于微信公众号(云户):继续我们的SpringSecurity 记住我和CSRF
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/25775.html