一、配置文件
upms-server/springMVC-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 根目录'/'对应页面 -->
<mvc:view-controller path="/" view-name="/index.jsp"/> <!-- 拦截器 -->
<mvc:interceptors>
<!-- 获取登录信息 -->
<mvc:interceptor>
<mvc:mapping path="/manage/**"/>
<bean class="com.zheng.upms.server.interceptor.UpmsInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors> <!-- Jsp视图 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="order" value=""/>
<property name="prefix" value="/WEB-INF/jsp"/>
<property name="suffix" value=""/>
<property name="contentType" value="text/html; charset=utf-8"/>
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
</bean> </beans>
登陆信息拦截器:
package com.zheng.upms.server.interceptor; import com.zheng.common.util.PropertiesFileUtil;
import com.zheng.upms.dao.model.UpmsUser;
import com.zheng.upms.rpc.api.UpmsApiService;
import com.zheng.upms.server.controller.manage.UpmsOrganizationController;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; /**
* 登录信息拦截器
* Created by shuzheng on 2017/2/11.
*/
public class UpmsInterceptor extends HandlerInterceptorAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(UpmsInterceptor.class);
private static final String ZHENG_OSS_ALIYUN_OSS_POLICY = PropertiesFileUtil.getInstance("zheng-oss-client").get("zheng.oss.aliyun.oss.policy"); @Autowired
UpmsApiService upmsApiService; @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
request.setAttribute("ZHENG_OSS_ALIYUN_OSS_POLICY", ZHENG_OSS_ALIYUN_OSS_POLICY);
// 过滤ajax
if (null != request.getHeader("X-Requested-With") && "XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"))) {
return true;
}
// 登录信息
Subject subject = SecurityUtils.getSubject();
String username = (String) subject.getPrincipal();
UpmsUser upmsUser = upmsApiService.selectUpmsUserByUsername(username);
request.setAttribute("upmsUser", upmsUser);
return true;
} @Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
super.postHandle(request, response, handler, modelAndView);
} @Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
super.afterCompletion(request, response, handler, ex);
} @Override
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
super.afterConcurrentHandlingStarted(request, response, handler);
} }
zheng-upms-server:resources/profiles:dev.properties
app.name=zheng-upms-server
profile.env=dev ##### redis #####
master.redis.ip=rdserver
master.redis.port=
master.redis.password=FNFl9F2O2Skb8yoKM0jhHA==
master.redis.max_active=
master.redis.max_idle=
master.redis.max_wait=
master.redis.timeout= ##### zheng-admin #####
zheng.admin.version=1.0. ##### zheng-config #####
#zheng.config.path=http://config.zhangshuzheng.cn:1000/${app.name}/${profile.env}
zheng.config.path=http://127.0.0.1:1000/${app.name}/${profile.env}
##### zheng-upms #####
# \u7EC8\u7AEF\u7C7B\u578B
zheng.upms.type=server
# \u7EC8\u7AEFsession\u540D\u79F0
zheng.upms.session.id=zheng-upms-server-session-id
# \u4F1A\u8BDD\u65F6\u957F,\u534A\u5C0F\u65F6\uFF08\u5355\u4F4D\u6BEB\u79D2\uFF09
zheng.upms.session.timeout=
# \u5355\u70B9\u767B\u5F55\u8BA4\u8BC1\u4E2D\u5FC3\u5730\u5740
#zheng.upms.sso.server.url=http://upms.zhangshuzheng.cn:1111
zheng.upms.sso.server.url=http://127.0.0.1:1111
# \u767B\u5F55\u6210\u529F\u56DE\u8C03\u5730\u5740
zheng.upms.successUrl=/manage/index
# \u672A\u6388\u6743\u5730\u5740
zheng.upms.unauthorizedUrl=/
# \u8BB0\u4F4F\u5BC6\u7801\u65F6\u957F30\u5929
zheng.upms.rememberMe.timeout= ##### zheng-oss #####
#zheng.oss.aliyun.oss.policy=http://oss.zhangshuzheng.cn:7771/aliyun/oss/policy
zheng.oss.aliyun.oss.policy=http://127.0.0.1:7771/aliyun/oss/policy
zheng-upms-client:resources/applicationContext-shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <description>zheng-upms</description> <context:property-placeholder location="classpath*:zheng-upms-client.properties"/> <!-- Shiro的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="${zheng.upms.sso.server.url}"/>
<property name="successUrl" value="${zheng.upms.successUrl}"/>
<property name="unauthorizedUrl" value="${zheng.upms.unauthorizedUrl}"/>
<property name="filters">
<util:map>
<entry key="authc" value-ref="upmsAuthenticationFilter"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/manage/** = upmsSessionForceLogout,authc
/manage/index = user
/druid/** = user
/swagger-ui.html = user
/resources/** = anon
/** = anon
</value>
</property>
</bean> <!-- 重写authc过滤器 -->
<bean id="upmsAuthenticationFilter" class="com.zheng.upms.client.shiro.filter.UpmsAuthenticationFilter"/> <!-- 强制退出会话过滤器 -->
<bean id="upmsSessionForceLogout" class="com.zheng.upms.client.shiro.filter.UpmsSessionForceLogoutFilter"/> <!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realms">
<list><ref bean="upmsRealm"/></list>
</property>
<property name="sessionManager" ref="sessionManager"/>
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean> <!-- realm实现,继承自AuthorizingRealm -->
<bean id="upmsRealm" class="com.zheng.upms.client.shiro.realm.UpmsRealm"></bean> <!-- 会话管理器 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- 全局session超时时间 -->
<property name="globalSessionTimeout" value="${zheng.upms.session.timeout}"/>
<!-- sessionDAO -->
<property name="sessionDAO" ref="sessionDAO"/>
<property name="sessionIdCookieEnabled" value="true"/>
<property name="sessionIdCookie" ref="sessionIdCookie"/>
<property name="sessionValidationSchedulerEnabled" value="false"/>
<property name="sessionListeners">
<list><ref bean="sessionListener"/></list>
</property>
<property name="sessionFactory" ref="sessionFactory"/>
</bean> <!-- 会话DAO,可重写,持久化session -->
<bean id="sessionDAO" class="com.zheng.upms.client.shiro.session.UpmsSessionDao"/> <!-- 会话Cookie模板 -->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<!-- 不会暴露给客户端 -->
<property name="httpOnly" value="true"/>
<!-- 设置Cookie的过期时间,秒为单位,默认-1表示关闭浏览器时过期Cookie -->
<property name="maxAge" value="-1"/>
<!-- Cookie名称 -->
<property name="name" value="${zheng.upms.session.id}"/>
</bean> <!-- 会话监听器 -->
<bean id="sessionListener" class="com.zheng.upms.client.shiro.listener.UpmsSessionListener"/> <!-- session工厂 -->
<bean id="sessionFactory" class="com.zheng.upms.client.shiro.session.UpmsSessionFactory"/> <!-- rememberMe管理器 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<!-- rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)-->
<property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}"/>
<property name="cookie" ref="rememberMeCookie"/>
</bean> <!-- rememberMe缓存cookie -->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="rememberMe"/>
<!-- 不会暴露给客户端 -->
<property name="httpOnly" value="true"/>
<!-- 记住我cookie生效时间 -->
<property name="maxAge" value="${zheng.upms.rememberMe.timeout}"/>
</bean> <!-- 设置SecurityUtils,相当于调用SecurityUtils.setSecurityManager(securityManager) -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
<property name="arguments" ref="securityManager"/>
</bean> <!-- 开启Shiro Spring AOP权限注解@RequiresPermissions的支持 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean> <!-- Shiro生命周期处理器-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> </beans>
authc过滤器:
package com.zheng.upms.client.shiro.filter; import com.alibaba.fastjson.JSONObject;
import com.zheng.common.util.PropertiesFileUtil;
import com.zheng.common.util.RedisUtil;
import com.zheng.upms.client.shiro.session.UpmsSessionDao;
import com.zheng.upms.client.util.RequestParameterUtil;
import com.zheng.upms.common.constant.UpmsConstant;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import redis.clients.jedis.Jedis; import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List; /**
* 重写authc过滤器
* Created by shuzheng on 2017/3/11.
*/
public class UpmsAuthenticationFilter extends AuthenticationFilter { private static final Logger LOGGER = LoggerFactory.getLogger(UpmsAuthenticationFilter.class); // 局部会话key
private final static String ZHENG_UPMS_CLIENT_SESSION_ID = "zheng-upms-client-session-id";
// 单点同一个code所有局部会话key
private final static String ZHENG_UPMS_CLIENT_SESSION_IDS = "zheng-upms-client-session-ids"; @Autowired
UpmsSessionDao upmsSessionDao; @Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
Subject subject = getSubject(request, response);
Session session = subject.getSession();
// 判断请求类型
String upmsType = PropertiesFileUtil.getInstance("zheng-upms-client").get("zheng.upms.type");
session.setAttribute(UpmsConstant.UPMS_TYPE, upmsType);
if ("client".equals(upmsType)) {
return validateClient(request, response);
}
if ("server".equals(upmsType)) {
return subject.isAuthenticated();
}
return false;
} @Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
StringBuffer ssoServerUrl = new StringBuffer(PropertiesFileUtil.getInstance("zheng-upms-client").get("zheng.upms.sso.server.url"));
// server需要登录
String upmsType = PropertiesFileUtil.getInstance("zheng-upms-client").get("zheng.upms.type");
if ("server".equals(upmsType)) {
WebUtils.toHttp(response).sendRedirect(ssoServerUrl.append("/sso/login").toString());
return false;
}
ssoServerUrl.append("/sso/index").append("?").append("appid").append("=").append(PropertiesFileUtil.getInstance("zheng-upms-client").get("zheng.upms.appID"));
// 回跳地址
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
StringBuffer backurl = httpServletRequest.getRequestURL();
String queryString = httpServletRequest.getQueryString();
if (StringUtils.isNotBlank(queryString)) {
backurl.append("?").append(queryString);
}
ssoServerUrl.append("&").append("backurl").append("=").append(URLEncoder.encode(backurl.toString(), "utf-8"));
WebUtils.toHttp(response).sendRedirect(ssoServerUrl.toString());
return false;
} /**
* 认证中心登录成功带回code
* @param request
*/
private boolean validateClient(ServletRequest request, ServletResponse response) {
Subject subject = getSubject(request, response);
Session session = subject.getSession();
String sessionId = session.getId().toString();
int timeOut = (int) session.getTimeout() / ;
// 判断局部会话是否登录
String cacheClientSession = RedisUtil.get(ZHENG_UPMS_CLIENT_SESSION_ID + "_" + session.getId());
if (StringUtils.isNotBlank(cacheClientSession)) {
// 更新code有效期
RedisUtil.set(ZHENG_UPMS_CLIENT_SESSION_ID + "_" + sessionId, cacheClientSession, timeOut);
Jedis jedis = RedisUtil.getJedis();
jedis.expire(ZHENG_UPMS_CLIENT_SESSION_IDS + "_" + cacheClientSession, timeOut);
jedis.close();
// 移除url中的code参数
if (null != request.getParameter("code")) {
String backUrl = RequestParameterUtil.getParameterWithOutCode(WebUtils.toHttp(request));
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
try {
httpServletResponse.sendRedirect(backUrl.toString());
} catch (IOException e) {
LOGGER.error("局部会话已登录,移除code参数跳转出错:", e);
}
} else {
return true;
}
}
// 判断是否有认证中心code
String code = request.getParameter("upms_code");
// 已拿到code
if (StringUtils.isNotBlank(code)) {
// HttpPost去校验code
try {
StringBuffer ssoServerUrl = new StringBuffer(PropertiesFileUtil.getInstance("zheng-upms-client").get("zheng.upms.sso.server.url"));
HttpClient httpclient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(ssoServerUrl.toString() + "/sso/code"); List<NameValuePair> nameValuePairs = new ArrayList<>();
nameValuePairs.add(new BasicNameValuePair("code", code));
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs)); HttpResponse httpResponse = httpclient.execute(httpPost);
if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity httpEntity = httpResponse.getEntity();
JSONObject result = JSONObject.parseObject(EntityUtils.toString(httpEntity));
if ( == result.getIntValue("code") && result.getString("data").equals(code)) {
// code校验正确,创建局部会话
RedisUtil.set(ZHENG_UPMS_CLIENT_SESSION_ID + "_" + sessionId, code, timeOut);
// 保存code对应的局部会话sessionId,方便退出操作
RedisUtil.sadd(ZHENG_UPMS_CLIENT_SESSION_IDS + "_" + code, sessionId, timeOut);
LOGGER.debug("当前code={},对应的注册系统个数:{}个", code, RedisUtil.getJedis().scard(ZHENG_UPMS_CLIENT_SESSION_IDS + "_" + code));
// 移除url中的token参数
String backUrl = RequestParameterUtil.getParameterWithOutCode(WebUtils.toHttp(request));
// 返回请求资源
try {
// client无密认证
String username = request.getParameter("upms_username");
subject.login(new UsernamePasswordToken(username, ""));
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
httpServletResponse.sendRedirect(backUrl.toString());
return true;
} catch (IOException e) {
LOGGER.error("已拿到code,移除code参数跳转出错:", e);
}
} else {
LOGGER.warn(result.getString("data"));
}
}
} catch (IOException e) {
LOGGER.error("验证token失败:", e);
}
}
return false;
} }
强制退出会话过滤器:
package com.zheng.upms.client.shiro.filter; import org.apache.shiro.session.Session;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils; import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse; /**
* 强制退出会话过滤器
* Created by shuzheng on 2017/3/1.
*/
public class UpmsSessionForceLogoutFilter extends AccessControlFilter { @Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
Session session = getSubject(request, response).getSession(false);
if(session == null) {
return true;
}
boolean forceout = session.getAttribute("FORCE_LOGOUT") == null;
return forceout;
} @Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
getSubject(request, response).logout();
String loginUrl = getLoginUrl() + (getLoginUrl().contains("?") ? "&" : "?") + "forceLogout=1";
WebUtils.issueRedirect(request, response, loginUrl);
return false;
} }
realm自定义实现:
package com.zheng.upms.client.shiro.realm; import com.zheng.common.util.MD5Util;
import com.zheng.common.util.PropertiesFileUtil;
import com.zheng.upms.dao.model.UpmsPermission;
import com.zheng.upms.dao.model.UpmsRole;
import com.zheng.upms.dao.model.UpmsUser;
import com.zheng.upms.rpc.api.UpmsApiService;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import java.util.HashSet;
import java.util.List;
import java.util.Set; /**
* 用户认证和授权
* Created by shuzheng on 2017/1/20.
*/
public class UpmsRealm extends AuthorizingRealm { private static final Logger LOGGER = LoggerFactory.getLogger(UpmsRealm.class); @Autowired
private UpmsApiService upmsApiService; /**
* 授权:验证权限时调用
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String username = (String) principalCollection.getPrimaryPrincipal();
UpmsUser upmsUser = upmsApiService.selectUpmsUserByUsername(username); // 当前用户所有角色
List<UpmsRole> upmsRoles = upmsApiService.selectUpmsRoleByUpmsUserId(upmsUser.getUserId());
Set<String> roles = new HashSet<>();
for (UpmsRole upmsRole : upmsRoles) {
if (StringUtils.isNotBlank(upmsRole.getName())) {
roles.add(upmsRole.getName());
}
} // 当前用户所有权限
List<UpmsPermission> upmsPermissions = upmsApiService.selectUpmsPermissionByUpmsUserId(upmsUser.getUserId());
Set<String> permissions = new HashSet<>();
for (UpmsPermission upmsPermission : upmsPermissions) {
if (StringUtils.isNotBlank(upmsPermission.getPermissionValue())) {
permissions.add(upmsPermission.getPermissionValue());
}
} SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setStringPermissions(permissions);
simpleAuthorizationInfo.setRoles(roles);
return simpleAuthorizationInfo;
} /**
* 认证:登录时调用
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String username = (String) authenticationToken.getPrincipal();
String password = new String((char[]) authenticationToken.getCredentials());
// client无密认证
String upmsType = PropertiesFileUtil.getInstance("zheng-upms-client").get("zheng.upms.type");
if ("client".equals(upmsType)) {
return new SimpleAuthenticationInfo(username, password, getName());
} // 查询用户信息
UpmsUser upmsUser = upmsApiService.selectUpmsUserByUsername(username); if (null == upmsUser) {
throw new UnknownAccountException();
}
if (!upmsUser.getPassword().equals(MD5Util.md5(password + upmsUser.getSalt()))) {
throw new IncorrectCredentialsException();
}
if (upmsUser.getLocked() == ) {
throw new LockedAccountException();
} return new SimpleAuthenticationInfo(username, password, getName());
} }
会话DAO:
package com.zheng.upms.client.shiro.session; import com.zheng.common.util.RedisUtil;
import com.zheng.upms.client.util.SerializableUtil;
import com.zheng.upms.common.constant.UpmsConstant;
import org.apache.commons.lang.ObjectUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.ValidatingSession;
import org.apache.shiro.session.mgt.eis.CachingSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis; import java.io.Serializable;
import java.util.*; /**
* 基于redis的sessionDao,缓存共享session
* Created by shuzheng on 2017/2/23.
*/
public class UpmsSessionDao extends CachingSessionDAO { private static final Logger LOGGER = LoggerFactory.getLogger(UpmsSessionDao.class);
// 会话key
private final static String ZHENG_UPMS_SHIRO_SESSION_ID = "zheng-upms-shiro-session-id";
// 全局会话key
private final static String ZHENG_UPMS_SERVER_SESSION_ID = "zheng-upms-server-session-id";
// 全局会话列表key
private final static String ZHENG_UPMS_SERVER_SESSION_IDS = "zheng-upms-server-session-ids";
// code key
private final static String ZHENG_UPMS_SERVER_CODE = "zheng-upms-server-code";
// 局部会话key
private final static String ZHENG_UPMS_CLIENT_SESSION_ID = "zheng-upms-client-session-id";
// 单点同一个code所有局部会话key
private final static String ZHENG_UPMS_CLIENT_SESSION_IDS = "zheng-upms-client-session-ids"; @Override
protected Serializable doCreate(Session session) {
Serializable sessionId = generateSessionId(session);
assignSessionId(session, sessionId);
RedisUtil.set(ZHENG_UPMS_SHIRO_SESSION_ID + "_" + sessionId, SerializableUtil.serialize(session), (int) session.getTimeout() / );
LOGGER.debug("doCreate >>>>> sessionId={}", sessionId);
return sessionId;
} @Override
protected Session doReadSession(Serializable sessionId) {
String session = RedisUtil.get(ZHENG_UPMS_SHIRO_SESSION_ID + "_" + sessionId);
LOGGER.debug("doReadSession >>>>> sessionId={}", sessionId);
return SerializableUtil.deserialize(session);
} @Override
protected void doUpdate(Session session) {
// 如果会话过期/停止 没必要再更新了
if(session instanceof ValidatingSession && !((ValidatingSession)session).isValid()) {
return;
}
// 更新session的最后一次访问时间
UpmsSession upmsSession = (UpmsSession) session;
UpmsSession cacheUpmsSession = (UpmsSession) doReadSession(session.getId());
if (null != cacheUpmsSession) {
upmsSession.setStatus(cacheUpmsSession.getStatus());
upmsSession.setAttribute("FORCE_LOGOUT", cacheUpmsSession.getAttribute("FORCE_LOGOUT"));
}
RedisUtil.set(ZHENG_UPMS_SHIRO_SESSION_ID + "_" + session.getId(), SerializableUtil.serialize(session), (int) session.getTimeout() / );
// 更新ZHENG_UPMS_SERVER_SESSION_ID、ZHENG_UPMS_SERVER_CODE过期时间 TODO
LOGGER.debug("doUpdate >>>>> sessionId={}", session.getId());
} @Override
protected void doDelete(Session session) {
String sessionId = session.getId().toString();
String upmsType = ObjectUtils.toString(session.getAttribute(UpmsConstant.UPMS_TYPE));
if ("client".equals(upmsType)) {
// 删除局部会话和同一code注册的局部会话
String code = RedisUtil.get(ZHENG_UPMS_CLIENT_SESSION_ID + "_" + sessionId);
Jedis jedis = RedisUtil.getJedis();
jedis.del(ZHENG_UPMS_CLIENT_SESSION_ID + "_" + sessionId);
jedis.srem(ZHENG_UPMS_CLIENT_SESSION_IDS + "_" + code, sessionId);
jedis.close();
}
if ("server".equals(upmsType)) {
// 当前全局会话code
String code = RedisUtil.get(ZHENG_UPMS_SERVER_SESSION_ID + "_" + sessionId);
// 清除全局会话
RedisUtil.remove(ZHENG_UPMS_SERVER_SESSION_ID + "_" + sessionId);
// 清除code校验值
RedisUtil.remove(ZHENG_UPMS_SERVER_CODE + "_" + code);
// 清除所有局部会话
Jedis jedis = RedisUtil.getJedis();
Set<String> clientSessionIds = jedis.smembers(ZHENG_UPMS_CLIENT_SESSION_IDS + "_" + code);
for (String clientSessionId : clientSessionIds) {
jedis.del(ZHENG_UPMS_CLIENT_SESSION_ID + "_" + clientSessionId);
jedis.srem(ZHENG_UPMS_CLIENT_SESSION_IDS + "_" + code, clientSessionId);
}
LOGGER.debug("当前code={},对应的注册系统个数:{}个", code, jedis.scard(ZHENG_UPMS_CLIENT_SESSION_IDS + "_" + code));
jedis.close();
// 维护会话id列表,提供会话分页管理
RedisUtil.lrem(ZHENG_UPMS_SERVER_SESSION_IDS, , sessionId);
}
// 删除session
RedisUtil.remove(ZHENG_UPMS_SHIRO_SESSION_ID + "_" + sessionId);
LOGGER.debug("doUpdate >>>>> sessionId={}", sessionId);
} /**
* 获取会话列表
* @param offset
* @param limit
* @return
*/
public Map getActiveSessions(int offset, int limit) {
LOGGER.info("进入getActiveSession获取当前活动的session数据,服务端分页需要返回total 和rows");
Map sessions = new HashMap();
Jedis jedis = RedisUtil.getJedis();
// 获取在线会话总数
long total = jedis.llen(ZHENG_UPMS_SERVER_SESSION_IDS);
// 获取当前页会话详情
List<String> ids = jedis.lrange(ZHENG_UPMS_SERVER_SESSION_IDS, offset, (offset + limit - ));
List<Session> rows = new ArrayList<>();
for (String id : ids) {
String session = RedisUtil.get(ZHENG_UPMS_SHIRO_SESSION_ID + "_" + id);
// 过滤redis过期session
if (null == session) {
RedisUtil.lrem(ZHENG_UPMS_SERVER_SESSION_IDS, , id);
total = total - ;
continue;
}
rows.add(SerializableUtil.deserialize(session));
}
jedis.close();
sessions.put("total", total);
sessions.put("rows", rows);
return sessions;
} /**
* 强制退出
* @param ids
* @return
*/
public int forceout(String ids) {
String[] sessionIds = ids.split(",");
for (String sessionId : sessionIds) {
// 会话增加强制退出属性标识,当此会话访问系统时,判断有该标识,则退出登录
String session = RedisUtil.get(ZHENG_UPMS_SHIRO_SESSION_ID + "_" + sessionId);
UpmsSession upmsSession = (UpmsSession) SerializableUtil.deserialize(session);
upmsSession.setStatus(UpmsSession.OnlineStatus.force_logout);
upmsSession.setAttribute("FORCE_LOGOUT", "FORCE_LOGOUT");
RedisUtil.set(ZHENG_UPMS_SHIRO_SESSION_ID + "_" + sessionId, SerializableUtil.serialize(upmsSession), (int) upmsSession.getTimeout() / );
}
return sessionIds.length;
} /**
* 更改在线状态
*
* @param sessionId
* @param onlineStatus
*/
public void updateStatus(Serializable sessionId, UpmsSession.OnlineStatus onlineStatus) {
UpmsSession session = (UpmsSession) doReadSession(sessionId);
if (null == session) {
return;
}
session.setStatus(onlineStatus);
RedisUtil.set(ZHENG_UPMS_SHIRO_SESSION_ID + "_" + session.getId(), SerializableUtil.serialize(session), (int) session.getTimeout() / );
} }
会话监听器:
package com.zheng.upms.client.shiro.listener; import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* Created by shuzheng on 2017/2/12.
*/
public class UpmsSessionListener implements SessionListener { private static final Logger LOGGER = LoggerFactory.getLogger(UpmsSessionListener.class); @Override
public void onStart(Session session) {
LOGGER.debug("会话创建:" + session.getId());
} @Override
public void onStop(Session session) {
LOGGER.debug("会话停止:" + session.getId());
} @Override
public void onExpiration(Session session) {
LOGGER.debug("会话过期:" + session.getId());
} }
会话工厂:
package com.zheng.upms.client.shiro.session; import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.SessionContext;
import org.apache.shiro.session.mgt.SessionFactory;
import org.apache.shiro.web.session.mgt.WebSessionContext; import javax.servlet.http.HttpServletRequest; /**
* session工厂
* Created by shuzheng on 2017/2/27.
*/
public class UpmsSessionFactory implements SessionFactory { @Override
public Session createSession(SessionContext sessionContext) {
UpmsSession session = new UpmsSession();
if (null != sessionContext && sessionContext instanceof WebSessionContext) {
WebSessionContext webSessionContext = (WebSessionContext) sessionContext;
HttpServletRequest request = (HttpServletRequest) webSessionContext.getServletRequest();
if (null != request) {
session.setHost(request.getRemoteAddr());
session.setUserAgent(request.getHeader("User-Agent"));
}
}
return session;
} }
二、日志记录切面
springMVC-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
通过配置织入@Aspectj切面
<aop:aspectj-autoproxy proxy-target-class="true"/> <!-- 日志记录AOP实现 -->
<bean class="com.zheng.upms.client.interceptor.LogAspect"/> <!-- 日志记录AOP实现 -->
<bean class="com.zheng.common.aspect.RpcLogAspect"/> </beans>
LogAspect实现:
package com.zheng.upms.client.interceptor; import com.alibaba.fastjson.JSON;
import com.zheng.common.util.RequestUtil;
import com.zheng.upms.dao.model.UpmsLog;
import com.zheng.upms.rpc.api.UpmsApiService;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang.ObjectUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method; /**
* 日志记录AOP实现
* Created by ZhangShuzheng on 2017/3/14.
*/
@Aspect
public class LogAspect { private static final Logger LOGGER = LoggerFactory.getLogger(LogAspect.class); // 开始时间
private long startTime = 0L;
// 结束时间
private long endTime = 0L; @Autowired
UpmsApiService upmsApiService; @Before("execution(* *..controller..*.*(..))")
public void doBeforeInServiceLayer(JoinPoint joinPoint) {
LOGGER.debug("doBeforeInServiceLayer");
startTime = System.currentTimeMillis();
} @After("execution(* *..controller..*.*(..))")
public void doAfterInServiceLayer(JoinPoint joinPoint) {
LOGGER.debug("doAfterInServiceLayer");
} @Around("execution(* *..controller..*.*(..))")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
// 获取request
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest request = servletRequestAttributes.getRequest(); UpmsLog upmsLog = new UpmsLog();
// 从注解中获取操作名称、获取响应结果
Object result = pjp.proceed();
Signature signature = pjp.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method.isAnnotationPresent(ApiOperation.class)) {
ApiOperation log = method.getAnnotation(ApiOperation.class);
upmsLog.setDescription(log.value());
}
if (method.isAnnotationPresent(RequiresPermissions.class)) {
RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class);
String[] permissions = requiresPermissions.value();
if (permissions.length > ) {
upmsLog.setPermissions(permissions[]);
}
}
endTime = System.currentTimeMillis();
LOGGER.debug("doAround>>>result={},耗时:{}", result, endTime - startTime); upmsLog.setBasePath(RequestUtil.getBasePath(request));
upmsLog.setIp(RequestUtil.getIpAddr(request));
upmsLog.setMethod(request.getMethod());
if ("GET".equalsIgnoreCase(request.getMethod())) {
upmsLog.setParameter(request.getQueryString());
} else {
upmsLog.setParameter(ObjectUtils.toString(request.getParameterMap()));
}
upmsLog.setResult(JSON.toJSONString(result));
upmsLog.setSpendTime((int) (endTime - startTime));
upmsLog.setStartTime(startTime);
upmsLog.setUri(request.getRequestURI());
upmsLog.setUrl(ObjectUtils.toString(request.getRequestURL()));
upmsLog.setUserAgent(request.getHeader("User-Agent"));
upmsLog.setUsername(ObjectUtils.toString(request.getUserPrincipal()));
upmsApiService.insertUpmsLogSelective(upmsLog);
return result;
} }
rpc消费日志记录:
package com.zheng.common.aspect; import com.alibaba.dubbo.rpc.RpcContext;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* rpc提供者和消费者日志打印
* Created by ZhangShuzheng on 2017/4/19.
*/
public class RpcLogAspect { private static final Logger LOGGER = LoggerFactory.getLogger(RpcLogAspect.class); // 开始时间
private long startTime = 0L;
// 结束时间
private long endTime = 0L; @Before("execution(* *..rpc..*.*(..))")
public void doBeforeInServiceLayer(JoinPoint joinPoint) {
LOGGER.debug("doBeforeInServiceLayer");
startTime = System.currentTimeMillis();
} @After("execution(* *..rpc..*.*(..))")
public void doAfterInServiceLayer(JoinPoint joinPoint) {
LOGGER.debug("doAfterInServiceLayer");
} @Around("execution(* *..rpc..*.*(..))")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
Object result = pjp.proceed();
// 是否是消费端
boolean consumerSide = RpcContext.getContext().isConsumerSide();
// 获取最后一次提供方或调用方IP
String ip = RpcContext.getContext().getRemoteHost();
// 服务url
String rpcUrl = RpcContext.getContext().getUrl().getParameter("application");
LOGGER.info("consumerSide={}, ip={}, url={}", consumerSide, ip, rpcUrl);
return result;
} }
二、session管理页面
<%@ page contentType="text/html; charset=utf-8"%>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<c:set var="basePath" value="${pageContext.request.contextPath}"/>
<!DOCTYPE HTML>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>会话管理</title>
<jsp:include page="/resources/inc/head.jsp" flush="true"/>
</head>
<body>
<div id="main">
<div id="toolbar">
<shiro:hasPermission name="upms:session:forceout"><a class="waves-effect waves-button" href="javascript:;" onclick="forceoutAction()"><i class="zmdi zmdi-run"></i> 强制退出</a></shiro:hasPermission>
</div>
<table id="table"></table>
</div>
<jsp:include page="/resources/inc/footer.jsp" flush="true"/>
<script>
var $table = $('#table');
$(function() {
// bootstrap table初始化
$table.bootstrapTable({
//向服务器请求的url。
url: '${basePath}/manage/session/list',
//表格的高度
height: getHeight(),
//默认false,当设为true,则每行表格的背景会显示灰白相间
striped: true,
//默认false不显示表格右上方搜索框 ,可设为true,在搜索框内只要输入内容即开始搜索
search: false,
//默认为false隐藏刷新按钮,设为true显示
showRefresh: true,
//默认为false隐藏某列下拉菜单,设为true显示
showColumns: true,
//每列的下拉菜单最小数
minimumCountColumns: ,
//默认false不响应,设为true则当点击此行的某处时,会自动选中此行的checkbox(复选框)或radiobox(单选按钮)
clickToSelect: true,
//默认false,设为true显示detail view(细节视图)
detailView: true,
//前提:detailView设为true,启用了显示detail view。
// 用于格式化细节视图
// 返回一个字符串,通过第三个参数element直接添加到细节视图的cell(某一格)中,其中,element为目标cell的jQuery element
detailFormatter: 'detailFormatter',
//默认为false,表格的底部工具栏不会显示分页条(pagination toolbar),可以设为true来显示
pagination: true,
//默认true,分页条无限循环
paginationLoop: false,
//设置在哪进行分页,默认”client”,可选”server”,如果设置 “server”,则必须设置url或者重写ajax方法
sidePagination: 'server',
//设置为 false 将在点击分页按钮时,自动记住排序项。仅在 sidePagination设置为 server时生效。
silentSort: false,
//设置为 true 是程序自动判断显示分页信息和 card 视图。
smartDisplay: false,
//转义HTML字符串,替换 &, <, >, ", \`, 和 ' 字符。
escape: true,
//设置为 true时,按回车触发搜索方法,否则自动触发搜索方法。
searchOnEnterKey: true,
//指定主键列。
idField: 'id',
//设置为 true 在点击分页按钮或搜索按钮时,将记住checkbox的选择项。\
maintainSelected: true,
//一个jQuery 选择器,指明自定义的 toolbar。例如:#toolbar, .toolbar.
toolbar: '#toolbar',
//列配置项,详情请查看 列参数 表格.
columns: [
{field: 'ck', checkbox: true},
{field: 'id', title: '编号', sortable: true, align: 'center'},
{field: 'startTimestamp', title: '创建时间', sortable: true, align: 'center'},
{field: 'lastAccessTime', title: '最后访问时间'},
{field: 'expired', title: '是否过期', align: 'center'},
{field: 'host', title: '访问者IP', align: 'center'},
{field: 'userAgent', title: '用户标识', align: 'center'},
{field: 'status', title: '状态', align: 'center', formatter: 'statusFormatter'}
]
});
});
// 格式化状态
function statusFormatter(value, row, index) {
if (value == 'on_line') {
return '<span class="label label-success">在线</span>';
}
if (value == 'off_line') {
return '<span class="label label-default">离线</span>';
}
if (value == 'force_logout') {
return '<span class="label label-danger">踢离</span>';
}
}
// 强制退出
var forceoutDialog;
function forceoutAction() {
//getSelections方法返回所选的行,当没有选择任何行的时候返回一个空数组。
var rows = $table.bootstrapTable('getSelections');
if (rows.length == ) {
$.confirm({
title: false,
content: '请至少选择一条记录!',
autoClose: 'cancel|3000',
backgroundDismiss: true,
buttons: {
cancel: {
text: '取消',
btnClass: 'waves-effect waves-button'
}
}
});
} else {
forceoutDialog = $.confirm({
type: 'red',
animationSpeed: ,
title: false,
content: '确认强制退出该会话吗?',
buttons: {
confirm: {
text: '确认',
btnClass: 'waves-effect waves-button',
action: function () {
var ids = new Array();
for (var i in rows) {
ids.push(rows[i].id);
}
$.ajax({
type: 'get',
url: '${basePath}/manage/session/forceout/' + ids.join(","),
success: function(result) {
if (result.code != ) {
if (result.data instanceof Array) {
$.each(result.data, function(index, value) {
$.confirm({
theme: 'dark',
animation: 'rotateX',
closeAnimation: 'rotateX',
title: false,
content: value.errorMsg,
buttons: {
confirm: {
text: '确认',
btnClass: 'waves-effect waves-button waves-light'
}
}
});
});
} else {
$.confirm({
theme: 'dark',
animation: 'rotateX',
closeAnimation: 'rotateX',
title: false,
content: result.data.errorMsg,
buttons: {
confirm: {
text: '确认',
btnClass: 'waves-effect waves-button waves-light'
}
}
});
}
} else {
forceoutDialog.close();
$table.bootstrapTable('refresh');
}
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
$.confirm({
theme: 'dark',
animation: 'rotateX',
closeAnimation: 'rotateX',
title: false,
content: textStatus,
buttons: {
confirm: {
text: '确认',
btnClass: 'waves-effect waves-button waves-light'
}
}
});
}
});
}
},
cancel: {
text: '取消',
btnClass: 'waves-effect waves-button'
}
}
});
}
}
</script>
</body>
</html>
common.js
$(function() {
// Waves初始化
Waves.displayEffect();
// 数据表格动态高度
$(window).resize(function () {
$('#table').bootstrapTable('resetView', {
height: getHeight()
});
});
// 设置input特效
$(document).on('focus', 'input[type="text"]', function() {
$(this).parent().find('label').addClass('active');
}).on('blur', 'input[type="text"]', function() {
if ($(this).val() == '') {
$(this).parent().find('label').removeClass('active');
}
});
// select2初始化
$('select').select2();
});
// 动态高度
function getHeight() {
return $(window).height() - ;
}
// 数据表格展开内容
function detailFormatter(index, row) {
var html = [];
$.each(row, function (key, value) {
html.push('<p><b>' + key + ':</b> ' + value + '</p>');
});
return html.join('');
}
// 初始化input特效
function initMaterialInput() {
$('form input[type="text"]').each(function () {
if ($(this).val() != '') {
$(this).parent().find('label').addClass('active');
}
});
}