首页 文章
取消

一个简单的拔插式认证框架是什么样的

我们在初学Java Web时都做过最简单的认证:定义一个登录Servlet,负责将前端传入的用户名/密码与数据库中的数据比对,如果一致则登录成功,然后将用户信息放入Cookie。随后定义一个Filter,负责取出Cookie中的用户信息进行验证。步入职场后,我们发现实际使用的认证方案其实相当复杂,多个系统可能会依赖同一个认证服务——SSO,SSO又有多种协议:OAuth、OIDC、CAS、LDAP等等,又或者会集成微信、支付宝等第三方认证服务。但一般来说,每个系统的认证框架都会做成拔插式的,允许扩展多种认证方式。本文结合自身工作经历,介绍在实际生产中一个简单的拔插式认证框架是如何设计与实现的。

框架设计

首先我们要明确两个概念:登录、认证。登录是用户主动发起的请求,而认证是系统被动进行的验证;登录是为了获得系统的访问权限,而认证是为了确认用户的身份。前后端分离架构系统的主流认证方案是基于JWT的,我们的框架也以JWT为基础。

我们以框架中涉及到的主要类为切入点,阐述每个类之间的关系、职责,以点带面,一览框架的全貌。

  • LoginController:登录接口,职责是校验前端传入的用户名/密码,校验成功后记录Session并调用LoginAuthHandlerBasebuildJwt方法构建JWT

  • AuthenticationFilter:认证过滤器,它是认证的入口,除了登录接口外,所有请求都会经过它,职责是拦截请求,验证请求中携带的访问令牌。

  • ILoginAuthHandler:认证处理器接口,它是认证处理器的顶层接口,也是LoginAuthHandlerBase的父接口,是对外的认证门面。

  • LoginAuthHandlerBase:实现了ILoginAuthHandler的认证处理器基类,是所有认证处理器的基类。主要职责是完成登录、认证、登出等功能。

  • LoginAuthFactory:认证处理器工厂,职责是在容器刷新时将所有认证处理器bean加载到内存中,以便随时取用。

  • DefaultLoginAuthHandler:认证处理器,真正执行认证等逻辑的类,职责是完成具体的登录、认证、登出等功能,并将结果返回给LoginAuthHandlerBase

从功能的最小实现的角度来说,有LoginControllerAuthenticationFilterDefaultLoginAuthHandler足以,但我们考虑到需要支持多种认证方式,并且要遵循开闭原则,于是就有了ILoginAuthHandlerLoginAuthHandlerBaseLoginAuthFactory

代码实现

ILoginAuthHandler

package neatlogic.framework.filter.core;

import com.alibaba.fastjson.JSONObject;
import neatlogic.framework.dto.UserVo;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface ILoginAuthHandler {

    String getType();

    UserVo auth(HttpServletRequest request, HttpServletResponse response) throws Exception;

    UserVo login(UserVo userVo, JSONObject resultJson);

    String directUrl();

    String logout();

}

ILoginAuthHandler接口定义了5个方法:

  • getType():获取处理器的标识

  • auth():执行认证逻辑的入口

  • login():执行登录逻辑的入口

  • logout():执行登出逻辑的入口

  • directUrl():获取登录页面地址

LoginAuthHandlerBase

package neatlogic.framework.filter.core;

import com.alibaba.fastjson.JSONObject;
import neatlogic.framework.asynchronization.threadlocal.UserContext;
import neatlogic.framework.common.config.Config;
import neatlogic.framework.dao.mapper.LoginMapper;
import neatlogic.framework.dao.mapper.RoleMapper;
import neatlogic.framework.dao.mapper.UserMapper;
import neatlogic.framework.dao.mapper.UserSessionMapper;
import neatlogic.framework.dto.JwtVo;
import neatlogic.framework.dto.UserVo;
import neatlogic.framework.dto.captcha.LoginFailedCountVo;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.zip.GZIPOutputStream;

public abstract class LoginAuthHandlerBase implements ILoginAuthHandler {
    protected static Logger logger = LoggerFactory.getLogger(LoginAuthHandlerBase.class);

