前言:
前段时间在搭建公司游戏框架安全验证的时候,就想到之前web最火的shiro框架,虽然后面实践发现在netty中不太适用,最后自己模仿shiro写了一个缩减版的,但是中间花费两天时间弄出来的shiro可不能白费,这里给大家出个简单的教程说明吧。

shiro的基本介绍这里就不再说了,可以自行翻阅博主之前写的shiro教程,这篇文章主要说明分布式架构下shiro的session共享问题。

一、原理描述

无论分布式、还是集群下,项目都需要获取登录用户的信息,而不可能做的就是让客户在每个系统或者每个模块中反复登录,也不存在让客户端存载用户信息给服务端,这是很常识的问题

而单机模式下,我们用shiro做了登录验证,他的主要方式就是在第一次登陆的时候,把我们设置的用户信息保存在cache(内存)中和自带的ehcahe(缓存管理器)中,然后给客户端一个cookie,在每次客户端访问时获取cookie值,从而得到用户信息。

好了,那么逻辑就清楚了,分布式架构下,要与多系统共享用户信息,其实就是共享shiro保存的cache。

要在多项目中共享,内存是不可能的了,ehcache对分布式支持不太好,或者说根本不支持。那么剩下只能是我么熟悉的mysql,redis,mongdb啥的数据库了。这么一对比,不用我说大家也明白了,最适合的无疑是redis了,速度快,主从啥的。

二、流程描述

查看源码我们可以知道,cacheManager最终会被set到sessionDAO中,所以我们要自己写sessionDAO。有两个类去操作保存的,那么我们只需要重写,实现这两个类,然后在注册的时候声明即可。

1.shiroCache:cache类,可以自己写一个定时消除的MAP存放更好,文章结尾我会给出map的代码。而这里的代码我是放在redis的。

package com.result.shiro.distributed;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import com.result.redis.RedisKey;
import com.result.redis.RedisUtil;
import com.result.tools.KyroUtil;
/**
* @author 作者 huangxinyu
* @version 创建时间:2018年1月8日 下午9:33:23
* cache共享
*/
@SuppressWarnings("unchecked")
public class ShiroCache<K, V> implements Cache<K, V> {
private static final String REDIS_SHIRO_CACHE = RedisKey.CACHEKEY;
private String cacheKey;
private long globExpire = 30;
@SuppressWarnings("rawtypes")
public ShiroCache(String name) {
this.cacheKey = REDIS_SHIRO_CACHE + name + ":";
}
@Override
public V get(K key) throws CacheException {
Object obj = RedisUtil.get(KyroUtil.serialization(getCacheKey(key)));
if(obj==null){
return null;
}
return (V) KyroUtil.deserialization((String)obj);
}
@Override
public V put(K key, V value) throws CacheException {
V old = get(key);
RedisUtil.setex(KyroUtil.serialization(getCacheKey(key)), 18000, KyroUtil.serialization(value));
return old;
}
@Override
public V remove(K key) throws CacheException {
V old = get(key);
RedisUtil.del(KyroUtil.serialization(getCacheKey(key)));
return old;
}
@Override
public void clear() throws CacheException {
for(String key : (Set<String>)keys()){
RedisUtil.del(key);
}
}
@Override
public int size() {
return keys().size();
}
@Override
public Set<K> keys() {
return (Set<K>) RedisUtil.keys(KyroUtil.serialization(getCacheKey("*")));
}
@Override
public Collection<V> values() {
Set<K> set = keys();
List<V> list = new ArrayList<>();
for (K s : set) {
list.add(get(s));
}
return list;
}
private K getCacheKey(Object k) {
return (K) (this.cacheKey + k);
}
}

2.session操作类:这里用来把用户信息存放在redis中共享的。

