继续我们的SpringSecurity 记住我和CSRF


SpringSecurity 记住我和CSRF


本篇内容继续对SpringSecurity的基础功能进行学习,主要是【记住我】和【CSRF】相关的设置和源码。

记住我

继续我们的SpringSecurity 记住我和CSRF
image-20210423090023069

源码

初次认证过程

//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>
继续我们的SpringSecurity 记住我和CSRF
image-20210423103439832
继续我们的SpringSecurity 记住我和CSRF
image-20210423104309203

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

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!