美文网首页Spring Security
Spring Security源码分析七:Spring Secu

Spring Security源码分析七:Spring Secu

作者: 淡淡的伤你 | 来源:发表于2018-01-17 22:56 被阅读49次

    有这样一个场景——有个用户初访并登录了你的网站,然而第二天他又来了,却必须再次登录。于是就有了“记住我”这样的功能来方便用户使用,然而有一件不言自明的事情,那就是这种认证状态的”旷日持久“早已超出了用户原本所需要的使用范围。这意味着,他们可以关闭浏览器,然后再关闭电脑,下周或者下个月,乃至更久以后再回来,只要这间隔时间不要太离谱,该网站总会知道谁是谁,并一如既往的为他们提供所有相同的功能和服务——与许久前他们离开的时候别无二致。

    记住我基本原理

    http://dandandeshangni.oss-cn-beijing.aliyuncs.com/github/Spring%20Security/Spring-Security-remeber.png
    1. 用户认证成功之后调用RemeberMeService根据用户名名生成TokenTokenRepository写入到数据库,同时也将Token写入到浏览器的Cookie
    2. 重启服务之后,用户再次登入系统会由RememberMeAuthenticationFilter拦截,从Cookie中读取Token信息,与persistent_logins表匹配判断是否使用记住我功能。最中由UserDetailsService查询用户信息

    记住我实现

    1. 创建persistent_logins
    create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null);
    
    1. 登陆页面添加记住我复选款(name必须是remeber-me)
    <input name="remember-me" type="checkbox"> 下次自动登录
    
    1. 配置MerryyouSecurityConfig
    http.
    ......
                    .and()
                    .rememberMe()
                    .tokenRepository(persistentTokenRepository())//设置操作表的Repository
                    .tokenValiditySeconds(securityProperties.getRememberMeSeconds())//设置记住我的时间
                    .userDetailsService(userDetailsService)//设置userDetailsService
                    .and()
        ......
    

    效果如下

    [图片上传失败...(image-7630cd-1516200922659)]

    源码分析

    首次登录

    AbstractAuthenticationProcessingFilter#successfulAuthentication
    protected void successfulAuthentication(HttpServletRequest request,
                HttpServletResponse response, FilterChain chain, Authentication authResult)
                throws IOException, ServletException {
    
            if (logger.isDebugEnabled()) {
                logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
                        + authResult);
            }
            //# 1.将已认证过的Authentication放入到SecurityContext中
            SecurityContextHolder.getContext().setAuthentication(authResult);
            //# 2.登录成功调用rememberMeServices
            rememberMeServices.loginSuccess(request, response, authResult);
    
            // Fire event
            if (this.eventPublisher != null) {
                eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
                        authResult, this.getClass()));
            }
    
            successHandler.onAuthenticationSuccess(request, response, authResult);
        }
    
    1. 将已认证过的Authentication放入到SecurityContext中
    2. 登录成功调用rememberMeServices
    AbstractRememberMeServices#loginSuccess
    private String parameter = DEFAULT_PARAMETER;//remember-me
    
    public final void loginSuccess(HttpServletRequest request,
                HttpServletResponse response, Authentication successfulAuthentication) {
            // #1.判断是否勾选记住我
            if (!rememberMeRequested(request, parameter)) {
                logger.debug("Remember-me login not requested.");
                return;
            }
    
            onLoginSuccess(request, response, successfulAuthentication);
        }
    
    1. 判断是否勾选记住我
    PersistentTokenBasedRememberMeServices#onLoginSuccess
    protected void onLoginSuccess(HttpServletRequest request,
                HttpServletResponse response, Authentication successfulAuthentication) {
            //#1.获取用户名
            String username = successfulAuthentication.getName();
    
            logger.debug("Creating new persistent login for user " + username);
            //#2.创建Token
            PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(
                    username, generateSeriesData(), generateTokenData(), new Date());
            try {
                //#3.存储都数据库
                tokenRepository.createNewToken(persistentToken);
                //#4.写入到浏览器的Cookie中
                addCookie(persistentToken, request, response);
            }
            catch (Exception e) {
                logger.error("Failed to save persistent token ", e);
            }
        }
    
    1. 获取用户名
    2. 创建Token
    3. 存储都数据库
    4. 写入到浏览器的Cookie中

    二次登录Remember-me

    RememberMeAuthenticationFilter#doFilter
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
                throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;
            //#1.判断SecurityContext中没有Authentication
            if (SecurityContextHolder.getContext().getAuthentication() == null) {
                //#2.从Cookie查询用户信息返回RememberMeAuthenticationToken
                Authentication rememberMeAuth = rememberMeServices.autoLogin(request,
                        response);
    
                if (rememberMeAuth != null) {
                    // Attempt authenticaton via AuthenticationManager
                    try {
                        //#3.如果不为空则由authenticationManager认证
                        rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);
    
                        // Store to SecurityContextHolder
                        SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
    
                        onSuccessfulAuthentication(request, response, rememberMeAuth);
    ......
    
    1. 判断SecurityContext中没有Authentication
    2. 从Cookie查询用户信息返回RememberMeAuthenticationToken
    3. 如果不为空则由authenticationManager认证
    AbstractRememberMeServices#autoLogin
    public final Authentication autoLogin(HttpServletRequest request,
                HttpServletResponse response) {
                //#1.获取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 {
                //#2.解析Cookie
                String[] cookieTokens = decodeCookie(rememberMeCookie);
                //#3.获取用户凭证
                user = processAutoLoginCookie(cookieTokens, request, response);
                //#4.检查用户凭证
                userDetailsChecker.check(user);
    
                logger.debug("Remember-me cookie accepted");
                //#5.返回Authentication
                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. 获取Cookie
    2. 解析Cookie
    3. 获取用户凭证
    4. 检查用户凭证

    代码下载

    从我的 github 中下载,https://github.com/longfeizheng/logback

    相关文章

      网友评论

      • 编程界的小学生:通用的权限处理框架,基于Spring-Security的二次封装,可扩展,可配置。即使不想用的话也可以学习学习这种封装可扩展可配置框架的思想。
        包含了前后分离处理方案(JWT+Redis),前后不分离处理方案(session),validate验证部分处理。
        https://gitee.com/geekerdream/common-security
        给个star吧,不容易的。

      本文标题:Spring Security源码分析七:Spring Secu

      本文链接:https://www.haomeiwen.com/subject/xdspoxtx.html