package com.result.shiro.distributed;
/**
* @author 作者 huangxinyu
* @version 创建时间:2018年1月6日 上午10:12:42
* redis实现共享session
*/
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.result.redis.RedisKey;
import com.result.redis.RedisUtil;
import com.result.tools.KyroUtil;
import com.result.tools.SerializationUtil;
public class RedisSessionDao extends EnterpriseCacheSessionDAO {
private static Logger logger = LoggerFactory.getLogger(RedisSessionDao.class);
@Override
public void update(Session session) throws UnknownSessionException {
this.saveSession(session);
}
/**
* 删除session
*/
@Override
public void delete(Session session) {
if (session == null || session.getId() == null) {
logger.error("==========session或sessionI 不存在");
return;
}
RedisUtil.del(KyroUtil.serialization(RedisKey.SESSIONKEY + session.getId()));
}
/**
* 获取存活的sessions
*/
@Override
public Collection<Session> getActiveSessions() {
Set<Session> sessions = new HashSet<>();
Set<String> keys = RedisUtil.keys(KyroUtil.serialization(RedisKey.SESSIONKEY + "*"));
for(String key:keys){
sessions.add((Session)KyroUtil.deserialization((String)RedisUtil.get(key)));
}
return sessions;
}
/**
* 创建session
*/
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = this.generateSessionId(session);
this.assignSessionId(session, sessionId);
this.saveSession(session);
return sessionId;
}
/**
* 获取session
*/
@Override
protected Session doReadSession(Serializable sessionId) {
if(sessionId == null){
logger.error("==========session id 不存在");
return null;
}
Object obj = RedisUtil.get(KyroUtil.serialization(RedisKey.SESSIONKEY + sessionId));
if(obj==null){
return null;
}
Session s = (Session)KyroUtil.deserialization((String)obj);
return s;
}
/**
* 保存session并存储过期时间
* @param session
* @throws UnknownSessionException
*/
public static void saveSession(String sessionId,Object obj) throws UnknownSessionException{
if (obj == null) {
logger.error("要存入的session为空");
return;
}
//设置过期时间
int expireTime = 1800;
RedisUtil.setex(sessionId,expireTime,SerializationUtil.serializeToString(obj));
}
}
然后还有一个类也是必要的 package com.result.shiro.distributed;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
/**
* @author 作者 huangxinyu
* @version 创建时间:2018年1月8日 下午9:32:41
* 类说明
*/
public class RedisCacheManager implements CacheManager {
@Override
public <K, V> Cache<K, V> getCache(String name) throws CacheException {
return new ShiroCache<K, V>(name);
}
}

三:辅助类说明

用户信息的session存放在redis中肯定是需要序列化的,然而用json这种可读性太强的东西安全性显得极低,而且长度太大,浪费存储空间和IO。所以需要找其他的序列化工具。

常规的好用的序列化工具有kyro,protobuff,这些是性能极高而且序列化之后长度极小的序列化工具,其中protobuf支持跨语言。不过这些在之后的文章再和大家介绍去了,因为~!!session不支持这两种操作(因为上面两个类中操作的session实际是一个接口)。

那么序列化用的什么,emmmm~一个很原生的东西,测试效率也挺高的,和protobuf差不太多。下面贴出的代码实际就是上面类中kyroUtils中的方法,因为shiro分布式在项目中被废掉了,我也没去改名字了。大家自己看仔细点就可以了。

被注释掉的代码是kyro的序列化工具。

