SpringMVC整合Apache Shiro
关于什么是Shiro,可以查看这篇文章http://www.cnblogs.com/Laymen/articles/6117751.html
一、添加maven依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.3</version>
</dependency> <dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.3</version>
</dependency>
如果嫌麻烦可以直接添加shiro-all的依赖
二、web.xml配置Shiro的过滤器
要让shiro拦截web的所有请求那么需要我们在web.xml中配置Shrio和web项目整合提供的filter,配置如下:
<!-- 配置Shiro过滤器,先让Shiro过滤系统接收到的请求 -->
<!-- 这里filter-name必须对应applicationContext.xml中定义的<bean id="shiroFilter"/> -->
<!-- 使用[/*]匹配所有请求,保证所有的可控请求都经过Shiro的过滤 -->
<!-- 通常会将此filter-mapping放置到最前面(即其他filter-mapping前面),以保证它是过滤器链中第一个起作用的 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 -->
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
三、Application-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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <util:properties id="securityCode" location="classpath:config/security-management.properties"/> <bean id="jdbcRealm" class="com.layman.study.core.shiro.realm.LaymanJdbcRealm">
<property name="permissionsLookupEnabled" value="true"/>
<property name="name" value="jdbcRealm"/>
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="storedCredentialsHexEncoded" value="true"/>
<property name="hashAlgorithmName" value="MD5"/>
</bean>
</property>
<property name="authorizationCachingEnabled" value="true"/>
<property name="authorizationCacheName" value="shiro_authorization_cache"/>
</bean> <bean id="customAuthorizationFilter" class="com.layman.study.core.shiro.filter.CustomAuthorizationFilter">
<property name="ignoreList">
<list>
<value>/</value>
<value>/login</value>
<value>/logout</value>
<value>/index</value>
<value>/user/register</value>
</list>
</property>
</bean> <!-- Shiro默认会使用Servlet容器的Session,可通过sessionMode属性来指定使用Shiro原生Session -->
<!-- 即<property name="sessionMode" value="native"/>,详细说明见官方文档 -->
<!-- 这里主要是设置自定义的单Realm应用,若有多个Realm,可使用'realms'属性代替 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="jdbcRealm"/>
<property name="cacheManager">
<bean class="com.layman.study.core.shiro.cache.CustomCacheManager"/>
</property>
<property name="sessionManager">
<bean class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="deleteInvalidSessions" value="true"/>
<property name="sessionDAO">
<bean class="com.layman.study.core.shiro.session.CustomSessionDao"/>
</property>
</bean>
</property>
</bean> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login"/>
<property name="successUrl" value="/index"/>
<property name="unauthorizedUrl" value="/static/page/404.html"/> <property name="filters">
<util:map>
<entry key="customAuthorizationFilter" value-ref="customAuthorizationFilter"/>
</util:map>
</property> <property name="filterChainDefinitionMap">
<map>
<entry key="/index" value="authc"/>
<entry key="/**" value="customAuthorizationFilter"/>
</map>
</property>
</bean>
<!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
</beans>
bean的id为shiroFilter的就是web.xml中代理filter需要的spring bean,这个bean中可以看到:
1.loginUrl:指定了当拦截到请求时如果没有登录那么就会跳转到这个属性指定的地址让用户进行登录操作。
2.successUrl:指定了用户登录成功后跳转的地址
3.unauthorizedUrl:用户的请求被判断为没有权限时会跳转到这个属性指定的页面
4.filters:指定过滤器链,可以是默认提供的过滤器也可以指定自定义的过滤器,在这里我指定的是自定义的过滤器com.layman.study.core.shiro.filter.CustomAuthorizationFilter
5.filterChainDefinitionMap:指定过滤器拦截的urlpatten,<entry key="/index" value="authc"/>这句就是使用了Shiro默认给我们提供的一个过滤器org.apache.shiro.web.filter.authc.FormAuthenticationFilter(用户访问/index这个路劲时是需要登录的)<entry key="/**" value="customAuthorizationFilter"/>定义了我们自定义过滤器拦截所有的请求。
Shrio为我们提供的默认过滤器:
/** * Shiro-1.2.2内置的FilterChain * @see ========================================================================================================= * @see 1)Shiro验证URL时,URL匹配成功便不再继续匹配查找(所以要注意配置文件中的URL顺序,尤其在使用通配符时) * @see 故filterChainDefinitions的配置顺序为自上而下,以最上面的为准 * @see 2)当运行一个Web应用程序时,Shiro将会创建一些有用的默认Filter实例,并自动地在[main]项中将它们置为可用 * @see 自动地可用的默认的Filter实例是被DefaultFilter枚举类定义的,枚举的名称字段就是可供配置的名称 * @see anon---------------org.apache.shiro.web.filter.authc.AnonymousFilter * @see authc--------------org.apache.shiro.web.filter.authc.FormAuthenticationFilter * @see authcBasic---------org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter * @see logout-------------org.apache.shiro.web.filter.authc.LogoutFilter * @see noSessionCreation--org.apache.shiro.web.filter.session.NoSessionCreationFilter * @see perms--------------org.apache.shiro.web.filter.authz.PermissionAuthorizationFilter * @see port---------------org.apache.shiro.web.filter.authz.PortFilter * @see rest---------------org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter * @see roles--------------org.apache.shiro.web.filter.authz.RolesAuthorizationFilter * @see ssl----------------org.apache.shiro.web.filter.authz.SslFilter *@see user---------------org.apache.shiro.web.filter.authz.UserFilter * @see ========================================================================================================= * @see 3)通常可将这些过滤器分为两组 * @see anon,authc,authcBasic,user是第一组认证过滤器 * @see perms,port,rest,roles,ssl是第二组授权过滤器 * @see 注意user和authc不同:当应用开启了rememberMe时,用户下次访问时可以是一个user,但绝不会是authc,因为authc是需要重新认证的 * @see user表示用户不一定已通过认证,只要曾被Shiro记住过登录状态的用户就可以正常发起请求,比如rememberMe * @see 说白了,以前的一个用户登录时开启了rememberMe,然后他关闭浏览器,下次再访问时他就是一个user,而不会authc * @see ========================================================================================================== * @see 4)举几个例子 * @see /admin=authc,roles[admin] 表示用户必需已通过认证,并拥有admin角色才可以正常发起'/admin'请求 * @see /edit=authc,perms[admin:edit] 表示用户必需已通过认证,并拥有admin:edit权限才可以正常发起'/edit'请求 * @see /home=user 表示用户不一定需要已经通过认证,只需要曾经被Shiro记住过登录状态就可以正常发起'/home'请求 * @see ========================================================================================================== * @see 5)各默认过滤器常用如下(注意URL Pattern里用到的是两颗星,这样才能实现任意层次的全匹配) * @see /admins/**=anon 无参,表示可匿名使用,可以理解为匿名用户或游客 * @see /admins/user/**=authc 无参,表示需认证才能使用 * @see /admins/user/**=authcBasic 无参,表示httpBasic认证 * @see /admins/user/**=user 无参,表示必须存在用户,当登入操作时不做检查 * @see /admins/user/**=ssl 无参,表示安全的URL请求,协议为https * @see /admins/user/**=perms[user:add:*] * @see 参数可写多个,多参时必须加上引号,且参数之间用逗号分割,如/admins/user/**=perms["user:add:*,user:modify:*"] * @see 当有多个参数时必须每个参数都通过才算通过,相当于isPermitedAll()方法 * @see /admins/user/**=port[8081] * @see 当请求的URL端口不是8081时,跳转到schemal://serverName:8081?queryString * @see 其中schmal是协议http或https等,serverName是你访问的Host,8081是Port端口,queryString是你访问的URL里的?后面的参数 * @see /admins/user/**=rest[user] * @see 根据请求的方法,相当于/admins/user/**=perms[user:method],其中method为post,get,delete等 * @see /admins/user/**=roles[admin] * @see 参数可写多个,多个时必须加上引号,且参数之间用逗号分割,如/admins/user/**=roles["admin,guest"] * @see 当有多个参数时必须每个参数都通过才算通过,相当于hasAllRoles()方法
com.layman.study.core.shiro.filter.CustomAuthorizationFilter自定义过滤器源码:
public class CustomAuthorizationFilter extends AuthorizationFilter {
    private static final Logger LOGGER = LoggerFactory.getLogger(CustomAuthorizationFilter.class);
    private static final String AJAX_REQUEST = "XMLHttpRequest";
    private List<String> ignoreList;
    public void setIgnoreList(List<String> ignoreList) {
        this.ignoreList = ignoreList;
    }
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        String path = getPathWithinApplication(request);
        //去除.json的后缀
        if (path.endsWith(".json")) {
            path = path.substring(0, path.length() - 5);
        }
        //忽略(通过)特定后缀的访问
        String ext = getExt(path);
        if (ext != null && !ext.equals(".json")) {
            return true;
        }
        if (!CollectionUtils.isEmpty(ignoreList) && ignoreList.contains(path)) {
            return true;
        }
        //url改写 如/site/add  改为/site:add,就是把后面的操作(方法)区分出来
        int i = path.lastIndexOf('/');
        if (i > 0) {
            path = path.substring(0, i) + ":" + path.substring(i + 1, path.length());
        } else if (i == 0) {
            path = "/:" + path.substring(1, path.length());
        }
        //进行权限验证
        Subject subject = getSubject(request, response);
        boolean isPermitted = false;
        try {
            isPermitted = subject.isPermitted(path);
        } catch (Exception e) {
            LOGGER.error("判断权限出错:{}", e);
        }
        LOGGER.info(path + ":" + isPermitted);
        return isPermitted;
    }
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
        Subject subject = getSubject(request, response);
        if (StringUtils.isEmpty(subject.getPrincipal())) {
            String header = WebUtils.toHttp(request).getHeader("x-requested-with");
            //当shiro的session超时时 如果用户发起了ajax请求这个时候页面并没有跳转到我们的配置的登录页面,
            // 所以在后端判断了下如果登录超时并是ajax请求就发送一个错误码,在页面使用全局ajax配置判断返回码进行跳转操作
            if (AJAX_REQUEST.equals(header)) {
                WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
            } else {
                saveRequestAndRedirectToLogin(request, response);
            }
        } else {
            String unauthorizedUrl = getUnauthorizedUrl();
            String path = getPathWithinApplication(request);
            //如果以.json的形式访问 则返回.json形式的提醒
            if (path.endsWith(".json")) {
                unauthorizedUrl += ".json";
            }
            if (org.apache.shiro.util.StringUtils.hasText(unauthorizedUrl)) {
                WebUtils.issueRedirect(request, response, unauthorizedUrl);
            } else {
                WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
            }
        }
        return false;
    }
    /**
     * 获取后缀  .js .css等
     *
     * @param path
     * @return
     */
    private String getExt(String path) {
        if (path != null) {
            int index = path.lastIndexOf(".");
            if (index >= 0) {
                return path.substring(index, path.length());
            }
        }
        return null;
    }
}
过滤器拦截到请求后会委托给SecurityManager进行权限验证,SecurityManager配置:
<!-- Shiro默认会使用Servlet容器的Session,可通过sessionMode属性来指定使用Shiro原生Session -->
<!-- 即<property name="sessionMode" value="native"/>,详细说明见官方文档 -->
<!-- 这里主要是设置自定义的单Realm应用,若有多个Realm,可使用'realms'属性代替 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="jdbcRealm"/>
<property name="cacheManager">
<bean class="com.layman.study.core.shiro.cache.CustomCacheManager"/>
</property>
<property name="sessionManager">
<bean class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="deleteInvalidSessions" value="true"/>
<property name="sessionDAO">
<bean class="com.layman.study.core.shiro.session.CustomSessionDao"/>
</property>
</bean>
</property>
</bean>
自定义realm
在进行权限验证的时候会通过realm去查询身份和权限信息,这里我使用了一个自定义的realm(com.layman.study.core.shiro.realm.LaymanJdbcRealm)去mysql中查询用户身份信息和权限信息,relam配置如下:
<bean id="jdbcRealm" class="com.layman.study.core.shiro.realm.LaymanJdbcRealm">
<property name="permissionsLookupEnabled" value="true"/>
<property name="name" value="jdbcRealm"/>
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="storedCredentialsHexEncoded" value="true"/>
<property name="hashAlgorithmName" value="MD5"/>
</bean>
</property>
<property name="authorizationCachingEnabled" value="true"/>
<property name="authorizationCacheName" value="shiro_authorization_cache"/>
</bean>
1.credentialsMatcher:mysql中用户信息的密码我进行了一次MD5散列算法,不是保存的明文,所以在realm中我们可以通过这个属性指定我们需要的散列算法,同时在进行身份验证的时候我们还可以指定参与散列算法的salt,这个值是用户注册时一起保存在数据库中的。shiro在身份验证的时候对salt的操作是使用ByteSource类将salt转会为byte[],所以在进行散列算法的时候需要使用shiro提供的算法类在提供了salt时进行相同的处理,用户密码散列算法如下:
//使用shiro提供的散列算法类进行散列计算
String pwd = new Md5Hash(user.getPassword(), user.getSalt(), 1).toString(); /**
* 随机产生的salt
*
* @return
*/
private String getRandomSalt() {
//shiro提供的一个随机数生存类
RandomNumberGenerator gen = new SecureRandomNumberGenerator();
ByteSource salt = gen.nextBytes();//返回的是一个SimpleByteSource实例
return salt.toString();//SimpleByteSource类覆写了toString方法,其实质就是调用了它的toBase64()方法
}
或者使用SimpleHashRequest类,指定需要的散列算法
2.authorizationCacheName:指定权限缓存名称,shiro针对用户每个请求都会去判断是否有权限,如果使用了缓存,CacheManager会调用getCache(String name)(方法这里的name值就是authorizationCacheName的值)获得一个Cache实例进行缓存操作。
LaymanJdbcRealm源码
@Component
public class LaymanJdbcRealm extends JdbcRealm { @Autowired
private SysUserService userService; @Autowired
private SysPermissionService permissionService; @Autowired
private SysRoleService roleService; @Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName = (String) token.getPrincipal();
SysUser user = userService.getUserByName(userName); if (null == user || null == user.getPassword()) {
return null;
} return new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), ByteSource.Util.bytes(user.getSalt()), user.getNickName());
} @Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
Set<String> roles = new HashSet<String>();
Set<String> permissions = new HashSet<String>(); String userName = (String) principals.getPrimaryPrincipal();
SysUser user = userService.getUserByName(userName); List<SysRole> roleList = roleService.getRoleList(user.getId());
if (null != user) {
for (SysRole sysRole : roleList) {
roles.add(sysRole.getId().toString());
List<SysPermission> permissionList = permissionService.getPermissionListByRoleId(sysRole.getId());
for (SysPermission permission : permissionList) {
permissions.add(permission.getPermissionCode());
}
}
}
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setStringPermissions(permissions); return authorizationInfo;
}
}
自定义缓存(Cache)策略(使用的redis作为缓存)
com.layman.study.core.shiro.cache.CustomCacheManager源码:
public class CustomCacheManager implements CacheManager {
    private static final Logger LOGGER = LoggerFactory.getLogger(CustomCacheManager.class);
    @Autowired
    private RedisTemplate redisTemplate;
    @Value("#{securityCode['security.management.code']}")
    private String securityManagementCode;
    private static ConcurrentMap<String, CustomRedisCache> cacheMap = new ConcurrentHashMap<String, CustomRedisCache>();
    public <K, V> Cache<K, V> getCache(String name) throws CacheException {
        CustomRedisCache cache;
        cache = cacheMap.get(name);
        if (null != cache) {
            LOGGER.info("从cacheMap中根据名字:{},获取到了cache", name);
        }
        cache = new CustomRedisCache(redisTemplate, securityManagementCode);
        cacheMap.put(name, cache);
        return cache;
    }
}
CustomCacheManager在spring实例化时就会根据realm中配置的信息调用getCache(String name)方法查询Cache实例,如果没有获取到就创建一个Cache返回并保存到自己的ConcurrentMap中,Cache实例才是我们对权限信息进行缓存操作的具体实现,这里我使用的是redis作为缓存容器
那么就需要自定一个Cache对redis进行操作,CustomRedisCache源码如下:
public class CustomRedisCache<K, V> implements Cache<K, V> {
    private static final Logger LOGGER = LoggerFactory.getLogger(CustomRedisCache.class);
    private static final String SHIRO_CACHE_PREFIX = "shiro-cache:";
    private String SECURITY_MANAGEMENT_CODE = "default";
    private RedisTemplate redisTemplate;
    public CustomRedisCache() {
    }
    public CustomRedisCache(RedisTemplate redisTemplate, String securityManagementCode) {
        this.redisTemplate = redisTemplate;
        this.SECURITY_MANAGEMENT_CODE = securityManagementCode;
    }
    @Override
    public V get(K key) throws CacheException {
        if (null == key) {
            return null;
        }
        try {
            V result = (V) redisTemplate.opsForValue().get((K) (SHIRO_CACHE_PREFIX + key));
            LOGGER.info("根据key[{}]获得缓存{}", key, result);
            return result;
        } catch (Exception e) {
            LOGGER.error("获取shiro缓存错误:", e);
            throw new CacheException(e);
        }
    }
    @Override
    public V put(K key, V value) throws CacheException {
        try {
            redisTemplate.opsForValue().set((K) (SHIRO_CACHE_PREFIX + key), value, 10, TimeUnit.MINUTES);
            LOGGER.info("存储shiro的缓存信息,key:{},value:{}", key, value);
            return value;
        } catch (Exception e) {
            LOGGER.error("存储shiro缓存发生错误key={},value={},error=", key, value, e);
            throw new CacheException(e);
        }
    }
    @Override
    public V remove(K key) throws CacheException {
        try {
            V deleteObj = get(key);
            redisTemplate.delete((K) (SHIRO_CACHE_PREFIX + key));
            LOGGER.info("删除shrio缓存key={},value={}", key, deleteObj);
            return deleteObj;
        } catch (Exception e) {
            LOGGER.error("删除shiro缓存发生错误:{}", e);
            throw new CacheException(e);
        }
    }
    @Override
    public void clear() throws CacheException {
        try {
            redisTemplate.delete(SHIRO_CACHE_PREFIX + "*");
            LOGGER.info("成功清楚shiro所有缓存");
        } catch (Exception e) {
            LOGGER.error("删除所有shiro缓存出错:{}", e);
            throw new CacheException(e);
        }
    }
    @Override
    public int size() {
        try {
            Long size = redisTemplate.opsForValue().size(SHIRO_CACHE_PREFIX + "*");
            LOGGER.info("shiro缓存大小:{}", size);
            return size.intValue();
        } catch (Exception e) {
            LOGGER.error("获取食肉缓存大小错误:{}", e);
            throw new CacheException(e);
        }
    }
    @Override
    public Set<K> keys() {
        try {
            Set<K> keys = redisTemplate.keys((K) (SHIRO_CACHE_PREFIX + "*"));
            if (CollectionUtils.isEmpty(keys)) {
                return Collections.emptySet();
            } else {
                Set<K> newKeys = new HashSet<K>();
                for (K key : keys) {
                    newKeys.add(key);
                }
                LOGGER.info("获取shiro缓存的所有key:{}", newKeys);
                return newKeys;
            }
        } catch (Exception e) {
            LOGGER.error("获取shiro缓存的keys错误:{}", e);
            throw new CacheException(e);
        }
    }
    @Override
    public Collection<V> values() {
        try {
            Set<K> keys = redisTemplate.keys(SHIRO_CACHE_PREFIX + "*");
            if (!CollectionUtils.isEmpty(keys)) {
                List<V> values = new ArrayList<V>(keys.size());
                for (K key : keys) {
                    V value = get(key);
                    if (value != null) {
                        values.add(value);
                    }
                }
                LOGGER.info("获取shiro缓存的所有value:{}", values);
                return Collections.unmodifiableList(values);
            } else {
                return Collections.emptyList();
            }
        } catch (Throwable t) {
            LOGGER.error("获取shiro缓存的values错误:{}", t);
            throw new CacheException(t);
        }
    }
}
自定义SessionDao
Shiro提供安全框架界独一无二的东西:一个完整的企业级Session 解决方案,可以为任意的应用提供session支持,包括web和非web应用,并且无需部署你的应用程序到Web 容器或使用EJB容器。 关于SessionManager,SessionManager是用来管理Session的组件,包括:创建,删除,inactivity(失效)及验证,等等。SessionManager 也是一个由SecurityManager 维护的顶级组件。shiro提供了默认的SessionManager实现,一般没有必要自定义这个。 但是可以通过设置他的属性来控制Session管理策略:
(1)设置Sessioin的过期时间;Shiro 的SessionManager 实现默认是30 分钟会话超时。你可以设置SessionManager 默认实现的globalSessionTimeout 属性来为所有的会话定义默认的超时时间。
(2)Sessioin的事件监听;你可以实现SessionListener 接口(或扩展易用的SessionListenerAdapter)并与相应的会话操作作出反应。
。。。。。。。等
关于SessionDAO,每当一个会话被创建或更新时,它的数据需要持久化到一个存储位置以便它能够被稍后的应用程序访问,实现这个功能的组件就是SessionDAO。你能够实现该接口来与你想要的任何数据存储进行通信。这意味着你的会话数据可以驻留在内存中,文件系统,关系数据库或NoSQL 的数据存储,或其他任何你需要的位置。
com.layman.study.core.shiro.session.CustomSessionDao源码:
public class CustomSessionDao extends AbstractSessionDAO {
    private static final Logger LOGGER = LoggerFactory.getLogger(CustomSessionDao.class);
    private static final String SHIRO_SESSION_PREFIX = "shiro-session:";
    @Value("#{securityCode['security.management.code']}")
    private String securityManagementCode;
    @Autowired
    private RedisTemplate<String, byte[]> redisTemplate;
    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = this.getSessionIdGenerator().generateId(session);
        this.assignSessionId(session, sessionId);
        String key = this.buildRedisKey(sessionId);
        redisTemplate.opsForValue().set(key, SerializationUtils.serialize(session), session.getTimeout(), TimeUnit.MILLISECONDS);
        return sessionId;
    }
    @Override
    protected Session doReadSession(Serializable serializable) {
        if (null == serializable) {
            return null;
        }
        String key = this.buildRedisKey(serializable);
        byte[] value = redisTemplate.opsForValue().get(key);
        return (Session) SerializationUtils.deserialize(value);
    }
    @Override
    public void update(Session session) throws UnknownSessionException {
        redisTemplate.opsForValue().set(this.buildRedisKey(session.getId()), SerializationUtils.serialize(session), session.getTimeout(), TimeUnit.MILLISECONDS);
    }
    @Override
    public void delete(Session session) {
        redisTemplate.delete(this.buildRedisKey(session.getId()));
    }
    @Override
    public Collection<Session> getActiveSessions() {
        Set<Session> sessions = new HashSet<Session>();
        Set<String> keys = redisTemplate.keys(this.buildRedisKey("*"));
        if (!CollectionUtils.isEmpty(keys)) {
            for (String key : keys) {
                Session s = (Session) SerializationUtils.deserialize(redisTemplate.opsForValue().get(key));
                sessions.add(s);
            }
        }
        return sessions;
    }
    private String buildRedisKey(Serializable sessionId) {
        return SHIRO_SESSION_PREFIX + securityManagementCode + ":" + sessionId;
    }
}
开启注解
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor" > <property name="proxyTargetClass" value="true"/> </bean> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager" /> </bean>
@RequiresPermissions() @RequiresAuthentication() @RequiresRoles() @RequiresUser() @RequiresGuest()的使用
引入Tag
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
guest 标签将显示它包含的内容,仅当当前的Subject 被认为是‘guest’时。‘guest’是指没有身份ID 的任何Subject。也就是说,我们并不知道用户是谁,因为他们没有登录并且他们没有在上一次的访问中被记住(RememberMe 服务), guest 标签与user 标签逻辑相反。例子:
<shiro:guest> Hi there! Please <a href="login.jsp">Login</a> or <a href="signup.jsp">Signup</a>today! </shiro:guest>
user 标签将显示它包含的内容,仅当当前的Subject 被认为是‘user’时。‘user’在上下文中被定义为一个已知身份ID的Subject,或是成功通过身份验证及通过‘RememberMe’服务的。请注意这个标签在语义上与authenticated 标签是不同的,authenticated 标签更为严格。usre 标签与guest 标签逻辑相反。
仅仅只当当前用户在当前会话中成功地通过了身份验证authenticated 标签才会显示包含的内容。它比‘user’标签更为严格。它在逻辑上与‘notAuthenticated’标签相反。
notAuthenticated 标签将会显示它所包含的内容,如果当前Subject 还没有在其当前会话中成功地通过验证。
principal 标签将会输出Subject 的主体(标识属性)或主要的属性。
hasRole 标签将会显示它所包含的内容,仅当当前Subject 被分配了具体的角色。 hasRole 标签与lacksRole 标签逻辑相反。 例如:
<shiro:hasRole name="administrator">
<a href="admin.jsp">Administer the system</a>
</shiro:hasRole>
lacksRole 标签将会显示它所包含的内容,仅当当前Subject 未被分配具体的角色
hasAnyRole 标签将会显示它所包含的内容,如果当前的Subject 被分配了任意一个来自于逗号分隔的角色名列表中的具体角色。例如:
<shiro:hasAnyRoles name="developer, project manager, administrator">
You are either a developer, project manager, or administrater.
</shiro:hasAnyRoles>
hasPermission 标签将会显示它所包含的内容,仅当当前Subject“拥有”(蕴含)特定的权限。也就是说,用户具有特定的能力。hasPermission 标签与lacksPermission 标签逻辑相反。例如:
<shiro:hasPermission name="user:create">
<a href="createUser.jsp">Create a new User</a>
</shiro:hasPermission>
lacksPermission 标签将会显示它所包含的内容,仅当当前Subject 没有拥有(蕴含)特定的权限。也就是说,用户没有特定的能力。
SpringMVC整合Apache Shiro的更多相关文章
- springboot 整合apache shiro
		