    public abstract String getType();

    protected static UserMapper userMapper;

    protected static RoleMapper roleMapper;

    protected static LoginMapper loginMapper;

    protected static UserSessionMapper userSessionMapper;

    @Autowired
    public void setUserMapper(UserMapper _userMapper) {
        userMapper = _userMapper;
    }

    @Autowired
    public void setRoleMapper(RoleMapper _roleMapper) {
        roleMapper = _roleMapper;
    }

    @Autowired
    public void setLoginMapper(LoginMapper _loginMapper) {
        loginMapper = _loginMapper;
    }

    @Autowired
    public void setUserSessionMapper(UserSessionMapper _userSessionMapper) {
        userSessionMapper = _userSessionMapper;
    }

    @Override
    public UserVo auth(HttpServletRequest request, HttpServletResponse response) throws Exception {
        UserVo userVo = myAuth(request);
        if (userVo != null && StringUtils.isBlank(userVo.getUuid())) {
            userVo = null;
        }
        //如果认证cookie为null则构建JWT作为cookie
        if (userVo != null && StringUtils.isBlank(userVo.getCookieAuthorization())) {
            JwtVo jwtVo = buildJwt(userVo);
            setResponseAuthCookie(response, request, jwtVo);
            userSessionMapper.insertUserSession(userVo.getUuid());
        }
        return userVo;
    }

    public abstract UserVo myAuth(HttpServletRequest request) throws Exception;

    /**
     * 生成JWT对象
     *
     * @param checkUserVo 用户
     * @return JWT对象
     * @throws Exception 异常
     */
    public static JwtVo buildJwt(UserVo checkUserVo) throws Exception {
        // 构建head
        JSONObject jwtHeadObj = new JSONObject();
        jwtHeadObj.put("alg", "HS256");
        jwtHeadObj.put("typ", "JWT");
        // 构建payload
        JSONObject jwtBodyObj = new JSONObject();
        jwtBodyObj.put("useruuid", checkUserVo.getUuid());
        jwtBodyObj.put("userid", checkUserVo.getUserId());
        jwtBodyObj.put("username", checkUserVo.getUserName());
        jwtBodyObj.put("isSuperAdmin", checkUserVo.getIsSuperAdmin());

        String jwthead = Base64.getUrlEncoder().encodeToString(jwtHeadObj.toJSONString().getBytes());
        String jwtbody = Base64.getUrlEncoder().encodeToString(jwtBodyObj.toJSONString().getBytes());

        SecretKeySpec signingKey = new SecretKeySpec(Config.JWT_SECRET().getBytes(), "HmacSHA1");
        Mac mac;

        mac = Mac.getInstance("HmacSHA1");
        mac.init(signingKey);
        // 对head与payload签名并URL编码
        byte[] rawHmac = mac.doFinal((jwthead + "." + jwtbody).getBytes());
        String jwtsign = Base64.getUrlEncoder().encodeToString(rawHmac);

        // 压缩JWT内容并URL编码
        String c = "Bearer_" + jwthead + "." + jwtbody + "." + jwtsign;
        checkUserVo.setAuthorization(c);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        GZIPOutputStream gzipOutputStream = new GZIPOutputStream(bos);
        gzipOutputStream.write(c.getBytes());
        gzipOutputStream.close();
        String cc = Base64.getEncoder().encodeToString(bos.toByteArray());
        bos.close();
        JwtVo jwtVo = new JwtVo();
        jwtVo.setCc(cc);
        jwtVo.setJwthead(jwthead);
        jwtVo.setJwtbody(jwtbody);
        jwtVo.setJwtsign(jwtsign);
        return jwtVo;
    }

