本案例是使用的简单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>