本案例是使用的簡單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>