这几天因为项目需要,学习了下shiro,由此留下一些记录,也希望对初学shiro的朋友有帮助. springboot 是这两年新兴起来的一个项目,它的出现是为了减少springmvc开发过程中需要引入 ...
 - SpringBoot整合Apache Shiro权限验证框架
		
比较常见的权限框架有两种,一种是Spring Security,另一种是Apache Shiro,两种框架各有优劣,个人感觉Shiro更容易使用,更加灵活,也更符合RABC规则,而且是java官方更推 ...
 - SpringBoot整合Apache Shiro
		
Subject 用户主体 (把操作交给SecurityManager)SecurityManager 安全管理器 (关联Realm)Realm Shiro连接数据的桥梁 引入maven依赖 < ...
 - SpringMVC+Apache Shiro+JPA(hibernate)整合配置
		
序: 关于标题: 说是教学,实在愧不敢当,但苦与本人文笔有限,实在找不到更合理,谦逊的词语表达,只能先这样定义了. 其实最真实的想法,只是希望这个关键词能让更多的人浏览到这篇文章,也算是对于自己写文章 ...
 - 将 Shiro 作为应用的权限基础 五:SpringMVC+Apache Shiro+JPA(hibernate)整合配置
		
配置web.xml,applicationContext.xml, spring-mvc.xml,applicationContext-shiro.xml,而且都有详细的说明. Web.xml是web ...
 - [转载] 【Shiro】Apache Shiro架构之实际运用(整合到Spring中)
		
