簡單的系統登錄案例

本案例是使用的簡單token作爲驗證;而token與用戶信息會保存在redis中;

簡單說明一下本案例的一些邏輯,可能存在某些漏洞,希望大家指正;


用戶未登錄時,訪問系統中任意controller方法都將跳轉至登錄頁面;

用戶使用URL訪問系統中任意頁面(我們是將頁面放在了WEB-INF之外的)都將跳轉至登錄頁面;

用戶已登錄,每次請求服務器,將token超時時間延後30分鐘;

用戶已登錄,訪問登錄頁面都將跳轉至已登錄的首頁;

session超時將清除redis中token,平臺的token超時將清除session中所有信息;

用戶登出,清除session信息,清除Redis中的token信息;


redis相關代碼:

jedis連接池:

public class JedisClientPool {
    
    private static JedisClientPool jedisClientPool = new JedisClientPool();
    private static JedisPool pool;

    //redis連接相關屬性
    private static String host;
    private static Integer port;
    private static String password;
    private static Integer timeout;
    private static Integer poolMaxtotal;
    private static Integer poolMaxIdle;
    private static Long poolMaxwaitMillis;

    private JedisClientPool(){
    }

    static{
        //自定義的properties讀取器,獲取properties文件中的值;
        host = PropertiesHandler.get(String.class,"redis_host");
        port = PropertiesHandler.get(Integer .class,"redis_port");
        password = PropertiesHandler.get(String.class,"redis_password");
        timeout = PropertiesHandler.get(Integer.class,"redis_timeout");
        poolMaxtotal = PropertiesHandler.get(Integer.class,"redis_pool_maxtotal");
        poolMaxIdle = PropertiesHandler.get(Integer.class,"redis_pool_maxIdle");
        poolMaxwaitMillis = PropertiesHandler.get(Long.class,"redis_pool_maxwaitMillis");
    }

    //初始化jedisPool對象;
    private static void initJedisPool(){
        JedisPoolConfig jedisPoolConfig = initPoolConfig();
        pool = new JedisPool(jedisPoolConfig,host,port,timeout);
    }
    //初始化jedisPoolConfig對象;
    private static JedisPoolConfig initPoolConfig(){
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(poolMaxIdle);
        jedisPoolConfig.setMaxTotal(poolMaxtotal);
        jedisPoolConfig.setMaxWaitMillis(poolMaxwaitMillis);
        return jedisPoolConfig;
    }
    //單例模式,確保系統中只有一個Jedis連接池;
    public static JedisClientPool getInstance(){
        if(pool == null){
            synchronized (JedisClientPool.class){
                if(pool == null){
                    initJedisPool();
                }
            }
        }
        return jedisClientPool;
    }
    //獲取jedis連接,可以在返回連接之前做密碼驗證;
    public Jedis getResource() {
        Jedis resource = pool.getResource();
//        resource.auth(password);
        return resource;
    }
    //歸還連接給連接池;
    public void returnResource(Jedis jedis){
        jedis.close();
    }
}

操作redis的工具類:

public class RedisCache {
    //得到一個自定義的jedis連接池對象
    private static JedisClientPool pool = JedisClientPool.getInstance();

    private final static int TIME_OUT_SECOND = 60;
    /**
     * 從redis連接池中獲取一個連接
     * @return
     */
    private static Jedis getJedis(){
        Jedis resource = pool.getResource();
        return resource;
    }


    /**
     * 歸還一個連接給redis連接池
     * @param jedis
     */
    private static void returnJedis(Jedis jedis){
        pool.returnResource(jedis);
    }

    /**
     * 判斷redis中該key是否存在
     * @param key
     * @return
     */
    public static Boolean exists(final String key) {
        Jedis jedis = getJedis();
        Boolean exists = jedis.exists(StrUtil.encodeStr(key));
        returnJedis(jedis);
        return exists;
    }

    /**
     * 獲取redis中指定key的值
     * @param key
     * @return
     */
    public static String get(String key){
        Jedis jedis = getJedis();
        String string = StrUtil.decodeToString(jedis.get(StrUtil.encodeStr(key)));
        returnJedis(jedis);
        return string;
    }

    /**
     * 向redis中設置一對key-value
     * @param key
     * @param value
     */
    public static void set(String key ,String value){
        Jedis jedis = getJedis();
        jedis.set(StrUtil.encodeStr(key), StrUtil.encodeStr(value));
        returnJedis(jedis);

    }

    /**
     * 刪除redis中指定的key
     * @param key
     */
    public static void del(String key){
        Jedis jedis = getJedis();
        jedis.del(StrUtil.encodeStr(key));
        returnJedis(jedis);
    }

    /**
     * 爲redis中指定key設置超時時間
     * @param key
     * @param seconds
     */
    public static void expire(String key,int seconds){
        Jedis jedis = getJedis();
        jedis.expire(StrUtil.encodeStr(key),seconds);
        returnJedis(jedis);
    }
}

redis相關已經完畢,接下來就是過濾器的一些規則:

token過濾器,由於我們使用的是nutz框架,所以繼承的是框架內的filter,返回也不一樣,但是對於session和token的驗證還是可以複用到其他過濾器中;

