shiro 入门

一、概述

Apache Shiro是Java的一个安全框架,旨在简化身份验证和授权。Shiro在JavaSE和JavaEE项目中都可以使用。它主要用来处理身份认证,授权,企业会话管理和加密等。Shiro的具体功能点如下:

(1)身份认证/登录,验证用户是不是拥有相应的身份; 
(2)授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限; 
(3)会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的; 
(4)加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储; 
(5)Web支持,可以非常容易的集成到Web环境; 
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率; 
(6)shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去; 
(7)提供测试支持; 
(8)允许一个用户假装为另一个用户(如果他们允许)的身份进行访问; 
(9)记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

文字描述可能并不能让猿友们完全理解具体功能的意思。下面我们以登录验证为例,向猿友们介绍Shiro的使用。至于其他功能点,猿友们用到的时候再去深究其用法也不迟。

二、Shiro实例详细说明

1)依赖包

<!-- shiro -->
<dependency>
   <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-spring</artifactId>
   <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session</artifactId>
    <version>2.0.0.M2</version>
</dependency>
2)web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="http://java.sun.com/xml/ns/javaee"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
   version="3.0">
   <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:shiro.xml</param-value>
   </context-param>
   <filter>
      <filter-name>encodingFilter</filter-name>
      <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
      <async-supported>true</async-supported>
      <init-param>
         <param-name>encoding</param-name>
         <param-value>UTF-8</param-value>
      </init-param>
      <init-param>
         <param-name>forceEncoding</param-name>
         <param-value>true</param-value>
      </init-param>
   </filter>
   <filter-mapping>
      <filter-name>encodingFilter</filter-name>
      <url-pattern>/*</url-pattern>
   </filter-mapping>
   <filter>
      <filter-name>csrfFilter</filter-name>
      <filter-class>top.ibase4j.core.filter.CsrfFilter</filter-class>
   </filter>
   <filter-mapping>
      <filter-name>csrfFilter</filter-name>
      <url-pattern>/*</url-pattern>
   </filter-mapping>
   <filter>
      <filter-name>shiroFilter</filter-name>
      <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
      <init-param>
         <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>
   <listener>
      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
   </listener>
   <listener>
      <listener-class>org.ibase4j.core.ServerListener</listener-class>
   </listener>
   <session-config>
      <session-timeout>30</session-timeout>
   </session-config>
   <welcome-file-list>
      <welcome-file>index.jsp</welcome-file>
   </welcome-file-list>
</web-app>
3).shiro.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
   xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd  
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.0.xsd">
   <!-- 这里主要是设置自定义的单Realm应用,若有多个Realm,可使用'realms'属性代替 -->
   <bean id="sessionDAO" class="top.ibase4j.core.support.cache.shiro.RedisSessionDAO" />
   <bean id="realm" class="org.ibase4j.core.shiro.Realm" />
   <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
      <property name="realm" ref="realm" />
      <property name="cacheManager">
         <bean class="top.ibase4j.core.support.cache.shiro.RedisCacheManager" />
      </property>
      <property name="sessionManager" ref="sessionManager" />
      <property name="rememberMeManager" ref="rememberMeManager" />
   </bean>
   <bean id="sessionListener" class="top.ibase4j.core.listener.SessionListener" />
   <bean id="sessionManager"
      class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
      <property name="sessionDAO" ref="sessionDAO" />
      <property name="sessionListeners">
         <list>
            <ref bean="sessionListener" />
         </list>
      </property>
      <property name="sessionIdCookie">
         <bean class="org.apache.shiro.web.servlet.SimpleCookie">
            <constructor-arg value="IBASE4JSESSIONID" />
            <property name="httpOnly" value="true" />
            <property name="maxAge" value="-1" />
            <property name="path" value="/" />
            <property name="secure" value="${session.cookie.secure}" />
         </bean>
      </property>
   </bean>
   <!-- rememberMe管理器 -->
   <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
      <property name="cookie">
         <!-- rememberMe缓存cookie -->
         <bean class="org.apache.shiro.web.servlet.SimpleCookie">
            <constructor-arg value="rememberMe" />
            <!-- 不会暴露给客户端 -->
            <property name="httpOnly" value="true" />
            <!-- 记住我cookie生效时间 -->
            <property name="maxAge" value="${rememberMe.cookie.maxAge}" />
         </bean>
      </property>
   </bean>
   <!-- Web应用中,Shiro可控制的Web请求必须经过Shiro主过滤器的拦截,Shiro对基于Spring的Web应用提供了完美的支持 -->
   <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
      <!-- Shiro的核心安全接口,这个属性是必须的 -->
      <property name="securityManager" ref="securityManager" />
      <!-- 要求登录时的链接 -->
      <property name="loginUrl" value="/unauthorized" />
      <!-- 用户访问未对其授权的资源时,所显示的连接 -->
      <property name="unauthorizedUrl" value="/forbidden" />
      <!-- Shiro连接约束配置,即过滤链的定义 -->
      <!-- anon:它对应的过滤器里面是空的,什么都没做 -->
      <!-- authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter -->
      <property name="filterChainDefinitions">
         <value>
            /=anon
            /app/**=anon
            /index.jsp=anon
            /regin=anon
            /login=anon
            /*.ico=anon
            /upload/*=anon
            /unauthorized=anon
            /forbidden=anon
            /sns*=anon
            /*/api-docs=anon
            /callback*=anon
            /swagger*=anon
            /configuration/*=anon
            /*/configuration/*=anon
            /webjars/**=anon
            /**=authc,user
         </value>
      </property>
   </bean>
   <!-- Shiro生命周期处理器 -->
   <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
   <!-- 开启Shiro Spring AOP权限注解@RequiresPermissions的支持 -->
   <bean depends-on="lifecycleBeanPostProcessor"
      class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
      <property name="proxyTargetClass" value="true" />
   </bean>
   <bean
      class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
      <property name="securityManager" ref="securityManager" />
   </bean>
</beans>

RedisCacheManage.java


package top.ibase4j.core.support.cache.shiro;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;

public class RedisCacheManager implements CacheManager {
    private final Logger logger = LogManager.getLogger();
    private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap();
    private String keyPrefix = "S:iBase4J:SHIRO-CACHE:";

    public RedisCacheManager() {
    }

    public String getKeyPrefix() {
        return this.keyPrefix;
    }

    public void setKeyPrefix(String keyPrefix) {
        this.keyPrefix = keyPrefix;
    }

    public <K, V> Cache<K, V> getCache(String name) throws CacheException {
        this.logger.debug("获取名称为: " + name + " 的RedisCache实例");
        Cache c = (Cache)this.caches.get(name);
        if (c == null) {
            RedisCache cache = new RedisCache(this.keyPrefix);
            this.caches.put(name, cache);
        }

        return c;
    }
}



package top.ibase4j.core.support.cache.shiro;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.util.CollectionUtils;
import top.ibase4j.core.util.CacheUtil;

public class RedisCache<K, V> implements Cache<K, V> {
    private final Logger logger = LogManager.getLogger();
    private String keyPrefix = "shiro_redis_session:";

    public String getKeyPrefix() {
        return this.keyPrefix;
    }

    public void setKeyPrefix(String keyPrefix) {
        this.keyPrefix = keyPrefix;
    }

    public RedisCache(String prefix) {
        this.keyPrefix = prefix;
    }

    public V get(K key) throws CacheException {
        this.logger.debug("根据key从Redis中获取对象 key [" + key + "]");
        Object value = CacheUtil.getCache().getFire(this.getKey(key));
        return value;
    }

    public V put(K key, V value) throws CacheException {
        this.logger.debug("根据key从存储 key [" + key + "]");
        CacheUtil.getCache().set(this.getKey(key), (Serializable)value);
        return value;
    }

    public V remove(K key) throws CacheException {
        this.logger.debug("从redis中删除 key [" + key + "]");
        Object previous = this.get(key);
        CacheUtil.getCache().del(this.getKey(key));
        return previous;
    }

    public void clear() throws CacheException {
        this.logger.debug("从redis中删除所有元素");
        CacheUtil.getCache().delAll(this.keyPrefix + "*");
    }

    public int size() {
        return CacheUtil.getCache().getAll(this.keyPrefix + "*").size();
    }

    public Set<K> keys() {
        Set keys = CacheUtil.getCache().getAll(this.keyPrefix + "*");
        if(CollectionUtils.isEmpty(keys)) {
            return Collections.emptySet();
        } else {
            HashSet newKeys = new HashSet();
            Iterator var3 = keys.iterator();

            while(var3.hasNext()) {
                Object key = var3.next();
                newKeys.add(key);
            }

            return newKeys;
        }
    }

    public Collection<V> values() {
        Set keys = CacheUtil.getCache().getAll(this.keyPrefix + "*");
        if(!CollectionUtils.isEmpty(keys)) {
            ArrayList values = new ArrayList(keys.size());
            Iterator var3 = keys.iterator();

            while(var3.hasNext()) {
                Object key = var3.next();
                Object value = this.get(key);
                if(value != null) {
                    values.add(value);
                }
            }

            return Collections.unmodifiableList(values);
        } else {
            return Collections.emptyList();
        }
    }

    private String getKey(K key) {
        return this.keyPrefix + key;
    }
}
package top.ibase4j.core.util;

import top.ibase4j.core.support.cache.CacheManager;
import top.ibase4j.core.util.PropertiesUtil;

public class CacheUtil {
    private static CacheManager cacheManager;
    private static CacheManager lockManager;

    public CacheUtil() {
    }

    public static void setCacheManager(CacheManager cacheManager) {
        CacheUtil.cacheManager = cacheManager;
    }

    public static void setLockManager(CacheManager cacheManager) {
        lockManager = cacheManager;
    }

    public static CacheManager getCache() {
        return cacheManager;
    }

    public static CacheManager getLockManager() {
        return lockManager;
    }

    public static boolean tryLock(String key) {
        int expires = 1000 * PropertiesUtil.getInt("redis.lock.expires", 180);
        return lockManager.setnx(key, Integer.valueOf(expires));
    }

    public static boolean getLock(String key) {
        return lockManager.lock(key);
    }

    public static void unlock(String key) {
        lockManager.unlock(key);
    }
}

sessionListener.java


package top.ibase4j.core.listener;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.shiro.session.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;

public class SessionListener implements org.apache.shiro.session.SessionListener {
    private Logger logger = LogManager.getLogger();
    @Autowired
    RedisTemplate redisTemplate;

    public SessionListener() {
    }

    public void onStart(Session session) {
        session.setAttribute("webTheme", "default");
        this.logger.info("创建了一个Session连接:[" + session.getId() + "]");
        this.redisTemplate.opsForSet().add("S:iBase4J:ALLUSER_NUMBER", new Object[]{session.getId()});
    }

    public void onStop(Session session) {
        if (this.getAllUserNumber().intValue() > 0) {
            this.logger.info("销毁了一个Session连接:[" + session.getId() + "]");
        }

        session.removeAttribute("CURRENT_USER");
        this.redisTemplate.opsForSet().remove("S:iBase4J:ALLUSER_NUMBER", new Object[]{session.getId()});
    }

    public void onExpiration(Session session) {
        this.onStop(session);
    }

    public Integer getAllUserNumber() {
        return this.redisTemplate.opsForSet().size("S:iBase4J:ALLUSER_NUMBER").intValue();
    }
}
4)自定义 Real

package org.ibase4j.core.shiro;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.ibase4j.model.SysSession;
import org.ibase4j.model.SysUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;

import top.ibase4j.core.base.BaseProvider;
import top.ibase4j.core.base.Parameter;
import top.ibase4j.core.util.WebUtil;

/**
 * 权限检查类
 * 
 * @author ShenHuaJie
 * @version 2016年5月20日 下午3:44:45
 */