package com.result.tools;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author 作者 huangxinyu
* @version 创建时间:2018年1月6日 下午2:22:14
* Kryo工具类
*/
public class KyroUtil {
private static Logger logger = LoggerFactory.getLogger(KyroUtil.class);
//private static KryoPool pool;
//原本打算使用kyro序列化session,后来发现kyro对session序列化不支持,反序列后得不到value。 这种out序列化测试性能消耗时间更短,但是长度变大4倍意思,待优化
// static{
// KryoFactory factory = new KryoFactory() {
// public Kryo create() {
// Kryo kryo = new Kryo();
// kryo.setReferences(false);
// //把shiroSession的结构注册到Kryo注册器里面,提高序列化/反序列化效率
// kryo.register(Session.class, new JavaSerializer());
// kryo.register(String.class, new JavaSerializer());
// kryo.register(User.class, new JavaSerializer());
// kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
// return kryo;
// }
// };
// pool = new KryoPool.Builder(factory).build();
// logger.info("KryoPool初始化成功====================================");
// }
/**
* 对象编码
* @param value
* @return
*/
public static String serialization(Object value) {
// String str ="";
// try {
// Kryo kryo = pool.borrow();
// ByteArrayOutputStream baos = new ByteArrayOutputStream();
// Output output = new Output(baos);
// kryo.writeClassAndObject(output, value);
// output.flush();
// output.close();
// byte[] b = baos.toByteArray();
// baos.flush();
// baos.close();
// str = new String(b, "ISO8859-1");
// } catch (IOException e) {
// e.printStackTrace();
// }
// return str;
//
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
try {
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(value);
return new String(bos.toByteArray(), "ISO8859-1");
} catch (Exception e) {
throw new RuntimeException("serialize session error", e);
} finally {
try {
oos.close();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// return new String(new Base64().encode(b));
}
/**
* 对象解码
* @param <T>
* @param <T>
* @param obj
* @param clazz
* @return
*/
public static Object deserialization(String obj) {
// try {
// Kryo kryo = pool.borrow();
// ByteArrayInputStream bais;
// bais = new ByteArrayInputStream(obj.getBytes("ISO8859-1"));
// //new Base64().decode(obj));
// Input input = new Input(bais);
// return kryo.readClassAndObject(input);
// } catch (UnsupportedEncodingException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// return null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
bis = new ByteArrayInputStream(obj.getBytes("ISO8859-1"));
ois = new ObjectInputStream(bis);
return ois.readObject();
} catch (Exception e) {
throw new RuntimeException("deserialize session error", e);
} finally {
try {
ois.close();
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

四、注册

好了,该重写的都重写了,那么最后一步就是整合spring的时候我们要告诉spring,我们要用的是我们重写过的sessiondao了。

我这里用的是代码的方式,因为某些原因在写框架的时候不太好用xml去整合。

反正原理都差不多,大家看看就明白了:

package com.business.shiro;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import com.result.shiro.distributed.RedisCacheManager;
import com.result.shiro.distributed.RedisSessionDao;
/**
* @author 作者 huangxinyu
* @version 创建时间:2018年1月8日 下午8:29:12
* 类说明
*/
@Configuration
public class ShiroConfiguration {
private static Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
@Bean(name = "cacheShiroManager")
public CacheManager getCacheManage() {
return new RedisCacheManager();
}
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean(name = "sessionValidationScheduler")
public ExecutorServiceSessionValidationScheduler getExecutorServiceSessionValidationScheduler() {
ExecutorServiceSessionValidationScheduler scheduler = new ExecutorServiceSessionValidationScheduler();
scheduler.setInterval(900000);
return scheduler;
}
@Bean(name = "hashedCredentialsMatcher")
public HashedCredentialsMatcher getHashedCredentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("MD5");
credentialsMatcher.setHashIterations(1);
credentialsMatcher.setStoredCredentialsHexEncoded(true);
return credentialsMatcher;
}
@Bean(name = "sessionIdCookie")
public SimpleCookie getSessionIdCookie() {
SimpleCookie cookie = new SimpleCookie("sid");
cookie.setHttpOnly(true);
cookie.setMaxAge(-1);
return cookie;
}
@Bean(name = "rememberMeCookie")
public SimpleCookie getRememberMeCookie() {
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
simpleCookie.setHttpOnly(true);
simpleCookie.setMaxAge(2592000);
return simpleCookie;
}
@Bean
public CookieRememberMeManager getRememberManager(){
CookieRememberMeManager meManager = new CookieRememberMeManager();
meManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
meManager.setCookie(getRememberMeCookie());
return meManager;
}
@Bean(name = "sessionManager")
public DefaultWebSessionManager getSessionManage() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(1800000);
sessionManager.setSessionValidationScheduler(getExecutorServiceSessionValidationScheduler());
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionIdCookieEnabled(true);
sessionManager.setSessionIdCookie(getSessionIdCookie());
RedisSessionDao cacheSessionDAO = new RedisSessionDao();
cacheSessionDAO.setCacheManager(getCacheManage());
sessionManager.setSessionDAO(cacheSessionDAO);
// -----可以添加session 创建、删除的监听器
return sessionManager;
}
@Bean(name = "myRealm")
public AuthorizingRealm getShiroRealm() {
MyRealm realm = new MyRealm();
// realm.setName("shiro_auth_cache");
// realm.setAuthenticationCache(getCacheManage().getCache(realm.getName()));
// realm.setAuthenticationTokenClass(UserAuthenticationToken.class);
return realm;
}
@Bean(name = "securityManager")
public DefaultWebSecurityManager getSecurityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setCacheManager(getCacheManage());
securityManager.setSessionManager(getSessionManage());
securityManager.setRememberMeManager(getRememberManager());
securityManager.setRealm(getShiroRealm());
return securityManager;
}
@Bean
public MethodInvokingFactoryBean getMethodInvokingFactoryBean(){
MethodInvokingFactoryBean factoryBean = new MethodInvokingFactoryBean();
factoryBean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
factoryBean.setArguments(new Object[]{getSecurityManager()});
return factoryBean;
}
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(){
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(getSecurityManager());
return advisor;
}
/**
* @return
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(getSecurityManager());
factoryBean.setLoginUrl("/toLogin");
factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return factoryBean;
}
}

优化:伪定时消除map,最好配合quartz清楚,不然内存中MAP如果不访问就不消除,容易累计。

package com.result.security;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import com.result.NettyGoConstant;
/**
* @author 作者 huangxinyu
* @version 创建时间:2018年1月29日 上午10:31:50 类说明
*/
public class ExpiryMap<K, V> extends HashMap<K, V> {
private static final long serialVersionUID = 1L;
/**
* default expiry time 2m
*/
private long EXPIRY = NettyGoConstant.LOGINSESSIONTIMEOUT;
private HashMap<K, Long> expiryMap = new HashMap<>();
public ExpiryMap() {
super();
}
public ExpiryMap(long defaultExpiryTime) {
this(1 << 4, defaultExpiryTime);
}
public ExpiryMap(int initialCapacity, long defaultExpiryTime) {
super(initialCapacity);
this.EXPIRY = defaultExpiryTime;
}
public V put(K key, V value) {
expiryMap.put(key, System.currentTimeMillis() + EXPIRY);
return super.put(key, value);
}
public boolean containsKey(Object key) {
return !checkExpiry(key, true) && super.containsKey(key);
}
/**
* @param key
* @param value
* @param expiryTime
* 键值对有效期 毫秒
* @return
*/
public V put(K key, V value, long expiryTime) {
expiryMap.put(key, System.currentTimeMillis() + expiryTime);
return super.put(key, value);
}
public int size() {
return entrySet().size();
}
public boolean isEmpty() {
return entrySet().size() == 0;
}
public boolean containsValue(Object value) {
if (value == null)
return Boolean.FALSE;
Set<java.util.Map.Entry<K, V>> set = super.entrySet();
Iterator<java.util.Map.Entry<K, V>> iterator = set.iterator();
while (iterator.hasNext()) {
java.util.Map.Entry<K, V> entry = iterator.next();
if (value.equals(entry.getValue())) {
if (checkExpiry(entry.getKey(), false)) {
iterator.remove();
return Boolean.FALSE;
} else
return Boolean.TRUE;
}
}
return Boolean.FALSE;
}
public Collection<V> values() {
Collection<V> values = super.values();
if (values == null || values.size() < 1)
return values;
Iterator<V> iterator = values.iterator();
while (iterator.hasNext()) {
V next = iterator.next();
if (!containsValue(next))
iterator.remove();
}
return values;
}
public V get(Object key) {
if (key == null)
return null;
if (checkExpiry(key, true))
return null;
return super.get(key);
}
/**
*
* @Description: 是否过期
* @param key
* @return null:不存在或key为null -1:过期 存在且没过期返回value 因为过期的不是实时删除,所以稍微有点作用
*/
public Object isInvalid(Object key) {
if (key == null)
return null;
if (!expiryMap.containsKey(key)) {
return null;
}
long expiryTime = expiryMap.get(key);
boolean flag = System.currentTimeMillis() > expiryTime;
if (flag) {
super.remove(key);
expiryMap.remove(key);
return -1;
}
return super.get(key);
}
public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
expiryMap.put(e.getKey(), System.currentTimeMillis() + EXPIRY);
super.putAll(m);
}
public Set<Map.Entry<K, V>> entrySet() {
Set<java.util.Map.Entry<K, V>> set = super.entrySet();
Iterator<java.util.Map.Entry<K, V>> iterator = set.iterator();
while (iterator.hasNext()) {
java.util.Map.Entry<K, V> entry = iterator.next();
if (checkExpiry(entry.getKey(), false))
iterator.remove();
}
return set;
}
/**
*
* @Description: 是否过期
* @author: qd-ankang
* @date: 2016-11-24 下午4:05:02
* @param expiryTime
* true 过期
* @param isRemoveSuper
* true super删除
* @return
*/
private boolean checkExpiry(Object key, boolean isRemoveSuper) {
if (!expiryMap.containsKey(key)) {
return Boolean.FALSE;
}
long expiryTime = expiryMap.get(key);
boolean flag = System.currentTimeMillis() > expiryTime;
if (flag) {
if (isRemoveSuper)
super.remove(key);
expiryMap.remove(key);
}
return flag;
}
/**
* 删除
* @param key
*/
public void del(Object key){
super.remove(key);
expiryMap.remove(key);
}
public static void main(String[] args) throws InterruptedException {
ExpiryMap<String, String> map = new ExpiryMap<>(10);
map.put("test", "ankang");
map.put("test1", "ankang");
map.put("test2", "ankang", 3000);
System.out.println("test1" + map.get("test"));
Thread.sleep(1000);
System.out.println("isInvalid:" + map.isInvalid("test"));
System.out.println("size:" + map.size());
System.out.println("size:" + ((HashMap<String, String>) map).size());
for (Map.Entry<String, String> m : map.entrySet()) {
System.out.println("isInvalid:" + map.isInvalid(m.getKey()));
map.containsKey(m.getKey());
System.out.println("key:" + m.getKey() + " value:" + m.getValue());
}
System.out.println("test1" + map.get("test"));
}
/**
* 是否超过过期的一半时间
* @param key
* @return
*/
public boolean isHalfExpiryTime(Object key ){
if (!expiryMap.containsKey(key)) {
return false;
}
long expiryTime = expiryMap.get(key);
boolean flag = System.currentTimeMillis()-(expiryTime-NettyGoConstant.LOGINSESSIONTIMEOUT)>=NettyGoConstant.LOGINSESSIONTIMEOUT/2;
return flag;
}
}

shiro权限控制(二):分布式架构中shiro的实现的更多相关文章

  1. 分布式架构中shiro

    分布式架构中shiro 前言:前段时间在搭建公司游戏框架安全验证的时候,就想到之前web最火的shiro框架,虽然后面实践发现在netty中不太适用,最后自己模仿shiro写了一个缩减版的,但是中间花 ...

  2. shiro权限控制(一):shiro介绍以及整合SSM框架

    shiro安全框架是目前为止作为登录注册最常用的框架,因为它十分的强大简单,提供了认证.授权.加密和会话管理等功能 . shiro能做什么? 认证:验证用户的身份 授权:对用户执行访问控制:判断用户是 ...

  3. 项目一:第十二天 1、常见权限控制方式 2、基于shiro提供url拦截方式验证权限 3、在realm中授权 5、总结验证权限方式(四种) 6、用户注销7、基于treegrid实现菜单展示

    1 课程计划 1. 常见权限控制方式 2. 基于shiro提供url拦截方式验证权限 3. 在realm中授权 4. 基于shiro提供注解方式验证权限 5. 总结验证权限方式(四种) 6. 用户注销 ...

  4. Shiro权限管理框架(三):Shiro中权限过滤器的初始化流程和实现原理

    本篇是Shiro系列第三篇,Shiro中的过滤器初始化流程和实现原理.Shiro基于URL的权限控制是通过Filter实现的,本篇从我们注入的ShiroFilterFactoryBean开始入手,翻看 ...

  5. shiro权限控制入门

    一:权限控制两种主要方式 粗粒度 URL 级别权限控制和细粒度方法级别权限控制 1.粗粒度 URL 级别权限控制 可以基于 Filter 实现在数据库中存放 用户.权限.访问 URL 对应关系, 当前 ...

  6. Shiro权限管理框架(一):Shiro的基本使用

    首发地址:https://www.guitu18.com/post/2019/07/26/43.html 核心概念 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码 ...

  7. Zookeeper在分布式架构中的应用

    Zookeeper 是一个高性能.高可靠的分布式协调系统,是 Google Chubby 的一个开源实现.Zookeeper 能够为分布式应用提供一致性服务,提供的功能包括:配置维护.域名服务.分布式 ...

  8. Spring boot后台搭建二集成Shiro权限控制

    上一篇文章,实现了用户验证 查看,接下来实现下权限控制 权限控制,是管理资源访问的过程,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等 Apache Shir ...

  9. shiro权限控制的简单实现

    权限控制常用的有shiro.spring security,两者相比较,各有优缺点,此篇文章以shiro为例,实现系统的权限控制. 一.数据库的设计 简单的五张表,用户.角色.权限及关联表: CREA ...

随机推荐

  1. 【转】.NET IL实现对象深拷贝

    对于深拷贝,通常的方法是将对象进行序列化,然后再反序化成为另一个对象.例如在stackoverflow上有这样的解决办法:https://stackoverflow.com/questions/785 ...

  2. [bzoj1227] [SDOI2009]虔诚的墓主人

    终于填上了这个万年巨坑....从初二的时候就听说过这题...然后一直不敢写QAQ 现在感觉也不是很烦(然而我还是写麻烦了 离散化一波,预处理出组合数什么的.. 要维护对于当前行,每列上方和下方节点凑出 ...

  3. UVA 572 dfs求连通块

    The GeoSurvComp geologic survey company is responsible for detecting underground oil deposits. GeoSu ...

  4. java初学

    1.Scanner类 1)使用 a.导入Scanner类 improt java.util.Scanner; b.创建Scanner对象 Scanner input = new Scanner(Sys ...

  5. centos6+cdh5.4.0 离线搭建cdh搭建

    p.MsoNormal { margin: 0pt; margin-bottom: .0001pt; text-align: justify; font-family: Calibri; font-s ...

  6. 四 : springMVC各种跳页面传值

    第一种方式 : 返回值为String类型的跳转页面,犯法参数里面需要写Model modelimport org.springframework.ui.Model;包下的.返回String1):字符串 ...

  7. Java入门篇(五)——Java的字符串/String类

    前面在举例时有出现过String的例子,当时肯定有一部分朋友不知道这个是做什么用的.其实String类是Java中一个比较特殊的类,字符串即String类,它不是Java的基本数据类型之一,但可以像基 ...

  8. nginx服务器配置/websocket nginx 配置笔记

    server { listen 80; server_name xxx.com; # add_header '*' ; location /u/ { # 反向代理透传客户端ip proxy_set_h ...

  9. vue-cli的webpack模版项目配置解析-build/dev-server.js

    我们在使用vue-cli搭建vuejs项目(Vuejs实例-01使用vue-cli脚手架搭建Vue.js项目)的时候,会自动生成一系列文件,其中就包含webpack配置文件.我们现在来看下,这些配置到 ...

  10. DESTOON B2B标签(tag)调用手册

    路径:include/tag.func.php 1.标签格式的大致说明 {tag("moduleid=9&table=article_9&length=40&cond ...