写在前面:前面陆陆续续对Shiro的使用做了一些总结,如题,这篇博文主要是总结一下如何将Shiro运用到实际项目中,也就是将Shiro整到Spring中进行开发.后来想想既然要整,就索性把Spring ...
 - SpringMVC整合Shiro——(3)
		
SpringMVC整合Shiro,Shiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能. 第一步:配置web.xml <!-- 配置Shiro过滤器,先让Shiro ...
 - Apache shiro的简单介绍与使用(与spring整合使用)
		
apache shiro框架简介 Apache Shiro是一个强大而灵活的开源安全框架,它能够干净利落地处理身份认证,授权,企业会话管理和加密.现在,使用Apache Shiro的人越来越多,因为它 ...
 - SpringMVC整合Shiro权限框架
		
尊重原创:http://blog.csdn.net/donggua3694857/article/details/52157313 最近在学习Shiro,首先非常感谢开涛大神的<跟我学Shiro ...
 
随机推荐
- Webpack概念
			
webpack概念 我经常用 webpack,打算做一次比较详细的概念清点和梳理.从 0 配置 webpack,由于 webpack5(2019.07.27)暂时还没有发布.并且从Webpack Mi ...
 - pheatmap绘制“热图”,你需要的都在这
			
热图可以聚合大量的数据,并可以用一种渐进色来优雅地表现,可以很直观地展现数据的疏密程度或频率高低. 本文利用R语言 pheatmap 包从头开始绘制各种漂亮的热图.参数像积木,拼凑出你最喜欢的热图即可 ...
 - Ubuntu 下jdk的安装
			
