【zheng阅读系列】shiro权限管理
一、配置文件
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');
}
});
}
【zheng阅读系列】shiro权限管理的更多相关文章
- (39.4) Spring Boot Shiro权限管理【从零开始学Spring Boot】
在读此文章之前您还可能需要先了解: (39.1) Spring Boot Shiro权限管理[从零开始学Spring Boot] http://412887952-qq-com.iteye.com/b ...
- (39.3) Spring Boot Shiro权限管理【从零开始学Spring Boot】
在学习此小节之前您可能还需要学习: (39.1) Spring Boot Shiro权限管理[从零开始学Spring Boot] http://412887952-qq-com.iteye.com/b ...
- (39.2). Spring Boot Shiro权限管理【从零开始学Spring Boot】
(本节提供源代码,在最下面可以下载) (4). 集成Shiro 进行用户授权 在看此小节前,您可能需要先看: http://412887952-qq-com.iteye.com/blog/229973 ...
- (39.1) Spring Boot Shiro权限管理【从零开始学Spring Boot】
(本节提供源代码,在最下面可以下载)距上一个章节过了二个星期了,最近时间也是比较紧,一直没有时间可以写博客,今天难得有点时间,就说说Spring Boot如何集成Shiro吧.这个章节会比较复杂,牵涉 ...
- Spring Boot Shiro 权限管理
Spring Boot Shiro 权限管理 标签: springshiro 2016-01-14 23:44 94587人阅读 评论(60) 收藏 举报 .embody{ padding:10px ...
- Spring Boot Shiro 权限管理 【转】
http://blog.csdn.net/catoop/article/details/50520958 主要用于备忘 本来是打算接着写关于数据库方面,集成MyBatis的,刚好赶上朋友问到Shiro ...
- shiro权限管理的框架-入门
shiro权限管理的框架 1.权限管理的概念 基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能 ...
- (补漏)Springboot2.0 集成shiro权限管理
原文Springboot2.0 集成shiro权限管理 一.关于停止使用外键. 原本集成shiro建立用户.角色.权限表的时候使用了外键,系统自动创建其中两个关联表,用@JoinTable.看起来省事 ...
- SpringMVC+Shiro权限管理(转载)
源码 http://pan.baidu.com/s/1pJzG4t1 SpringMVC+Shiro权限管理 博文目录 权限的简单描述 实例表结构及内容及POJO Shiro-pom.xml Shir ...
随机推荐
- Java知识回顾 (4)Java包装类
一. Java Number 一般地,当需要使用数字的时候,我们通常使用内置数据类型,如:byte.int.long.double 等. 然而,在实际开发过程中,我们经常会遇到需要使用对象,而不是内置 ...
- vcs+Makefile实现简单的testbench
网络上找的文章,实现了一遍. 步骤如下: 1. 创建verilog代码, 包括8位加法器代码和testbench代码. adder8.v module adder8 ( input clk, inpu ...
- 微软BI 之SSIS 系列 - 在 SSIS 中将指定目录下的所有文件分类输出到不同文件夹
开篇介绍 比如有这样的一个需求,旧的一个业务系统通常将产出的文件输出到同一个指定的目录下的不同子目录,输出的文件类型有 XML,EXCEL, TXT 这些不同后缀的文件.现在需要在 SSIS 中将它们 ...
- spring事务心得积累
一.使用事务 1.spring配置文件里开启事务注解机制 <!-- 开启事务注解机制 --> <bean id="transactionManager" clas ...
- SqlDateTime overflow / SqlDateTime 溢出
Error - SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM SqlDat ...
- BigDecimal四舍五入保留两位小数
import java.math.BigDecimal; import java.text.DecimalFormat; import java.text.NumberFormat; public c ...
- 2.Swift快速浏览
传统认为,在一个新的语言的第一个程序要在屏幕上显示“Hello world!”.在Swift,可以用一行代码来完成: println("Hello, world!") 如果你已经在 ...
- [k8s]jenkins配合kubernetes插件实现k8s集群构建的持续集成
另一个结合harbor自动构建镜像的思路: 即code+baseimage一体的方案 - 程序员将代码提交到代码仓库gitlab - 钩子触发jenkins master启动一次构建 - jenkin ...
- [svc]sed&awk过滤行及sed常用例子
- sed过滤行 sed '2p' sed '2,5p' sed '2p;3p;4p' - awk过滤行 awk 'NR==2' awk 'NR>=2 && NR <=3' ...
- 【Socket】关于socket长连接的心跳包
TCP的socket本身就是长连接的,那么为什么还要心跳包呢? 在smack里有个30s发送一个空消息的线程,同样关于心跳包(keepalive) 据网络搜索到的资料解释如下 内网机器如果不主动向外发 ...