    /**
     * 设置登录cookie
     *
     * @param response 响应
     * @param request  请求
     * @param jwtVo    JWT对象
     */
    public static void setResponseAuthCookie(HttpServletResponse response, HttpServletRequest request, JwtVo jwtVo) {
        Cookie authCookie = new Cookie("authorization", "GZIP_" + jwtVo.getCc());
        authCookie.setPath("/");
        String domainName = request.getServerName();
        if (StringUtils.isNotBlank(domainName)) {
            String[] ds = domainName.split("\\.");
            int len = ds.length;
            if (len > 2 && !StringUtils.isNumeric(ds[len - 1])) {
                authCookie.setDomain(ds[len - 2] + "." + ds[len - 1]);
            }
        }
        response.addCookie(authCookie);
        // 允许跨域携带cookie
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setContentType(Config.RESPONSE_TYPE_JSON);
    }

    @Override
    public String logout() {
        userSessionMapper.deleteUserSessionByUserUuid(UserContext.get().getUserUuid(true));
        String url;
        try {
            url = myLogout();
        } catch (IOException e) {
            logger.error(e.getMessage());
            throw new RuntimeException();
        }
        return url;
    }

    protected String myLogout() throws IOException {
        return null;
    }

    @Override
    public String directUrl() {
        String directUrl = myDirectUrl();
        if (StringUtils.isBlank(directUrl)) {
            directUrl = Config.DIRECT_URL();
        }
        return directUrl;
    }

    protected String myDirectUrl() {
        return null;
    }

    @Override
    public UserVo login(UserVo userVo, JSONObject resultJson) {
        UserVo checkUserVo = myLogin(userVo, resultJson);
        LoginFailedCountVo loginFailedCountVo;
        if (checkUserVo == null) {//如果正常用户登录失败则失败次数+1
            int failedCount = 1;
            loginFailedCountVo = loginMapper.getLoginFailedCountVoByUserId(userVo.getUserId());
            if (loginFailedCountVo != null) {
                failedCount = loginFailedCountVo.getFailedCount();
            }
            loginFailedCountVo = new LoginFailedCountVo(userVo.getUserId(), failedCount);
            loginMapper.updateLoginFailedCount(loginFailedCountVo);
        } else {//如果正常用户登录成功,则清空该用户的失败次数
            resultJson.remove("isNeedCaptcha");
            loginMapper.deleteLoginFailedCountByUserId(userVo.getUserId());
        }
        return checkUserVo;
    }

    public UserVo myLogin(UserVo userVo, JSONObject resultJson) {
        return null;
    }
}

LoginAuthHandlerBase实现了ILoginAuthHandlerauth方法,这是认证的入口,方法内部使用模板方法模式,先调用子类的myAuth方法完成真正的认证逻辑,然后调用自身的buildJwt方法构建JWT。也就是说,无论以何种方式通过认证,只要发现Cookie中没有JWT,就会构建一个JWT给客户端,这就是上文所说的“以JWT为基础”的含义。

buildJwt方法展示了如何构建一个JWT

  1. 创建JWTheadpayload

  2. 使用HmacSHA1算法对headpayload进行签名

  3. 将经过URL编码过的headpayload与签名拼接在一起,拼接时还加上了“Bearer_”作为前缀,这是构建JWT的通用做法

  4. 将拼接好的字符串进行压缩并URL编码,由此得到的字符串就是最终的JWT

值得一提的是,对headpayload进行签名而构建出来的JWT就是JSON Web Signature(JWS)JWS的标准格式为:Bearer_${head}.${payload}.${signature}

login方法使用的也是模板方法模式,调用子类的myLogin方法完成真正的登录逻辑,完成后回到login方法做一些附加的逻辑,例如统计登录失败次数。

LoginAuthFactory

package neatlogic.framework.filter.core;

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class LoginAuthFactory implements ApplicationListener<ContextRefreshedEvent> {
    private static final Map<String, ILoginAuthHandler> loginAuthMap = new HashMap<>();

    public static ILoginAuthHandler getLoginAuth(String type) {
        return loginAuthMap.get(type.toUpperCase());
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        Map<String, ILoginAuthHandler> myMap = event.getApplicationContext().getBeansOfType(ILoginAuthHandler.class);
        for (Map.Entry<String, ILoginAuthHandler> entry : myMap.entrySet()) {
            ILoginAuthHandler authAuth = entry.getValue();
            loginAuthMap.put(authAuth.getType().toUpperCase(), authAuth);
        }
    }

}