public class TokenFilter implements ActionFilter {
    @Override
    public View match(ActionContext actionContext) {
        HttpServletRequest request = actionContext.getRequest();
        String pathInfo = request.getRequestURI();
        System.out.println("requestPathis : "+pathInfo);


        HttpSession session = request.getSession();
        if(session == null){//session爲空,跳轉登錄頁面;
            return new DlxView("/login.html");
        }
        String authorization = (String) session.getAttribute("authorization");
        if(StrUtil.isBlank(authorization)){
            if(StrUtil.hasVal(pathInfo) && pathInfo.contains("/NDAS/login/")){
                //登錄模塊中的請求方法都可直接通過;
                //session中沒有token,且路徑是去往登錄方法時,允許通過
                return null;
            }else{
                //否則去往登錄頁面且清除session
                session.invalidate();
                return new DlxView("/login.html");
            }
        }
        //session中存在token,去redis中驗證
        Boolean exists = RedisCache.exists(authorization);
        if(!exists){//如果redis中不存在token,跳轉至登錄頁面
            System.out.println("redis token is not exists!!!");
            session.invalidate();//並清理session;
            return new DlxView("/login.html");
        }else{//如果redis中存在token,那麼證明此次用戶操作有效,則將token有效時長設置爲指定時間之後
            System.out.println(authorization + " will timeout after " + 1800 + " seconds!!!");
            RedisCache.expire(authorization,1800);
        }
        return null;
    }
}

由於我們的頁面時放在WEB-INF之外,所以需要一個靜態資源過濾器:

public class StaticFilter implements Filter {

    Log log = Logs.get();

    private static List<String> SUFFIX_LIST =new ArrayList<>();
    static {
        //初始化非頁面靜態資源後綴;
        SUFFIX_LIST.add("png");
        SUFFIX_LIST.add("gif");
        SUFFIX_LIST.add("jpg");
        SUFFIX_LIST.add("js");
        SUFFIX_LIST.add("css");
        SUFFIX_LIST.add("jspx");
        SUFFIX_LIST.add("jpeg");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("contextPath is "+filterConfig.getServletContext().getContextPath());
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //將filter中的servletRequest,servletResponse強轉爲http格式的;方便後面獲取url和session;
        HttpServletResponse resp = (HttpServletResponse) response;
        HttpServletRequest req = (HttpServletRequest) request;
        //從request中獲取url;
        String requestURL = req.getRequestURL().toString();
        //從request中獲取session;
        HttpSession session = req.getSession();
        //獲取session中的token;
        String token = (String) session.getAttribute("authorization");


        int lastIndexOf = requestURL.lastIndexOf(".");
        int lastIndexOf1 = requestURL.lastIndexOf("?");
        //首先暫定爲沒有帶參數的url
        String suffix = requestURL.substring(lastIndexOf + 1);
        //根據判斷,如果是帶參數的url則重新切割
        if(lastIndexOf1 != -1 && lastIndexOf1 > lastIndexOf){
            suffix = requestURL.substring(lastIndexOf+1,lastIndexOf1);
        }

        if(lastIndexOf == -1){
            log.info("url has no suffix");
            //沒有後綴,將會被重定向到登錄首頁;
            resp.sendRedirect("http://localhost:8090/XXXX/XXXX/toLogin.xhtml");
            return;
        }else{
            if(!SUFFIX_LIST.contains(suffix)&&("jsp".equals(suffix)||"html".equals(suffix))&&!"xhtml".equals(suffix)){
                //後綴匹配不上,重定向到首頁;
                resp.sendRedirect("http://localhost:8090/XXXX/XXXX/toLogin.xhtml");
                return;
            }
        }
        //如果沒有token,則對html和jsp的直接訪問url進行攔截
        if(StrUtil.isBlank(token)){
            //如果url以/結尾,則表示是直接訪問系統中的index.jsp或者index.html
            if(requestURL.endsWith("/")){
                log.info("url endWith '/'");
                //如果是直接訪問index.jsp,則重定向到登錄首頁
                resp.sendRedirect("http://localhost:8090/XXXX/XXXX/toLogin.xhtml");
                return;
            }else{
                if(StrUtil.hasVal(suffix)){
                    if(SUFFIX_LIST.contains(suffix)){
                        //普通靜態資源不做處理;
                        chain.doFilter(request,response);
                        return ;
                    }
                    if("jsp".equals(suffix)||"html".equals(suffix)){
                        log.info("url endWith jsp or html");
                        //直接訪問jsp或者html頁面,將會被重定向到登錄首頁;
                        resp.sendRedirect("http://localhost:8090/XXXX/XXXX/toLogin.xhtml");
                        return;
                    }
                }
            }
        }

        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {
        log.info("staticFilter is destroy");
    }
}

token(UUID)生成器:

public class UUIDFactory {

    /**
     * 獲取一個32位的UUID;
     * @return
     */
    public static String getUUID(){
        String uid = UUID.randomUUID().toString().replace("-", "");
        return uid;
    }

    /**
     * 獲取一個指定長度的UUID
     * @param length
     * @return
     */
    public static String getUUID(int length){
        String uid = UUID.randomUUID().toString().replace("-", "");
        uid = uid.substring(0, length);
        return uid;
    }
}

web.xml配置(注意,如果tokenfilter是實現的filter則也要在web.xml中配置)

    <filter>
        <filter-name>staticFilter</filter-name>
        <filter-class>com.XXXX.XXXX.XXXX.StaticFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>staticFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>


    <listener>
        <listener-class>com.XXXX.XXXX.XXXX.DlxSessionListener</listener-class>
    </listener>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章