因为我ubuntu下需要运行一个java程序,其实是想做一下tc,因为浏览器要运行java插件,那个客户端一直下载不了,我记得我装过的,这个问题后面说.然后我就打算重新安装,通过查找资料,终于解决了手 ...
 - 检测人脸及眼睛【OpenCV-Python实现 源码+打包.exe文件】
			
之前用opencv做的一个人脸及双眼检测,在此分享给大家 链接:https://pan.baidu.com/s/1BsKBH3wOF9TmdbRlPagEVQ 提取码:cqkv 效果如下:
 - 在 dotnet core (C#)下的颜色渐变
			
直接使用等比例抽样算法,连同透明度一起计算. public IList<Color> ShadeColors(Color c1, Color c2, int resultCount) { ...
 - 爬虫环境搭建及 scrapy 启动
			
创建虚拟环境 C:\Users\Toling>mkvirtualenv article 这个是普通的创建虚拟环境,但是实际开发中可能会使用python2或python3所以我们需要指定开发的环境 ...
 - <<Modern CMake>> 翻译 2.4 项目目录结构
			
<<Modern CMake>> 翻译 2.4 项目目录结构 本节内容有点跑题.但我认为这是一个很好的方法. 我将告诉你如何规划项目的目录. 这是基于惯例,但将帮助您: 轻松阅 ...
 - Samba:基于公网 IP 的服务访问
			
写在前面的话 由于使用过程中,发现如果 Samba 只用于内网访问,同事在外面甚至其它不是一个网段的同事就无法访问了.这显然不符合我们最终的需求,最后没法,只能把访问部署到云服务器上面去,此时问题来了 ...
 - getpost请求案例
			
public class MainActivity extends AppCompatActivity { private ListView lv; @Override protected void ...
 - linux应用问题分析命令
			
1. 描述 应用问题分析方式及命令有很多,一般都结合着使用,今天主要介绍下: (1)top命令,实时查看服务器资源使用情况,类似windows下的资源管理器: (2)tail命令,实时刷新查看日志命令 ...