LoginAuthFactory实现了ApplicationListener<ContextRefreshedEvent>接口,重写了onApplicationEvent方法,从ApplicationContext中获取所有实现了ILoginAuthHandler接口的bean,这些bean其实就是各认证处理器的实例,要用到它们时,直接从loginAuthMap取即可,这是对象池思想的具体实践

DefaultLoginAuthHandler

package neatlogic.module.framework.filter.handler;

import com.alibaba.fastjson.JSONObject;
import neatlogic.framework.common.config.Config;
import neatlogic.framework.dto.UserVo;
import neatlogic.framework.filter.core.LoginAuthHandlerBase;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.zip.GZIPInputStream;

@Service
public class DefaultLoginAuthHandler extends LoginAuthHandlerBase {

    @Override
    public String getType() {
        return "default";
    }

    @Override
    public UserVo myAuth(HttpServletRequest request) {
        //获取 authorization,优先获取header的authorization,不存在则从cookie获取authorization
        Cookie[] cookies = request.getCookies();
        UserVo userVo = new UserVo();
        String authorizationFromCookie = null;
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("authorization".equals(cookie.getName())) {
                    authorizationFromCookie = cookie.getValue();
                }
            }
        }
        String authorization = request.getHeader("Authorization");
        if (StringUtils.isBlank(authorization)) {
            if (StringUtils.isNotBlank(authorizationFromCookie)) {
                userVo.setCookieAuthorization(authorizationFromCookie);
                authorization = authorizationFromCookie;
                // 解压cookie内容
                if (authorization.startsWith("GZIP_")) {
                    authorization = authorization.substring(5);
                    try {
                        byte[] compressData = Base64.getDecoder().decode(authorization);
                        ByteArrayInputStream bis = new ByteArrayInputStream(compressData);
                        ByteArrayOutputStream bos = new ByteArrayOutputStream();
                        GZIPInputStream gzipInputStream = new GZIPInputStream(bis);
                        byte[] buffer = new byte[2048];
                        int n;
                        while ((n = gzipInputStream.read(buffer)) >= 0) {
                            bos.write(buffer, 0, n);
                        }
                        bis.close();
                        gzipInputStream.close();
                        authorization = bos.toString();
                        bos.close();
                    } catch (Exception ex) {
                        logger.error(ex.getMessage(), ex);
                    }
                }
            }
        } else {
            userVo.setAuthorization(authorization);
        }
        //如果 authorization 存在,则拆包获取用户信息
        if (StringUtils.isNotBlank(authorization)) {
            if (authorization.startsWith("Bearer") && authorization.length() > 7) {
                String jwt = authorization.substring(7);
                String[] jwtParts = jwt.split("\\.");
                if (jwtParts.length == 3) {
                    SecretKeySpec signingKey = new SecretKeySpec(Config.JWT_SECRET().getBytes(), "HmacSHA1");
                    Mac mac;
                    try {
                        mac = Mac.getInstance("HmacSHA1");
                        mac.init(signingKey);
                        byte[] rawHmac = mac.doFinal((jwtParts[0] + "." + jwtParts[1]).getBytes());
                        String result = Base64.getUrlEncoder().encodeToString(rawHmac);
                        if (result.equals(jwtParts[2])) {
                            String jwtBody = new String(Base64.getUrlDecoder().decode(jwtParts[1]), StandardCharsets.UTF_8);
                            JSONObject jwtBodyObj = JSONObject.parseObject(jwtBody);
                            userVo.setUuid(jwtBodyObj.getString("useruuid"));
                            userVo.setUserId(jwtBodyObj.getString("userid"));
                            userVo.setUserName(jwtBodyObj.getString("username"));
                            return userVo;
                        }
                    } catch (NoSuchAlgorithmException | InvalidKeyException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return userVo;
    }

    @Override
    public String myDirectUrl() {
        return Config.DIRECT_URL();
    }

    @Override
    public UserVo myLogin(UserVo userVo, JSONObject resultJson) {
        return userMapper.getUserByUserIdAndPassword(userVo);
    }

}

DefaultLoginAuthHandler重写了LoginAuthHandlerBasemyAuth方法,myAuth方法的主要逻辑就是对请求头或Cookie中的JWT进行拆包获取用户信息,最终返回一个UserVo对象。

LoginController

package neatlogic.module.framework.login.handler;

import com.alibaba.fastjson.JSONObject;
import neatlogic.framework.common.ReturnJson;
import neatlogic.framework.common.config.Config;
import neatlogic.framework.dao.mapper.UserSessionMapper;
import neatlogic.framework.dto.JwtVo;
import neatlogic.framework.dto.UserVo;
import neatlogic.framework.exception.core.ApiRuntimeException;
import neatlogic.framework.exception.login.LoginAuthPluginNoFoundException;
import neatlogic.framework.exception.user.UserAuthFailedException;
import neatlogic.framework.filter.core.ILoginAuthHandler;
import neatlogic.framework.filter.core.LoginAuthFactory;
import neatlogic.framework.filter.core.LoginAuthHandlerBase;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Controller
@RequestMapping("/login/")
public class LoginController {
    Logger logger = LoggerFactory.getLogger(LoginController.class);

    @Resource
    private UserSessionMapper userSessionMapper;

    @RequestMapping(value = "/check")
    public void dispatcherForPost(@RequestBody String json, HttpServletRequest request, HttpServletResponse response) throws Exception {
        JSONObject returnObj = new JSONObject();
        JSONObject jsonObj = JSONObject.parseObject(json);
        JSONObject resultJson = new JSONObject();
        try {
            String userId = jsonObj.getString("userid");
            String password = jsonObj.getString("password");
            String authType = "default";
            if (StringUtils.isNotBlank(jsonObj.getString("authType"))) {
                authType = jsonObj.getString("authType");
            } else if (StringUtils.isNotBlank(Config.LOGIN_AUTH_TYPE())) {
                authType = Config.LOGIN_AUTH_TYPE();
            }
            // 验证并获取用户
            UserVo userVo = new UserVo();
            userVo.setUserId(userId);
            userVo.setPassword(password);
            //切换到具体的认证插件
            ILoginAuthHandler loginAuth = LoginAuthFactory.getLoginAuth(authType);
            if (loginAuth == null) {//配置了插件,但不在已有的插件范围内
                throw new LoginAuthPluginNoFoundException();
            }
            UserVo checkUserVo = loginAuth.login(userVo, returnObj);
            if (checkUserVo != null) {
                // 保存 user 登录访问时间
                userSessionMapper.insertUserSession(checkUserVo.getUuid());
                JwtVo jwtVo = LoginAuthHandlerBase.buildJwt(checkUserVo);
                LoginAuthHandlerBase.setResponseAuthCookie(response, request, jwtVo);
                returnObj.put("Status", "OK");
                returnObj.put("JwtToken", jwtVo.getJwthead() + "." + jwtVo.getJwtbody() + "." + jwtVo.getJwtsign());
                response.getWriter().print(returnObj);
            } else {
                throw new UserAuthFailedException();
            }
        } catch (ApiRuntimeException ex) {
            ReturnJson.error(ex.getMessage(), resultJson, response);
        } catch (Exception ex) {
            logger.error(ex.getMessage(), ex);
            ReturnJson.error(ex.getMessage(), response);
        }
    }

}

LoginController的逻辑比较简单,这里不做阐述。

AuthenticationFilter

package neatlogic.framework.filter;

import com.alibaba.fastjson.JSONObject;
import neatlogic.framework.asynchronization.threadlocal.UserContext;
import neatlogic.framework.common.config.Config;
import neatlogic.framework.dao.mapper.UserSessionMapper;
import neatlogic.framework.dto.UserSessionVo;
import neatlogic.framework.dto.UserVo;
import neatlogic.framework.filter.core.ILoginAuthHandler;
import neatlogic.framework.filter.core.LoginAuthFactory;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;

public class AuthenticationFilter extends OncePerRequestFilter {

    @Resource
    private UserSessionMapper userSessionMapper;

    public AuthenticationFilter() {
    }

    @Override
    public void destroy() {
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws IOException {
        boolean isUnExpired = false;
        UserVo userVo;
        JSONObject redirectObj = new JSONObject();
        String authTypeHeader = request.getHeader("AuthType");
        String authType = StringUtils.isNotBlank(authTypeHeader) ? authTypeHeader : "default";
        ILoginAuthHandler authHandler = LoginAuthFactory.getLoginAuth(authType);
        try {
            if (authHandler == null) {
                throw new RuntimeException("找不到认证方式 '" + authType + "'");
            }
            userVo = authHandler.auth(request, response);
            if (userVo != null) {
                isUnExpired = userExpirationValid();
            }
            if (userVo != null && isUnExpired) {
                filterChain.doFilter(request, response);
            } else {
                if (userVo == null) {
                    response.setStatus(522);
                    redirectObj.put("Status", "FAILED");
                    redirectObj.put("Message", "认证失败(" + authHandler.getType() + "),请重新登录");
                    redirectObj.put("DirectUrl", authHandler.directUrl());
                } else {
                    response.setStatus(527);
                    redirectObj.put("Status", "FAILED");
                    redirectObj.put("Message", "会话已超时或已被终止,请重新登录");
                    redirectObj.put("DirectUrl", authHandler.directUrl());
                }
                removeAuthCookie(response);
                response.setContentType(Config.RESPONSE_TYPE_JSON);
                response.getWriter().print(redirectObj.toJSONString());
            }
        } catch (Exception ex) {
            logger.error("认证失败", ex);
            response.setStatus(522);
            redirectObj.put("Status", "FAILED");
            redirectObj.put("Message", ex.getMessage());
            redirectObj.put("DirectUrl", authHandler != null ? authHandler.directUrl() : Config.DIRECT_URL());
            removeAuthCookie(response);
        } finally {
            response.setContentType(Config.RESPONSE_TYPE_JSON);
            response.getWriter().print(redirectObj.toJSONString());
        }
    }

    /**
     * 登录异常后端清除authorization cookie,防止sso循环跳转
     */
    private void removeAuthCookie(HttpServletResponse response) {
        Cookie authCookie = new Cookie("authorization", null);
        authCookie.setPath("/");
        authCookie.setMaxAge(0);//表示删除
        response.addCookie(authCookie);
    }

    /**
     * 校验用户登录超时
     */
    private boolean userExpirationValid() {
        String userUuid = UserContext.get().getUserUuid();
        UserSessionVo userSessionVo = userSessionMapper.getUserSessionByUserUuid(userUuid);
        if (null != userSessionVo) {
            Date visitTime = userSessionVo.getSessionTime();
            Date now = new Date();
            int expire = Config.USER_EXPIRETIME();
            long expireTime = expire * 60L * 1000L + visitTime.getTime();
            if (now.getTime() < expireTime) {
                userSessionMapper.updateUserSession(userUuid);
                return true;
            }
            userSessionMapper.deleteUserSessionByUserUuid(userUuid);
        } else {
            return true;
        }
        return false;
    }

}

AuthenticationFilter的逻辑与LoginController类似,值得注意的是,AuthenticationFilter继承的是OncePerRequestFilterOncePerRequestFilter的主要目的是兼容不同的WEB容器,因为Servlet版本不同,执行的过程也不同,其实不是所有的容器一次请求只过滤一次。OncePerRequestFilter可以保证一次外部请求,只执行一次过滤方法,对于服务器内部之间的forward等请求,不会再次执行过滤方法。

结语

代码中使用到的一些类我们并没有一一给出定义,这是出于防止篇幅过长的考虑,同时也为了提炼精髓。一些无关紧要的细节在实际开发中自由发挥即可,我们只需要理清主体思路、把握整体脉络,不囿于细枝末节。