public class Realm extends AuthorizingRealm {
   private final Logger logger = LogManager.getLogger();
   @Autowired
   @Qualifier("sysProvider")
   protected BaseProvider provider;
   @Autowired
   private RedisSessionDAO sessionDAO;
   @Autowired
   private RedisOperationsSessionRepository sessionRepository;

   // 权限
   protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
      SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
      Long userId = (Long) WebUtil.getCurrentUser();
      Parameter parameter = new Parameter("sysAuthorizeService", "queryPermissionByUserId", userId);
      logger.info("{} execute queryPermissionByUserId start...", parameter.getNo());
      List<?> list = provider.execute(parameter).getResultList();
      logger.info("{} execute queryPermissionByUserId end.", parameter.getNo());
      for (Object permission : list) {
         if (StringUtils.isNotBlank((String) permission)) {
            // 添加基于Permission的权限信息
            info.addStringPermission((String) permission);
         }
      }
      // 添加用户权限
      info.addStringPermission("user");
      return info;
   }

   // 登录验证
   protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
         throws AuthenticationException {
      UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
      Map<String, Object> params = new HashMap<String, Object>();
      params.put("enable", 1);
      params.put("account", token.getUsername());
      Parameter parameter = new Parameter("sysUserService", "queryList", params);
      logger.info("{} execute sysUserService.queryList start...", parameter.getNo());
      List<?> list = provider.execute(parameter).getResultList();
      logger.info("{} execute sysUserService.queryList end.", parameter.getNo());
      if (list.size() == 1) {
         SysUser user = (SysUser) list.get(0);
         StringBuilder sb = new StringBuilder(100);
         for (int i = 0; i < token.getPassword().length; i++) {
            sb.append(token.getPassword()[i]);
         }
         if (user.getPassword().equals(sb.toString())) {
            WebUtil.saveCurrentUser(user.getId());
            saveSession(user.getAccount(), token.getHost());
            AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getAccount(), user.getPassword(),
                  user.getUserName());
            return authcInfo;
         }
         logger.warn("USER [{}] PASSWORD IS WRONG: {}", token.getUsername(), sb.toString());
         return null;
      } else {
         logger.warn("No user: {}", token.getUsername());
         return null;
      }
   }

   /** 保存session */
   private void saveSession(String account, String host) {
      // 踢出用户
      SysSession record = new SysSession();
      record.setAccount(account);
      Parameter parameter = new Parameter("sysSessionService", "querySessionIdByAccount", record);
      logger.info("{} execute querySessionIdByAccount start...", parameter.getNo());
      List<?> sessionIds = provider.execute(parameter).getResultList();
      logger.info("{} execute querySessionIdByAccount end.", parameter.getNo());
      Subject currentUser = SecurityUtils.getSubject();
      Session session = currentUser.getSession();
      String currentSessionId = session.getId().toString();
      if (sessionIds != null) {
         for (Object sessionId : sessionIds) {
            record.setSessionId((String) sessionId);
            parameter = new Parameter("sysSessionService", "deleteBySessionId", record);
            logger.info("{} execute deleteBySessionId start...", parameter.getNo());
            provider.execute(parameter);
            logger.info("{} execute deleteBySessionId end.", parameter.getNo());
            if (!currentSessionId.equals(sessionId)) {
               sessionDAO.delete((String) sessionId);
//             sessionRepository.delete((String) sessionId);
//             sessionRepository.cleanupExpiredSessions();
            }
         }
      }
      // 保存用户
      record.setSessionId(currentSessionId);
      record.setIp(StringUtils.isBlank(host) ? session.getHost() : host);
      record.setStartTime(session.getStartTimestamp());
      parameter = new Parameter("sysSessionService", "update", record);
      logger.info("{} execute sysSessionService.update start...", parameter.getNo());
      provider.execute(parameter);
      logger.info("{} execute sysSessionService.update end.", parameter.getNo());
   }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值