zoukankan      html  css  js  c++  java
  • Shiro 整合SpringMVC 并实现权限管理,登录和注销

    Shiro 整合SpringMVC 并且实现权限管理,登录和注销

    Apache Shiro是Java的一个安全框架。目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Security做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的Shiro就足够了。

      因为我总结的是使用SpringMVC和Apache Shiro整合,注重的是整合和使用,至于基础,我这里就不细说了。我使用的是maven进行项目的构建,对于非maven的项目只要把这些JAR包下载下来放到相应的位置即可。因为这个项目是整合Spring的,所以除了Apache shiro的JAR之外,我们还需要shiro-web和shiro-spring的的JAR,下面是所需要的所有shiro架包,至于其他的架包,像缓存的架包,Spring和SpringMVC的架包等等还是平时那些通用JAR,没有多余的。

    <dependency>  
            <groupId>org.apache.shiro</groupId>  
            <artifactId>shiro-core</artifactId>  
            <version>1.2.3</version>  
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.2.3</version>
        </dependency>
        <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>

    将JAR都准备好了之后,我们就可以开始正式搭建了。下面就分步骤来创建

    1.

    一:首先创建spring的配置文件,位置都在resource中(非maven的项目可以放到classpath或者是WEB-INF下面,只要保证最后编译之后能在classpath下即可),配置文件为spring-context.xml.

    二:创建Apache Shiro的配置文件,名字是spring-context-shiro.xml,我们只需要和spring的配置文件放在同一级就可以了。

    三:还有一个配置文件是springmvc的,配置文件是spring-mvc。前面两个文件都是以spring-context*开头是有原因的,因为这样我们就可以在web.xml中设置配置文件的时候,直接使用通配符扫描前两个但是又可以不扫描springmvc的配置文件

    这是在web.xml里面配置:

    <!-- 配置spring容器的路径 -->
      <context-param>
              <param-name>contextConfigLocation</param-name>
              <param-value>classpath*:/spring-context-*.xml</param-value>
      </context-param>
      <!-- 对spring开始监听 -->
      <listener>
          <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>

    除了spring的配置,还有一个配置是非常重要的:shiroFilter。对于初次配置shiro的同学经常遇到一个问题:问题大概讲的是shiroFilter找不到,但是我们明明在web.xml和spring-context-shiro配置文件里面配置了呀,怎么回事?这是因为这个shiroFilter名字两边需要一致!!!(是不是很坑,但是其实是可以配置的,只是一般人不知道,这个后面讲)

      <filter>
            <filter-name>shiroFilter</filter-name>
            <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>shiroFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>

    3.

    除了在web.xml中设置spring和spring-shiro配置文件位置之外,我们还需要在web.xml中设置spring-mvc的位置:

    <!-- MVC Servlet
         设置springmvc的Servlet
          -->
     <servlet>
         <servlet-name>springServlet</servlet-name>
         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
         <init-param>
             <param-name>contextConfigLocation</param-name>
             <param-value>classpath:springmvc.xml</param-value>
         </init-param>
         <load-on-startup>1</load-on-startup>
     </servlet>
      <servlet-mapping>
         <servlet-name>springServlet</servlet-name>
         <url-pattern>/</url-pattern>
     </servlet-mapping>

    4

    在spring-context配置文件中,还有一个是需要配置-cacheManager,因为shiro的session是自己实现的,所以我们还需要一个缓存框架,所以在spring的配置文件一定要注意配置哦,用的是ehcache

       <!-- 缓存 -->
        <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
            <property name="configLocation" value="classpath:${ehcache.file}"></property>
        </bean>

    Ehcache的maven地址:

     <dependency>
          <groupId>net.sf.ehcache</groupId>
          <artifactId>ehcache-core</artifactId>
          <version>2.6.9</version>
        </dependency>
       <dependency>
         <groupId>org.apache.shiro</groupId>
         <artifactId>shiro-ehcache</artifactId>
         <version>1.2.3</version>
       </dependency>

    5

    在项目中重点还是配置spring-context-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: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"
        default-lazy-init="true">
    
        <description>Shiro Configuration</description>
    
        <!-- 加载配置属性文件 -->
        <context:property-placeholder ignore-unresolvable="true" location="classpath:yonyou.properties" />
        
        <!-- Shiro权限过滤过滤器定义 -->
        <bean name="shiroFilterChainDefinitions" class="java.lang.String">
            <constructor-arg>
                <value>
                    /static/** = anon
                    /userfiles/** = anon
                    ${adminPath}/login = authc
                    ${adminPath}/logout = logout
                    ${adminPath}/** = user
                </value>
            </constructor-arg>
        </bean>
        
        <!-- 安全认证过滤器 -->
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <property name="securityManager" ref="securityManager" /><!--
            <property name="loginUrl" value="${adminPath}/login" />
            <property name="successUrl" value="${adminPath}?login" />
            <property name="filters">
                <map>
                    <entry key="cas" value-ref="casFilter"/>
                    <entry key="authc" value-ref="formAuthenticationFilter"/>
                </map>
            </property>
            <property name="filterChainDefinitions">
                <ref bean="shiroFilterChainDefinitions"/>
            </property>
        </bean>
    
        
        <!-- 定义Shiro安全管理配置 -->
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <property name="realm" ref="systemAuthorizingRealm" />
            <property name="sessionManager" ref="sessionManager" />
            <property name="cacheManager" ref="shiroCacheManager" />
        </bean>
        
        <!-- 自定义会话管理配置 -->
        <bean id="sessionManager" class="com.yonyou.hotusm.common.security.session.SessionManager"> 
            <property name="sessionDAO" ref="sessionDAO"/>
            
            <!-- 会话超时时间,单位:毫秒  -->
            <property name="globalSessionTimeout" value="${session.sessionTimeout}"/>
            
            <!-- 定时清理失效会话, 清理用户直接关闭浏览器造成的孤立会话   -->
            <property name="sessionValidationInterval" value="${session.sessionTimeoutClean}"/>
    <!--          <property name="sessionValidationSchedulerEnabled" value="false"/> -->
             <property name="sessionValidationSchedulerEnabled" value="true"/>
             
            <property name="sessionIdCookie" ref="sessionIdCookie"/>
            <property name="sessionIdCookieEnabled" value="true"/>
        </bean>
        
        <!-- 指定本系统SESSIONID, 默认为: JSESSIONID 问题: 与SERVLET容器名冲突, 如JETTY, TOMCAT 等默认JSESSIONID,
            当跳出SHIRO SERVLET时如ERROR-PAGE容器会为JSESSIONID重新分配值导致登录会话丢失! -->
        <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
            <constructor-arg name="name" value="hotusm.session.id"/>
        </bean>
    
        <bean id="sessionDAO" class="com.yonyou.hotusm.common.security.session.CacheSessionDAO">
            <property name="sessionIdGenerator" ref="idGen" />
            <property name="activeSessionsCacheName" value="activeSessionsCache" />
            <property name="cacheManager" ref="shiroCacheManager" />
        </bean>
        
        <!-- 定义授权缓存管理器 -->
    <!--     <bean id="shiroCacheManager" class="com.thinkgem.jeesite.common.security.shiro.cache.SessionCacheManager" /> -->
        <bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
            <property name="cacheManager" ref="cacheManager"/>
        </bean>
        
        <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
        <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
        
        <!-- AOP式方法级权限检查  -->
        <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>
        
    </beans>

    这里从上往下进行解释:
    1.shiroFilterChainDefinitions

    可以看到类型是String,String内部的各个字符串是使用" "进行换行。这里的每一行代表了一个路由,而后面的anno,user等等,也就是相对应的Filter(这块我们是可以自己定义的,后面会讲,${adminPath} 是我在配置文件里面配置的路径而已,完全可以根据自己的路由进行设置。shiroFilterChainDefinitions最主要是在shiroFilter中作为一个参数注入。

    ===============权限过滤器及配置释义=======================

    anon   org.apache.shiro.web.filter.authc.AnonymousFilter
     
    authc  org.apache.shiro.web.filter.authc.FormAuthenticationFilter
     
    authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
     
    perms  org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
     
    port   org.apache.shiro.web.filter.authz.PortFilter
     
    rest   org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
     
    roles  org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
     
    ssl    org.apache.shiro.web.filter.authz.SslFilter
     
    user   org.apache.shiro.web.filter.authc.UserFilter
     
    logout org.apache.shiro.web.filter.authc.LogoutFilter

    anon:例子/admins/**=anon 没有参数,表示可以匿名使用。

    authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数

    roles:例子/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。

    perms:例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。

    rest:例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。

    port:例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString

    是你访问的url里的?后面的参数。

    authcBasic:例如/admins/user/**=authcBasic没有参数表示httpBasic认证

    ssl:例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https

    user:例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查

     

    2.重点来了:shiroFilter(ShiroFilterFactoryBean),这里要非常小心!! 这里的bean的名字一定要和web.xml里面的那个Filter名字相同,具体可以见下面的源码:

    DelegatingFilterProxy.java:
      @Override
        protected void initFilterBean() throws ServletException {
            synchronized (this.delegateMonitor) {
                if (this.delegate == null) {
                    // If no target bean name specified, use filter name.
                    if (this.targetBeanName == null) {
                        this.targetBeanName = getFilterName();
                    }
                    // Fetch Spring root application context and initialize the delegate early,
                    // if possible. If the root application context will be started after this
                    // filter proxy, we'll have to resort to lazy initialization.
                    WebApplicationContext wac = findWebApplicationContext();
                    if (wac != null) {
                        this.delegate = initDelegate(wac);
                    }
                }
            }
        }

    还记得我们web.xml里面配置的那个Filter吗, 其实我们配置的Filter只不过是起到一个代理的作用,那么它代理谁呢? 它也不能知道,它所能做的就是根据targetBeanName去容器中获取bean(这个bean是实现了Filter接口的),其中的targetBeanName就是bean的名称,如果没有设置的话,那么就默认使用的Filter名称。所以说前面说过的必须相同是不正确的,你只需要在Filter中设置targetBeanName和spring-context-shiro配置文件中ShiroFilterFactoryBean的bean名称一样即可。

    除了上面需要注意的几个点之外,ShiroFilterFactoryBean还有一些属性:unauthorizedUrl,系统未认证时跳转的页面,loginUrl登录页面,successUrl登录成功的页面,filter属性就是和前面的shiroFilterChainDefinitions对应的。同时支持自定义,并且配置路由:像<entry key="outdate" value-ref="sessionOutDateFilter"/>这样的。最底层是过滤器,下面是我实现的一个filter:

    package com.yonyou.kms.common.security.shiro.session;
    
    import java.io.PrintWriter;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.apache.shiro.web.servlet.AdviceFilter;
    
    import com.yonyou.kms.modules.sys.security.SystemAuthorizingRealm.Principal;
    import com.yonyou.kms.modules.sys.utils.UserUtils;
    
    /**
     * 
     * 自定义filter
     * @author Hotusm
     *
     */
    public class SessionOutDateFilter extends AdviceFilter{
        
        private String redirectUrl="http://url/portal";//session 失效之后需要跳转的页面
        private String platformUrl="http://url/kms/a/login";
        //排除这个链接 其他的链接都会进行拦截
        private String loginUrl="/kms/a/login";
        private String frontUrl="cms/f";
        private String uploadUrl="cms/article/plupload";
        private String appUrl="a/app";
        
        protected boolean preHandle(ServletRequest request, ServletResponse response){
            Principal principal = UserUtils.getPrincipal();
            HttpServletRequest req=(HttpServletRequest) request;
            String uri=req.getRequestURI();
            if(checkUrl(uri, loginUrl,frontUrl,uploadUrl,appUrl)|(principal!=null&&!principal.isMobileLogin())){
                
                return true;
            }
            
            try {
                issueRedirect(request,response,redirectUrl);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
        
        
          protected void issueRedirect(ServletRequest request, ServletResponse response, String redirectUrl)
             throws Exception
          {      
              
              String url="<a href="+redirectUrl+" target="_blank" onclick="custom_close()">重新登录<a/> ";
              String platform="<a href="+platformUrl+" target="_blank" onclick="custom_close()">直接登录<a/> ";
              
              HttpServletResponse resp=(HttpServletResponse) response;
              HttpServletRequest req=(HttpServletRequest) request;
              response.setContentType("text/html;charset=UTF-8");
              PrintWriter out=resp.getWriter();
              out.print("<script language='javascript'>");
              out.print("function custom_close(){" +
                          "self.opener=null;" +
                          "self.close();}");
              out.print("</script>");
              out.print("没有权限或者验证信息过期,请点击"+url+"登录portal<br/>");
              out.print("直接登录"+platform);
          }
    
    
          public String getRedirectUrl() {
            return redirectUrl;
        }
    
    
        public void setRedirectUrl(String redirectUrl) {
            this.redirectUrl = redirectUrl;
        }
    
    
        public String getLoginUrl() {
            return loginUrl;
        }
    
    
        public void setLoginUrl(String loginUrl) {
            this.loginUrl = loginUrl;
        }
        
        /**
         * 排除一些url不进行拦截
         * @param targetUrl
         * @param urls
         * @return
         */
        private boolean checkUrl(String targetUrl,String ...urls){
            for(int i=0;i<urls.length;i++){
                if(targetUrl.contains(urls[i])){
                    return true;
                }
            }
            
            return false;
        }
    }

    这个和springmvc的拦截器是相同的用法,返回true则表示验证通过(后面的逻辑继续执行),返回false就表示验证不通过。

    最后在shiroFilter的filters进行配置我们自定义的bean:

    <property name="filters">
                <map>
                    <entry key="outdate" value-ref="sessionOutDateFilter"/>
                </map>
            </property>
    这个sessionOutDateFilter我们需要注入(这里省略)。最后我们就将可以将这些东西加到shiroFilterChainDefinitions中去:
       <bean name="shiroFilterChainDefinitions" class="java.lang.String">
            <constructor-arg>
                <value>
                                  ......
                    ${adminPath}/** = outdate
                                  .....
                </value>
            </constructor-arg>
        </bean>        

    这样我们自己定义的叫做outdata的路由会拦截${adminPath}下的所以路径,并且进行验证。

    3.

    SecurityManager

    它和我们前面讲的ShiroFilterFactoryBean的关系形象的将就是ShiroFilterFactoryBean是一个路由规则配置仓库和代理类,其实真正的逻辑都是在SecurityManager中进行的,下面来进行详讲SecurityManager的依赖类。

    一:realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源,下面是我重写的realm:

    package com.yonyou.hotusm.module.sys.security;
    
    import com.yonyou.hotusm.common.utils.Encodes;
    import com.yonyou.hotusm.module.sys.dao.UserDao;
    import com.yonyou.hotusm.module.sys.entity.User;
    import com.yonyou.hotusm.module.sys.service.UserService;
    import com.yonyou.hotusm.module.sys.util.UserUtils;
    import org.apache.shiro.authc.*;
    import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.authz.UnauthenticatedException;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.util.ByteSource;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.io.Serializable;
    
    @Service("systemAuthorizingRealm")
    public class SystemAuthorizingRealm extends AuthorizingRealm implements InitializingBean{
        
        @Autowired
        private UserDao userDao;
    
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(
                PrincipalCollection principals) {
            SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
            info.addStringPermission("sys:manager");
            info.addStringPermission("user");
            System.out.println("开始授权");
            return info;
        }
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(
                AuthenticationToken token) throws AuthenticationException {
            UsernamePasswordToken upToken=(UsernamePasswordToken) token; 
            String username=upToken.getUsername();
            User user=new User();
            user.setLoginName(username);
            user=userDao.get(user);
            
            if(user!=null){
                byte[] salt = Encodes.decodeHex(user.getPassword().substring(0,16));
                return new SimpleAuthenticationInfo(username, 
                    user.getPassword().substring(16), ByteSource.Util.bytes(salt), getName());
            }else{
                throw new UnauthenticatedException();
            }
        }
    
        public static class Principal implements Serializable {
    
            private static final long serialVersionUID = 1L;
            
            private String id; // 编号
            private String loginName; // 登录名
            private String name; // 姓名
            
    
            public Principal(User user) {
                this.id = user.getId();
                this.loginName = user.getLoginName();
                this.name = user.getName();
            }
    
            public String getId() {
                return id;
            }
    
            public String getLoginName() {
                return loginName;
            }
    
            public String getName() {
                return name;
            }
    
    
            /**
             * 获取SESSIONID
             */
            public String getSessionid() {
                try{
                    return (String) UserUtils.getSession().getId();
                }catch (Exception e) {
                    return "";
                }
            }
            
            @Override
            public String toString() {
                return id;
            }
    
        }
        //在bean初始化完成以后  设置校验的规则
        public void afterPropertiesSet() throws Exception {
            HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(UserService.HASH_ALGORITHM);
            matcher.setHashIterations(UserService.HASH_INTERATIONS);
            setCredentialsMatcher(matcher);
            
        }
    
    }

    其他的几个类在这里不是重点,重要的是看这里面的逻辑。其中最重要的是doGetAuthorizationInfo和doGetAuthenticationInfo以及afterPropertiesSet这三个方法,doGetAuthorizationInfo是对当前的用户进行授权的,至于授权的时期,就是当用户需要验证的时候(框架进行回调),我这里只是简单的写死了,但是在实际项目开发中,我们一般会将权限存放在数据表中,所以真实情况是先到数据库中查出一个集合,然后迭代授权。

        doGetAuthenticationInfo对于的是对用户验证,主要的一个点在于我们最后返回的那个SimpleAuthenticationInfo,这个是加密的策略,这里的密码是密文的(根据loginName数据中取得),下面是密码的加密策略:

    //为明文密码加密
        public String encryptionPassword(String plainPassword){
            byte[] salt = Digests.generateSalt(SALT_SIZE);  //SALT_SIZE=8
            byte[] hashPassword = Digests.sha1(plainPassword.getBytes(), salt, HASH_INTERATIONS);  //HASH_INTERATIONS=1024
    return Encodes.encodeHex(salt)+Encodes.encodeHex(hashPassword); }

    我这里是生成了了16位的salt,然后用来加密明文,最后两个加起来存入到数据中。根据上面说的,所以看到我们doGetAuthenticationInfo返回的是分开的两部分。这里也需要注意,这个密码最后的校验我们做的,而是框架!我们只是提供了校验类供它回调(下面我们使用的是默认的校验类,我们也可以自定义):

      //在bean初始化完成以后  设置校验的规则
        public void afterPropertiesSet() throws Exception {
            HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(UserService.HASH_ALGORITHM);
            matcher.setHashIterations(UserService.HASH_INTERATIONS);
            setCredentialsMatcher(matcher);

    ,那么在密码进行验证的时候,就会调用HashedCredentialsMatcher的

    @Override
        public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
            Object tokenHashedCredentials = hashProvidedCredentials(token, info);
            Object accountCredentials = getCredentials(info);
            return equals(tokenHashedCredentials, accountCredentials);
        }

    方法,这个Info就是我们前面方法doGetAuthenticationInfo提供的,至于另外的一个Token,后面会讲(也是一个方法提供的)。

    4

    下面就是讲解SessionManager,因为Shiro有自己的一套session体系,有sessionManager就不奇怪了,sessionManager主要职责是管理session的创建和删除,特别提一下,sessionManager对session的操作,其实只是调用了sessionDAO,然再加上自己的一些操作。

    看源码:

    
    
    public class DefaultSessionManager extends AbstractValidatingSessionManager implements CacheManagerAware {
    
        //TODO - complete JavaDoc
    
        private static final Logger log = LoggerFactory.getLogger(DefaultSessionManager.class);
    
        private SessionFactory sessionFactory;
    
        protected SessionDAO sessionDAO;  //todo - move SessionDAO up to AbstractValidatingSessionManager?
    
        private CacheManager cacheManager;
    
        private boolean deleteInvalidSessions;
    
        public DefaultSessionManager() {
            this.deleteInvalidSessions = true;
            this.sessionFactory = new SimpleSessionFactory();
            this.sessionDAO = new MemorySessionDAO();
        }
    .......
      protected void create(Session session) {
        if (log.isDebugEnabled()) {
            log.debug("Creating new EIS record for new session instance [" + session + "]");
        }
        sessionDAO.create(session);
      }
    
    

    这上面的源码中就知道SessionManager就是对SessionDAO进行了代理的作用。

    我们就明白了sessionManager依赖sessionDAO(后面实现自己的SessionDAO需要注入到SessionManager中),下面是自己实现的sessionManager:

    
    
    package com.yonyou.hotusm.common.security.session;
    
    import org.apache.shiro.session.InvalidSessionException;
    import org.apache.shiro.session.Session;
    import org.apache.shiro.session.UnknownSessionException;
    import org.apache.shiro.session.mgt.SessionContext;
    import org.apache.shiro.session.mgt.SessionKey;
    import org.apache.shiro.session.mgt.SimpleSession;
    import org.apache.shiro.web.servlet.Cookie;
    import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
    import org.apache.shiro.web.servlet.SimpleCookie;
    import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
    import org.apache.shiro.web.util.WebUtils;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.Serializable;
    import java.util.Collection;
    import java.util.Date;
    
    /***
     * 
     * @author Hotusm
     *    v2015-11-04
     */
    public class SessionManager extends DefaultWebSessionManager{
    
        /*
         *DefaultWebSessionManager 实现了DefaultSessionManager的功能 并在其上实现了web的功能
         * 也就是在上面实现了将SessionId 存到了Cookie中 
         * */
        @Override
        protected Serializable getSessionId(ServletRequest request,
                ServletResponse response) {
            
           
            String sid=request.getParameter("_sid");
            if(org.apache.commons.lang.StringUtils.isNotBlank(sid)){
                if(WebUtils.isTrue(request, "_cookie")){
                    HttpServletRequest req=(HttpServletRequest) request;
                    HttpServletResponse resp=(HttpServletResponse) response;
                    Cookie template=getSessionIdCookie();
                    Cookie cookie=new SimpleCookie(template);
                    cookie.setValue(sid);
                    cookie.saveTo(req, resp);
                }
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,ShiroHttpServletRequest.URL_SESSION_ID_SOURCE);
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sid);
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
                return sid;
            }
            
            return super.getSessionId(request, response);
        }
    
        @Override
        protected Session doCreateSession(SessionContext context) {
            try {
                return super.doCreateSession(context);
            } catch (Exception e) {
                return null;
            }
            
        }
    
        @Override
        protected Session newSessionInstance(SessionContext context) {
            
            Session session=super.newSessionInstance(context);
            session.setTimeout(getGlobalSessionTimeout());
            return session;
        }
    
        @Override
        protected Session retrieveSession(SessionKey sessionKey)
                throws UnknownSessionException {
            
            try {
                return super.retrieveSession(sessionKey);
            } catch (Exception e) {
                //获取不到SESSION不报错
                return null;
            }
            
        }
    
        @Override
        public void validateSessions() {
            super.validateSessions();
        }
    
        @Override
        public Session start(SessionContext context) {
            try {
                return super.start(context);
            } catch (Exception e) {
                SimpleSession session=new SimpleSession();
                session.setId(0);
                return session;
            }
        }
    
        @Override
        public Date getStartTimestamp(SessionKey key) {
            try {
                return super.getStartTimestamp(key);
            } catch (Exception e) {
                return null;
            }
            
        }
    
        @Override
        public Date getLastAccessTime(SessionKey key) {
            try {
                return super.getLastAccessTime(key);
            } catch (Exception e) {
                return null;
            }
        }
    
        @Override
        public long getTimeout(SessionKey key) throws InvalidSessionException {
            try {
                return super.getTimeout(key);
            } catch (Exception e) {
                return 0;
            }
            
        }
    
        @Override
        public void setTimeout(SessionKey key, long maxIdleTimeInMillis)
                throws InvalidSessionException {
            try {
                super.setTimeout(key, maxIdleTimeInMillis);
            } catch (Exception e) {
                
            }
        }
    
        @Override
        public void touch(SessionKey key) throws InvalidSessionException {
            try {
                super.touch(key);
            } catch (Exception e) {
            }
            
        }
    
        @Override
        public String getHost(SessionKey key) {
            try {
                return super.getHost(key);
            } catch (Exception e) {
                return null;
            }
            
        }
    
        @Override
        public Collection<Object> getAttributeKeys(SessionKey key) {
            try {
                return super.getAttributeKeys(key);
            } catch (Exception e) {
                return null;
            }
            
        }
    
        @Override
        public Object getAttribute(SessionKey sessionKey, Object attributeKey)
                throws InvalidSessionException {
            try {
                return super.getAttribute(sessionKey, attributeKey);
            } catch (Exception e) {
                return null;
            }
            
        }
    
        @Override
        public Object removeAttribute(SessionKey sessionKey, Object attributeKey)
                throws InvalidSessionException {
            try {
                return super.removeAttribute(sessionKey, attributeKey);
            } catch (Exception e) {
                return null;
            }
    //        
        }
    
        @Override
        public void stop(SessionKey key) throws InvalidSessionException {
            try {
                super.stop(key);
            } catch (Exception e) {
                
            }
            
        }
    
        @Override
        public void checkValid(SessionKey key) throws InvalidSessionException {
            try {
                super.checkValid(key);
            } catch (Exception e) {
            }
        } 
    }
    
    

    上面就是对session的操作.

    5

    还有就是sessionDAO了,这个sessionDAO才是真正对session操作的bean:

    
    
    package com.yonyou.hotusm.common.security.session;
    
    import com.google.common.collect.Sets;
    import com.yonyou.hotusm.common.config.Global;
    import com.yonyou.hotusm.common.web.Servlets;
    import org.apache.shiro.session.Session;
    import org.apache.shiro.session.UnknownSessionException;
    import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
    
    import javax.servlet.http.HttpServletRequest;
    import java.io.Serializable;
    import java.util.Collection;
    import java.util.Set;
    
    /**
     * @author Hotusm
     *         v-2015-10-28
     */
    public class CacheSessionDAO extends EnterpriseCacheSessionDAO implements SessionDAO {
    
    
        @Override
        protected Serializable doCreate(Session session) {
            HttpServletRequest request = Servlets.getRequest();
            if (request != null) {
                String uri = request.getRequestURI();
                if (Servlets.isStaticFile(uri)) {
                    return null;
                }
            }
            super.doCreate(session);
            //System.out.println("doCreate:"+" sessionId"+session.getId());
            return session.getId();
        }
    
        @Override
        public Session readSession(Serializable sessionId) throws UnknownSessionException {
            //System.out.println("readSession:"+" sessionId"+sessionId);
            //System.out.println();
            try {
                Session s = null;
                HttpServletRequest request = Servlets.getRequest();
                if (request != null) {
                    String uri = request.getRequestURI();
                    if (Servlets.isStaticFile(uri)) {
                        return null;
                    }
                    s = (Session) request.getAttribute("session_" + sessionId);
                }
    
                if (s != null) {
                    return s;
                }
                Session session = super.readSession(sessionId);
                if (request != null && session != null) {
                    request.setAttribute("session_" + sessionId, session);
                }
                return session;
            } catch (Exception e) {
    
                return null;
            }
    
        }
    
        @Override
        protected Session doReadSession(Serializable sessionId) {
            //System.out.println("doReadSession:"+" sessionId"+sessionId);
            return super.doReadSession(sessionId);
        }
    
    
        @Override
        protected void doUpdate(Session session) {
    //        System.out.println("doUpdate"+" sessionId"+session.getId());
            if (session == null || session.getId() == null) {
                return;
            }
            HttpServletRequest request = Servlets.getRequest();
            if (request != null) {
                String uri = request.getRequestURI();
                if (Servlets.isStaticFile(uri)) {
                    return;
                }
                if (org.apache.commons.lang.StringUtils.startsWith(uri, Global.getConfig("web.view.prefix"))
                        && org.apache.commons.lang.StringUtils.endsWith(uri, Global.getConfig("web.view.suffix"))) {
                    return;
                }
                //手动控制不更新session
                String updateSession = request.getParameter("updateSession");
                if (Global.FALSE.equals(updateSession) || Global.NO.equals(updateSession)) {
                    return;
                }
            }
            super.doUpdate(session);
        }
    
        @Override
        protected void doDelete(Session session) {
            //System.out.println("doDelete");
            if (session == null || session.getId() == null) {
                return;
            }
            super.doUpdate(session);
        }
    
    
        public Collection<Session> getActiveSessions(boolean includeLeave) {
    
            return null;
        }
    
        public Collection<Session> getActiveSessions(boolean includeLeave,
                                                     Object principal, Session filterSession) {
    
            if (includeLeave && principal == null) {
                return this.getActiveSessions();
            }
            Set<Session> sessions = Sets.newHashSet();
            for (Session session : getActiveSessions()) {
                boolean isActiveSession = true;
    
            }
            return null;
        }
    
    }
    
    

    6.

    看sessionDAO还有一个idGen依赖bean,指的是sessionId值的生成策略,这个bean也是自己定义的,但是需要继承SessionIdGenerator:

    public class IdGen implements SessionIdGenerator{
        
        private static SecureRandom secureRandom;
        
        /**
         * 封装JDK自带的UUID,通过random生成
         */
        public static String uuid(){
            return UUID.randomUUID().toString().replace("-", "");
        }
        
        public static long randomLong(){
            return Math.abs(secureRandom.nextLong());
        }
    
        public Serializable generateId(Session session) {
            return IdGen.uuid();
        }
        
    
    }

    返回的就是session的值,至于shiroCacheManager就是session缓存的储存位置(它依赖的是我们在spring-context定义的cacheManager)。

    3.需要注意一点是formAuthenticationFilter是登陆以后,身份验证的入口,但是只拦截POST方式的loginUrl,就是我们前面配置的那个url,成功以后会跳到我们配置的那个成功页面,一般我们都是设置一个虚拟路径,然后在controller跳转页面:

    /**
         * 登录成功,进入管理首页
         */
        @RequiresPermissions("user")
        @RequestMapping(value = "${adminPath}")
        public String index(HttpServletRequest request, HttpServletResponse response) {
            Principal principal = UserUtils.getPrincipal();
            List<String> str=commentService.commentList(null);
            //System.out.println(JsonMapper.toJsonString(str));
            // 登录成功后,验证码计算器清零
            isValidateCodeLogin(principal.getLoginName(), false, true);
            
            if (logger.isDebugEnabled()){
                
                logger.debug("show index, active session size: {}", sessionDAO.getActiveSessions(false).size());
            }
            
            // 如果已登录,再次访问主页,则退出原账号。
            if (Global.TRUE.equals(Global.getConfig("notAllowRefreshIndex"))){
                
                String logined = CookieUtils.getCookie(request, "LOGINED");
                if (org.apache.commons.lang3.StringUtils.isBlank(logined) || "false".equals(logined)){
                    
                    CookieUtils.setCookie(response, "LOGINED", "true");
                }else if (org.apache.commons.lang3.StringUtils.equals(logined, "true")){
                    UserUtils.getSubject().logout();
                    
                    return "redirect:" + adminPath + "/login";
                }
            }
    /
            return "modules/sys/sysIndex";
        }

    下面是authc对应的那个filter的代码,

    
    
    @Service
    public class FormAuthenticationFilter extends org.apache.shiro.web.filter.authc.FormAuthenticationFilter {
    
        public static final String DEFAULT_CAPTCHA_PARAM = "validateCode";
        public static final String DEFAULT_MOBILE_PARAM = "mobileLogin";
        public static final String DEFAULT_MESSAGE_PARAM = "message";
    
        private String captchaParam = DEFAULT_CAPTCHA_PARAM;
        private String mobileLoginParam = DEFAULT_MOBILE_PARAM;
        private String messageParam = DEFAULT_MESSAGE_PARAM;
        
    
        @Override
        protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
            
            String username = getUsername(request);
            String password = getPassword(request);boolean rememberMe = isRememberMe(request);
    
            String host = StringUtils.getRemoteAddr((HttpServletRequest)request);
            boolean mobile = isMobileLogin(request);
            return new UsernamePasswordToken(username, password.toCharArray(), rememberMe, host, mobile);
            //end
        }
    
        public String getCaptchaParam() {
            return captchaParam;
        }
    
        protected String getCaptcha(ServletRequest request) {
            return WebUtils.getCleanParam(request, getCaptchaParam());
        }
    
        public String getMobileLoginParam() {
            return mobileLoginParam;
        }
        
        protected boolean isMobileLogin(ServletRequest request) {
            return WebUtils.isTrue(request, getMobileLoginParam());
        }
        
        public String getMessageParam() {
            return messageParam;
        }
        
        /**
         * 登录成功之后跳转URL
         */
        @Override
        public String getSuccessUrl() {
            return super.getSuccessUrl();
        }
        
        @Override
        protected void issueSuccessRedirect(ServletRequest request,
                ServletResponse response) throws Exception {
    //        Principal p = UserUtils.getPrincipal();
    //        if (p != null && !p.isMobileLogin()){
                 WebUtils.issueRedirect(request, response, getSuccessUrl(), null, true);
    //        }else{
    //            super.issueSuccessRedirect(request, response);
    //        }
        }
    
        /**
         * 登录失败调用事件
         */
        @Override
        protected boolean onLoginFailure(AuthenticationToken token,
                AuthenticationException e, ServletRequest request, ServletResponse response) {
            String className = e.getClass().getName(), message = "";
            if (IncorrectCredentialsException.class.getName().equals(className)
                    || UnknownAccountException.class.getName().equals(className)){
                message = "用户或密码错误, 请重试.";
            }
            else if (e.getMessage() != null && org.apache.commons.lang3.StringUtils.startsWith(e.getMessage(), "msg:")){
                message = org.apache.commons.lang3.StringUtils.replace(e.getMessage(), "msg:", "");
            }
            else{
                message = "系统出现点问题,请稍后再试!";
                e.printStackTrace(); // 输出到控制台
            }
            request.setAttribute(getFailureKeyAttribute(), className);
            request.setAttribute(getMessageParam(), message);
            return true;
        }
        
    }
    
    

    这里的Token就是我们前面所讲的Info一起来做明文和密文进行校验的。

     

    经过上面的一些操作,shiro登录和授权就可以做好了,对于退出,我们只要设置退出按钮的链接地址是我们前面filterChainDefinitions配置的路径就可以了,我的是: ${adminPath}/logout = logout;

    具体的代码在github:https://github.com/Housum/blog.git 有 

     
  • 相关阅读:
    django ajax报错解决:You called this URL via POST, but the URL doesn't end in a slash and you have APPEND_SLASH set.
    django使用session报错:no such table: django_session
    centos7下yum安装mariadb
    pip报错解决:EnvironmentError: mysql_config not found
    django报错解决:view must be a callable or a list/tuple in the case of include().
    onpageshow和onpagehide
    深入理解Linux的CPU上下文切换
    看完就彻底懂了红黑树!红黑树的插入、删除、左旋、右旋
    shell根据csv生成sql
    shell中的EOF用法
  • 原文地址:https://www.cnblogs.com/cnblog-long/p/7646989.html
Copyright © 2011-2022 走看看