精华内容
下载资源
问答
  • 2019-05-14 17:01:01

    web和APP可以用shiro统一登录认证吗?

    https://www.cnblogs.com/sunshine-2015/p/5515429.html

    更多相关内容
  • 先说下背景,项目包含一个管理系统(web)和门户网站(web),还有一个手机APP(包括Android和IOS),三个系统共用一个后端,在后端使用shiro进行登录认证和权限控制。好的,那么问题来了:先说web端1.因为一般网页...

    先说下背景,项目包含一个管理系统(web)和门户网站(web),还有一个手机APP(包括Android和IOS),三个系统共用一个后端,在后端使用shiro进行登录认证和权限控制。好的,那么问题来了

    先说web端

    1.因为一般网页主需要记住7天密码(或者稍微更长)的功能就可以了,可以使用cookie实现,而且shiro也提供了记住密码的功能,在服务器端session不需要保存过长时间。

    再说app端

    2.因为APP免密码登录时间需要较长(在用户不主动退出的时候,应该一直保持登录状态),这样子在服务器端就得把session保存很长时间,给服务器内存和性能上造成较大的挑战,存在的矛盾是:APP需要较长时间的免密码登录,而服务器不能保存过长时间的session。

     解决办法:

    • APP第一次登录,使用用户名和密码,如果登录成功,将cookie保存在APP本地(比如sharepreference),后台将cookie值保存到user表里面
    • APP访问服务器,APP将cookie添加在heade里面,服务器session依然存在,可以正常访问
    • APP访问服务器,APP将cookie添加在heade里面,服务器session过期,访问失败,由APP自动带着保存在本地的cookie去服务器登录,服务器可以根据cookie和用户名进行登录,这样服务器又有session,会生成新的cookie返回给APP,APP更新本地cookie,又可以正常访问
    • 用户手动退出APP,删除APP本次存储的cookie,下次登录使用用户名和密码登录

    这种方法存在的问题:

    1. cookie保存在APP本地,安全性较低,可以通过加密cookie增加安全性
    2. 每次服务器session失效之后,得由APP再次发起登录请求(虽然用户是不知道的),但是这样本身就会增加访问次数,好在请求数量并不是很大,不过这种方式会使cookie经常更新,反而增加了安全性。

    这里给出另外一种实现方式:
    实现自己的SessionDao,将session保存在数据库,这样子的好处是,session不会大量堆积在内存中,就不需要考虑session的过期时间了,对于APP这种需要长期保存session的情况来说,就可以无限期的保存session了,也就不用APP在每次session过期之后重新发送登录请求了。实现方式如下:

    1. 数据库设计 (需要的话自己扩展)

                           

    2. 实体类
    import java.io.Serializable;
    import java.util.Date;
    
    
    
    /**
     * 用户session
     * 
     * @author flyingTiger
     * @email gaofeihu95@163.com
     * @date 2018-03-05 15:44:08
     */
    public class SysUserSessionEntity implements Serializable {
    	private static final long serialVersionUID = 1L;
    	
    	//id
    	private String id;
    	//session
    	private String session;
    	//cookie
    	private String cookie;
    	//user_id
    	private Long userId;
    	//创建时间
    	private Date createTime;
    	//最后更新时间
    	private Date lastUpTime;
    	//状态
    	private String status;
    
    	/**
    	 * 设置:id
    	 */
    	public void setId(String id) {
    		this.id = id;
    	}
    	/**
    	 * 获取:id
    	 */
    	public String getId() {
    		return id;
    	}
    	/**
    	 * 设置:session
    	 */
    	public void setSession(String session) {
    		this.session = session;
    	}
    	/**
    	 * 获取:session
    	 */
    	public String getSession() {
    		return session;
    	}
    	/**
    	 * 设置:cookie
    	 */
    	public void setCookie(String cookie) {
    		this.cookie = cookie;
    	}
    	/**
    	 * 获取:cookie
    	 */
    	public String getCookie() {
    		return cookie;
    	}
    	/**
    	 * 设置:user_id
    	 */
    	public void setUserId(Long userId) {
    		this.userId = userId;
    	}
    	/**
    	 * 获取:user_id
    	 */
    	public Long getUserId() {
    		return userId;
    	}
    	/**
    	 * 设置:创建时间
    	 */
    	public void setCreateTime(Date createTime) {
    		this.createTime = createTime;
    	}
    	/**
    	 * 获取:创建时间
    	 */
    	public Date getCreateTime() {
    		return createTime;
    	}
    	/**
    	 * 设置:最后更新时间
    	 */
    	public void setLastUpTime(Date lastUpTime) {
    		this.lastUpTime = lastUpTime;
    	}
    	/**
    	 * 获取:最后更新时间
    	 */
    	public Date getLastUpTime() {
    		return lastUpTime;
    	}
    	/**
    	 * 设置:状态
    	 */
    	public void setStatus(String status) {
    		this.status = status;
    	}
    	/**
    	 * 获取:状态
    	 */
    	public String getStatus() {
    		return status;
    	}
    }
    

    3. 在此之前我已经实现过sessionDao ,是将session放到redis里面

    import org.apache.shiro.session.Session;
    import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Component;
    
    import java.io.Serializable;
    import java.util.concurrent.TimeUnit;
    
    /**
     * shiro session dao
     *
     * @author flyingTiger
     * @email gaofeihu95@163.com
     * @date  2017/9/27 21:35
     */
    @Component
    public class RedisShiroSessionDAO extends EnterpriseCacheSessionDAO {
        @Autowired
        private RedisTemplate redisTemplate;
    
        //创建session
        @Override
        protected Serializable doCreate(Session session) {
            Serializable sessionId = super.doCreate(session);
            final String key = RedisKeys.getShiroSessionKey(sessionId.toString());
            setShiroSession(key, session);
            return sessionId;
        }
    
        //获取session
        @Override
        protected Session doReadSession(Serializable sessionId) {
            Session session = super.doReadSession(sessionId);
            if(session == null){
                final String key = RedisKeys.getShiroSessionKey(sessionId.toString());
                session = getShiroSession(key);
            }
            return session;
        }
    
        //更新session
        @Override
        protected void doUpdate(Session session) {
            super.doUpdate(session);
            final String key = RedisKeys.getShiroSessionKey(session.getId().toString());
            setShiroSession(key, session);
        }
    
        //删除session
        @Override
        protected void doDelete(Session session) {
            super.doDelete(session);
            final String key = RedisKeys.getShiroSessionKey(session.getId().toString());
            redisTemplate.delete(key);
        }
    
        private Session getShiroSession(String key) {
            return (Session)redisTemplate.opsForValue().get(key);
        }
    
        private void setShiroSession(String key, Session session){
            redisTemplate.opsForValue().set(key, session);
            //60分钟过期
            redisTemplate.expire(key, 60, TimeUnit.MINUTES);
        }
    
    }
    
    4. 那么现在我即要实现将session写入到数据库中,又要将sesion存储到redis里面。我还不想大量修改redisDao的代码,我该怎么做呢?
    答案就是,
        继承,
          1. 首先自己重新定义一个SessionDao比如叫做DataBaseSessionDao.

          2. 然后让redisDao继承DataBaseDao

    DataBaseSessionDao的代码如下

    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.session.Session;
    import org.apache.shiro.session.mgt.ValidatingSession;
    import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import java.io.Serializable;
    import java.util.HashMap;
    
    /**
     * 存放session 到数据库
     *
     * @author FlyingTiger
     * @version 1.0
     * @since 2018/03/05 14:52
     */
    @Component
    public class DataBaseSessionDao extends EnterpriseCacheSessionDAO {
        public static final String COOKIE = "cookie";
        // 此处虽然不符合三层设计,但有效避免sql注入和减少了更新的维护管理,是合理的。 at FlyingTiger by 2018年3月5日
        @Autowired
        private SysUserSessionService sysUserSessionService;
    
    
        //创建session
        @Override
        protected Serializable doCreate(Session session) {
            Serializable cookie = super.doCreate(session);
            // 保存session到数据库
            SysUserSessionEntity sysUserSession = new SysUserSessionEntity();
            sysUserSession.setCookie(cookie.toString());
            sysUserSession.setSession(SerializableUtils.serializ(session));
            sysUserSessionService.save(sysUserSession);
            return cookie;
        }
    
        //获取session
        @Override
        protected Session doReadSession(Serializable sessionId) {
            Session session = super.doReadSession(sessionId);
            if (session == null) {
                //final String key = RedisKeys.getShiroSessionKey(sessionId.toString());
                HashMap<String, Object> param = new HashMap<>();
                param.put(COOKIE, sessionId);
                SysUserSessionEntity sysUserSessionEntity = sysUserSessionService.queryObjectByMap(param);
                // 如果不为空
                if (sysUserSessionEntity != null) {
                    String sessionStr64 = sysUserSessionEntity.getSession();
                    session = SerializableUtils.deserializ(sessionStr64);
                }
    
            }
            return session;
        }
    
        //更新session
        @Override
        protected void doUpdate(Session session) {
            super.doUpdate(session);
            //当是ValidatingSession 无效的情况下,直接退出
            if (session instanceof ValidatingSession &&
                    !((ValidatingSession) session).isValid()) {
                return;
            }
            //检索到用户名
            // String username = String.valueOf(session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY));
            HashMap<String, Object> param = new HashMap<>();
            param.put(COOKIE, session.getId());
            SysUserSessionEntity sysUserSessionEntity = sysUserSessionService.queryObjectByMap(param);
            sysUserSessionEntity.setSession(SerializableUtils.serializ(session));
            // 如果登录成功,更新用户id
            if (ShiroUtils.getSubject().isAuthenticated()){
                SysUserEntity sysUser = (SysUserEntity) SecurityUtils.getSubject().getPrincipal();
                sysUserSessionEntity.setUserId(sysUser.getUserId());
            }
    
            sysUserSessionService.update(sysUserSessionEntity);
        }
    
        //删除session
        @Override
        protected void doDelete(Session session) {
            super.doDelete(session);
            HashMap<String, Object> param = new HashMap<>();
            param.put(COOKIE, session.getId());
            SysUserSessionEntity sysUserSessionEntity = sysUserSessionService.queryObjectByMap(param);
            if (sysUserSessionEntity != null) {
                sysUserSessionService.delete(sysUserSessionEntity.getId());
            }
        }
    然后让redisSessionDao 继承 DataBaseSessionDao (除了继承关系,其他都不变 序列化类参考标题7):
    import org.apache.shiro.session.Session;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Component;
    
    import java.io.Serializable;
    import java.util.concurrent.TimeUnit;
    
    /**
     * shiro session dao
     *
     * @author flyingTiger
     * @email gaofeihu95@163.com
     * @date  2017/9/27 21:35
     */
    @Component
    public class RedisShiroSessionDAO extends DataBaseSessionDao {
        @Autowired
        private RedisTemplate redisTemplate;
    
        //创建session
        @Override
        protected Serializable doCreate(Session session) {
            Serializable sessionId = super.doCreate(session);
            final String key = RedisKeys.getShiroSessionKey(sessionId.toString());
            setShiroSession(key, session);
            return sessionId;
        }
    
        //获取session
        @Override
        protected Session doReadSession(Serializable sessionId) {
            Session session = super.doReadSession(sessionId);
            if(session == null){
                final String key = RedisKeys.getShiroSessionKey(sessionId.toString());
                session = getShiroSession(key);
            }
            return session;
        }
    
        //更新session
        @Override
        protected void doUpdate(Session session) {
            super.doUpdate(session);
            final String key = RedisKeys.getShiroSessionKey(session.getId().toString());
            setShiroSession(key, session);
        }
    
        //删除session
        @Override
        protected void doDelete(Session session) {
            super.doDelete(session);
            final String key = RedisKeys.getShiroSessionKey(session.getId().toString());
            redisTemplate.delete(key);
        }
    
        private Session getShiroSession(String key) {
            return (Session)redisTemplate.opsForValue().get(key);
        }
    
        private void setShiroSession(String key, Session session){
            redisTemplate.opsForValue().set(key, session);
            //60分钟过期
            redisTemplate.expire(key, 60, TimeUnit.MINUTES);
        }
    
    }
    
    5.  这里用到的是shiro bean  配置 (配置文件请参考标题6)

    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.session.mgt.SessionManager;
    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.DefaultWebSecurityManager;
    import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
    import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    /**
     * Shiro的配置文件
     *
     * @author flyingTiger
     * @email gaofeihu95@163.com
     * @date 2017/9/27 22:02
     */
    @Configuration
    public class ShiroConfig {
    
        @Bean("sessionManager")
        public SessionManager sessionManager(RedisShiroSessionDAO redisShiroSessionDAO, DataBaseSessionDao dataBaseSessionDao, @Value("${renren.redis.open}") boolean redisOpen,
                                             @Value("${renren.shiro.redis}") boolean shiroRedis) {
            DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
            //设置session过期时间为1小时(单位:毫秒),默认为30分钟
            sessionManager.setGlobalSessionTimeout(60 * 60 * 1000);
            sessionManager.setSessionValidationSchedulerEnabled(true);
            sessionManager.setSessionIdUrlRewritingEnabled(false);
    
            //如果开启redis缓存且shiro.redis=true,则shiro session存到redis里
            if (redisOpen && shiroRedis) {
                sessionManager.setSessionDAO(redisShiroSessionDAO);
            } else {
                // 将session保存到数据库
                sessionManager.setSessionDAO(dataBaseSessionDao);
            }
            return sessionManager;
        }
    
    }

    6. 配置spring-shiro.xml配置文件(如果不是用bean配置)

    注意这个sessionDao 里面配置了activeSessionsCacheName 这个属性,这个在ecache.xml里面必须也配置一个shiro-activeSessionCache节点,用于存激活的session,简单来讲,就是登录的用户。

    <!-- 给予shior的内存缓存系统 ,这个是shiro的缓存服务-->
        <!-- <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />  
         --> 
    
        <!--  
             配置 CacheManager. 
        -->     
        <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
            <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> 
        </bean>
    
    
        <!-- 会话Session ID生成器 -->
        <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>
    
        <!-- 创建SessionDao  这个SessionDao继承了 EnterpriseCacheSessionDAO-->
        <bean id="sessionDao" class="com.yellowcong.shiro.dao.SessionDao">
            <!-- 配置SessionDao里面的 id生成器 -->
            <property name="sessionIdGenerator" ref="sessionIdGenerator"/>
            <!-- 设置Session缓存到Eacehc的名字,默认就是shiro-activeSessionCache -->
            <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>
        </bean>
    
        <!-- 配置Session管理 -->           
        <!-- org.apache.shiro.session.mgt.DefaultSessionManager
        DefaultWebSessionManager 继承了 DefaultSessionManager 这个类
         -->
        <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
            <!-- session超时 30 分钟 -->
            <property name="globalSessionTimeout" value="1800000"/>
    
            <property name="deleteInvalidSessions" value="true"/>
    
            <!-- Session调度器,用来检查Session是否还存在的问题 -->
            <property name="sessionValidationSchedulerEnabled" value="true"/>
            <!--  设定session的调度管理器-->
            <property name="sessionValidationScheduler" ref="sessionValidationScheduler" />
            <!-- 配置管理session的 dao -->
            <property name="sessionDAO" ref="sessionDao"/>
    
            <!-- 开启cookie 不然一直登录不上 -->
            <property name="sessionIdCookieEnabled" value="true"/>
            <property name="sessionIdCookie" ref="sessionIdCookie"/>
        </bean>
    
        <!-- 全局的会话信息检测扫描信息间隔30分钟 
             设定检查sesion过期事件,
        -->
        <bean id="sessionValidationScheduler"
            class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">
            <property name="sessionValidationInterval" value="1800000" />
            <property name="sessionManager" ref="sessionManager" />
        </bean>
    
        <!-- 安全管理器 -->    
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
            <!-- 缓存管理器 -->
            <property name="cacheManager" ref="cacheManager" />  
    
            <!-- 验证 -->        
            <property name="authenticator" ref="authenticator"/>
    
            <!-- 多个验证策略 realmes -->
            <property name="realms">
                <list>
                    <!-- 这个认证,有一个先后的顺序 -->
                    <ref bean="sampleRealm1"/>
                    <ref bean="sampleRealm2"/>
                </list>
            </property>
    
            <!-- 配置SessionManager -->
          <property name="sessionManager" ref="sessionManager"/> 
        </bean> 

    下面是完整配置

    其中还有一部分是关于Shiro生命周期的,存储在了Spring-mvc中,因为生命周期配置在spring-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:aop="http://www.springframework.org/schema/aop"
        xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
        <description>== Shiro Components ==</description>
    
        <!-- 给予shior的内存缓存系统 ,这个是shiro的缓存服务-->
        <!-- <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />  
         --> 
    
        <!--  
             配置 CacheManager. 
        -->     
        <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
            <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> 
        </bean>
    
    
        <!-- 会话Session ID生成器 -->
        <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>
    
        <!-- 创建SessionDao  这个SessionDao继承了 EnterpriseCacheSessionDAO-->
        <bean id="sessionDao" class="com.yellowcong.shiro.dao.SessionDao">
            <!-- 配置SessionDao里面的 id生成器 -->
            <property name="sessionIdGenerator" ref="sessionIdGenerator"/>
            <!-- 设置Session缓存到Eacehc的名字,默认就是shiro-activeSessionCache -->
            <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>
        </bean>
    
        <!-- 配置Session管理 -->           
        <!-- org.apache.shiro.session.mgt.DefaultSessionManager
        DefaultWebSessionManager 继承了 DefaultSessionManager 这个类
         -->
        <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
            <!-- session超时 30 分钟 -->
            <property name="globalSessionTimeout" value="1800000"/>
    
            <property name="deleteInvalidSessions" value="true"/>
    
            <!-- Session调度器,用来检查Session是否还存在的问题 -->
            <property name="sessionValidationSchedulerEnabled" value="true"/>
            <!--  设定session的调度管理器-->
            <property name="sessionValidationScheduler" ref="sessionValidationScheduler" />
            <!-- 配置管理session的 dao -->
            <property name="sessionDAO" ref="sessionDao"/>
    
            <!-- 开启cookie 不然一直登录不上 -->
            <property name="sessionIdCookieEnabled" value="true"/>
            <property name="sessionIdCookie" ref="sessionIdCookie"/>
        </bean>
    
        <!-- 全局的会话信息检测扫描信息间隔30分钟 
             设定检查sesion过期事件,
        -->
        <bean id="sessionValidationScheduler"
            class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">
            <property name="sessionValidationInterval" value="1800000" />
            <property name="sessionManager" ref="sessionManager" />
        </bean>
    
        <!-- 安全管理器 -->    
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
            <!-- 缓存管理器 -->
            <property name="cacheManager" ref="cacheManager" />  
    
            <!-- 验证 -->        
            <property name="authenticator" ref="authenticator"/>
    
            <!-- 多个验证策略 realmes -->
            <property name="realms">
                <list>
                    <!-- 这个认证,有一个先后的顺序 -->
                    <ref bean="sampleRealm1"/>
                    <ref bean="sampleRealm2"/>
                </list>
            </property>
    
            <!-- 配置SessionManager -->
          <property name="sessionManager" ref="sessionManager"/> 
        </bean>  
    
    
        <!-- 会话Cookie模板 -->
        <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
            <constructor-arg value="sid"/>
            <property name="httpOnly" value="true"/>
            <property name="maxAge" value="-1"/>
        </bean>
    
        <!-- 授权策略 -->
        <bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
            <property name="authenticationStrategy" >
                <!-- 所有Reaml都全部匹配的策略 -->
                <!-- <bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"/> -->
                <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"/>
            </property>
        </bean>
    
        <!-- 授权 认证 ,自己定义的,领域(Realm),shiro需要配置一个领域(Realm),以便我们可以访问用户-->
        <bean id="sampleRealm1" class=" com.yellowcong.shiro.realm.SampleRealm" >
    
            <!-- 如果不加入密码匹配的操作,密码就不会存在 -->
            <!-- 加入了密码匹配器之后,就会默认将前台传递过来的密码自动MD5加密 -->
            <property name="credentialsMatcher">
                <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                    <!-- 加密的方式 -->
                    <constructor-arg index="0" type="java.lang.String" value="MD5" />
                    <!-- 加密的次数,默认是1次 -->
                    <property name="hashIterations" value="1"/>
                </bean>
            </property>
        </bean>
    
    
        <!-- 授权 认证 ,自己定义的,领域(Realm),shiro需要配置一个领域(Realm),以便我们可以访问用户-->
        <bean id="sampleRealm2" class=" com.yellowcong.shiro.realm.SampleRealm2" >
    
            <!-- 如果不加入密码匹配的操作,密码就不会存在 -->
            <!-- 加入了密码匹配器之后,就会默认将前台传递过来的密码自动SHA1加密 -->
            <property name="credentialsMatcher">
                <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                    <!-- 加密的方式 -->
                    <constructor-arg index="0" type="java.lang.String" value="SHA1" />
                    <!-- 加密的次数,默认是1次 -->
                    <property name="hashIterations" value="1"/>
                </bean>
            </property>
        </bean>
    
        <!-- Shior的过滤器配置 -->
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
           <property name="securityManager" ref="securityManager" />  
           <!-- 用户登录地址 -->
           <property name="loginUrl" value="/user/login" />  
           <!-- 登录成功 -->
           <property name="successUrl" value="/user/list" />  
           <!-- 未授权的钦奎光 -->
           <property name="unauthorizedUrl" value="/user/error" />  
    
           <!-- 通过工厂模式,获取数据库里面 权限配置-->
           <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"/>
       </bean> 
    
       <!-- 获取Bean里面的Map集合 -->
       <bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionsMapBuilder" factory-method="loadFilterChainDefinitions"/>
    
       <!-- 读取初始自定义权限内容-->
       <bean id="filterChainDefinitionsMapBuilder" class="com.yellowcong.shiro.filter.FilterChainDefinitionsMapBuilder" />
    </beans>
    spring-mvc的配置

    <!-- Shiro生命周期处理器-->
       <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>     

    配置ecache (可选session缓存)


    <ehcache>
    
        <!-- Sets the path to the directory where cache .data files are created.
    
             If the path is a Java System Property it is replaced by
             its value in the running VM.
    
             The following properties are translated:
             user.home - User's home directory
             user.dir - User's current working directory
             java.io.tmpdir - Default temp file path -->
        <diskStore path="java.io.tmpdir"/>
    
        <cache name="authorizationCache"
               eternal="false"
               timeToIdleSeconds="3600"
               timeToLiveSeconds="0"
               overflowToDisk="false"
               statistics="true">
        </cache>
    
        <cache name="authenticationCache"
               eternal="false"
               timeToIdleSeconds="3600"
               timeToLiveSeconds="0"
               overflowToDisk="false"
               statistics="true">
        </cache>
    
        <cache name="shiro-activeSessionCache"
               eternal="false"
               timeToIdleSeconds="3600"
               timeToLiveSeconds="0"
               overflowToDisk="false"
               statistics="true">
        </cache>
    
        <!--Default Cache configuration. These will applied to caches programmatically created through
            the CacheManager.
    
            The following attributes are required for defaultCache:
    
            maxInMemory       - Sets the maximum number of objects that will be created in memory
            eternal           - Sets whether elements are eternal. If eternal,  timeouts are ignored and the element
                                is never expired.
            timeToIdleSeconds - Sets the time to idle for an element before it expires. Is only used
                                if the element is not eternal. Idle time is now - last accessed time
            timeToLiveSeconds - Sets the time to live for an element before it expires. Is only used
                                if the element is not eternal. TTL is now - creation time
            overflowToDisk    - Sets whether elements can overflow to disk when the in-memory cache
                                has reached the maxInMemory limit.
    
            -->
        <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            />
    
        <!--Predefined caches.  Add your cache configuration settings here.
            If you do not have a configuration for your cache a WARNING will be issued when the
            CacheManager starts
    
            The following attributes are required for defaultCache:
    
            name              - Sets the name of the cache. This is used to identify the cache. It must be unique.
            maxInMemory       - Sets the maximum number of objects that will be created in memory
            eternal           - Sets whether elements are eternal. If eternal,  timeouts are ignored and the element
                                is never expired.
            timeToIdleSeconds - Sets the time to idle for an element before it expires. Is only used
                                if the element is not eternal. Idle time is now - last accessed time
            timeToLiveSeconds - Sets the time to live for an element before it expires. Is only used
                                if the element is not eternal. TTL is now - creation time
            overflowToDisk    - Sets whether elements can overflow to disk when the in-memory cache
                                has reached the maxInMemory limit.
    
            -->
    
        <!-- Sample cache named sampleCache1
            This cache contains a maximum in memory of 10000 elements, and will expire
            an element if it is idle for more than 5 minutes and lives for more than
            10 minutes.
    
            If there are more than 10000 elements it will overflow to the
            disk cache, which in this configuration will go to wherever java.io.tmp is
            defined on your system. On a standard Linux system this will be /tmp"
            -->
      <!--   <cache name="sampleCache1"
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="300"
            timeToLiveSeconds="600"
            overflowToDisk="true"
            />
     -->
        <!-- Sample cache named sampleCache2
            This cache contains 1000 elements. Elements will always be held in memory.
            They are not expired. -->
    
        <!-- Place configuration for your caches following -->
    
    </ehcache>

    7.  SerializableUtils

    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.util.Base64;
    
    import org.apache.shiro.session.Session;
    
    /**
     * 序列和反序列Session对象,只有将session对象序列化成字符串,才可以存储到Mysql上,不能直接存
     *
     * @author FlyingTiger
     * @version 1.0
     * @since 2018/03/05 15:59
     */
    public class SerializableUtils {
    
    
        /**
         * 创建日期:2017/12/21<br/>
         * 创建时间:9:25:30<br/>
         * 创建用户:FlyingTiger<br/>
         * 机能概要:将Session序列化成String类型
         *
         * @param session
         * @return
         */
        public static String serializ(Session session) {
            try {
                //ByteArrayOutputStream 用于存储序列化的Session对象
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
    
                //将Object对象输出成byte数据
                ObjectOutputStream out = new ObjectOutputStream(bos);
                out.writeObject(session);
    
                //将字节码,编码成String类型数据
                return Base64.getEncoder().encodeToString(bos.toByteArray());
            } catch (Exception e) {
                throw new RuntimeException("序列化失败");
            }
        }
    
    
        /**
         * 创建日期:2017/12/21<br/>
         * 创建时间:9:26:19<br/>
         * 创建用户:FlyingTiger<br/>
         * 机能概要:将一个Session的字符串序列化成字符串,反序列化
         *
         * @param sessionStr
         * @return
         */
        public static Session deserializ(String sessionStr) {
            try {
                //读取字节码表
                ByteArrayInputStream bis = new ByteArrayInputStream(Base64.getDecoder().decode(sessionStr));
    
                //将字节码反序列化成 对象
                ObjectInputStream in = new ObjectInputStream(bis);
                Session session = (Session) in.readObject();
                return session;
            } catch (Exception e) {
                throw new RuntimeException("反序列化失败");
            }
        }
    }



    8.  用户登录成功后,将JSESSIONID 下发到终端,存储到移动端应用层变量。 每次访问的时候带上此sessionId 即可保持 用户登录。

    PS:

    • 在用户使用用户名和密码登录的时候,对密码进行加密
    • 会话保持如果使用cookie这种技术的话,存在被别人截取cookie之后就可以认证登录了
    • 在本地保存密码肯定是不合适的,如果保存cookie(token)的话,手机被root之后,很容易就可以看得到了,比如Android的就只是一个xml文件,所以cookie保存要加密,加密之后提高了破解门槛,加密就涉及到秘钥的问题了,秘钥如果写在代码里面,java被反编译之后就很容易秘钥找得到了,当然了google早就已经开始支持NDK(即Android原生开发,这个原生是指使用C/C++开发,编译成为so文件,在java中调用),这样又加大了破解难度,使用Hybrid就更不用说了,直接解压安装包就可以看到了。
    • cookie如果保存在本地,更新的时机(频率)是什么,这样就算是cookie泄露了,也只是在某一段时间内有用(当然了,对于“有心人”来说“这段时间”已经足够做一些事儿了)
    • 可以在session表中添加ip地址来进行再次判断,这样可以解决上述问题。
        参考:

       shiro实现APP、web统一登录认证和权限管理

        Shiro之保存Session到数据库中-yellowcong

    展开全文
  • 采用模块化的方法对系统各个功能模块进行详细设计,然后采用MyEclispe开发平台、tomcat 5.5为服务器、J2EE Web设计语言、 MVC 开发模式实现本系统, 结合统一身份认证系统整体设计思想,设计统一身份认证系统。...
  • SSO 单点登录确实有其存在的道理,无论对用户还是对开发人员或者网站管理人员。能够拥有统一的用户管理...web端使用java,session机制来控制登录状态。 手机端使用token来判断登录状态。 这是我的单点登录,我没有带盐~
  • 单点登录(SSO)是目前比较流行的企业业务整合的解决方案之一,它的机制是在企业网络用户访问企业网站时作一次身份认证,随后就可以对所有被授权的网络资源进行无缝的访问,而不需要多次输人自己的认证信息Web服务具有...
  • 登录请求表单数据 密钥与加密密码均为Base64编码 常见加密: 1)数据加密标准(DES Data Encryption Standard):DES(密钥长度64位)(ECB模式) 2)分组密码算法:AES(密钥长度在128位及以上)(GCM或...

    工具

    Base64 在线解码、编码:https://the-x.cn/base64 

    DES加密/解密:https://the-x.cn/cryptography/Des.aspx 

    解决方案

    登录请求表单数据 

    密钥与加密密码均为Base64编码

     

    常见加密:

    1)数据加密标准(DES Data Encryption Standard):DES(密钥长度64位)(ECB模式)

    2)分组密码算法:AES(密钥长度在128位及以上)(GCM或CBC模式)

    3)流密码算法:AES(密钥长度在128位及以上)(OFB或CTR模式)、chacha20

    4)哈希算法:SHA2、SHA3

    5)密钥交换算法:DSA/DH(密钥长度2048位及以上)、ECDH(密钥长度223及以上)

    6)HMAC(基于哈希的消息验证码)算法:HMAC-SHA2

    7)非对称加密算法:RSA(2048位及以上)、ECC(256位以上)

    DEC解密 

    密钥获取

    在登录页面HTML代码中可以找到 

     

    参考文章

    https://blog.csdn.net/tryheart/article/details/107181738

    https://blog.csdn.net/weixin_39722651/article/details/90523286

    https://blog.csdn.net/weixin_39722651/article/details/90523286

    展开全文
  • 基于PKI的WEB统一电子认证,张高山,宋俊德,随着信息化的发展,电子商务和电子政务等网络服务得到了广泛应用。但随着用户和服务数量的急剧增加,认证技术安全性和方便性受到
  • 提供一种统一身份认证的解决方案,结合互联网单点登录的思路。用户在其中注册一个单点登录账号,然后针对每个应用系统绑定一个该应用系统中原有的账号,并维护这些注册和绑定信息。绑定的过程需要单点登录管理应用...
  • 统一登录门户系统

    千次阅读 2020-04-04 21:02:19
    常见的统一登录要求,还是基于一个统一的入口,由统一登录入口完成登录后,可以自由访问其他系统,而其他系统的用户登录应跳转到统一登录入口。 可能存在的问题: 1.用户系统如何建立,如何解决存量用户。 2.应...

    随着等保2.0和密评工作的深入推进,各政企单位的应用系统建设会向着更安全、更标准方向发展。

    为了推进整合信息共享,破除各系统之间的壁垒,首先要建设的就是统一登录门户系统。

    常见的统一登录要求,还是基于一个统一的入口,由统一登录入口完成登录后,可以自由访问其他系统,而其他系统的用户登录应跳转到统一登录入口。

    可能存在的问题:

    1.用户系统如何建立,如何解决存量用户。

    2.应符合等保要求和国密要求。

     

    一种简单的统一登录系统,在主域名中设置cookie信息,保证在访问其他子域名时也带上cookie,通过cookie获得用户的身份信息。知识点如下:

    1.基于cookie用户登录,cookie和session的关系

    2.cookie的domain(域)信息

    一、基础知识

    1.1 cookie

    cookie是http协议的产物,由server生成,通过http response发送给webbrowser,webbrowser存储在用户本地的文件,在下一次访问同一url时候,在http header中携带cookie一起发送到服务器端。cookie采用key-value的数据结果。

    在OSI 网络模型中,会话层在第五层,我们熟悉的Http、FTP、SMTP是TCP/IP网络协议的应用层协议。

    Http是一种无状态的连接协议,本身并没有提供会话协议。通过cookie方式,可以为http添加会话信息。

    如何理解,Http是一种无状态的连接协议。当用户的一次访问请求结束后,后端服务器就无法知道下一次来访问的还是不是上次访问的用户。另一种情况,在一个NAT的网络中,有10个用户通过NAT方式访问网络,那么这10个用户的ip都是一样,10个用户访问Web服务器时,在服务端无法区分这10个用户。

    当一个web系统,需要会话信息时,可以在用户登录时,在cookie中保存用户名。这样在下一次访问时,cookie信息会放在http header中,随着http请求发送到服务端,起到会话标识的作用。即同一个客户端发出的请求,每次发出的请求都会带有上一次访问时服务端设置的信息,这样服务端就可以根据之前存入 Cookie 的值来做相应的处理。

        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
            Cookie cookie = new Cookie("mcrwayfun",System.currentTimeMillis()+"");
            // 设置生命周期为MAX_VALUE
            cookie.setMaxAge(Integer.MAX_VALUE);
            resp.addCookie(cookie);
        }

    1.2 cookie域

    产生Cookie的服务器可以向set-Cookie响应首部添加一个Domain属性来控制哪些站点可以看到那个cookie,例如下面:

    Set-Cookie: name="wang"; domain="m.zhuanzhuan.58.com"
    

    如果用户访问的是m.zhuanzhuan.58.com那就会发送cookie: name="wang", 如果用户访问www.aaa.com(非zhuanzhuan.58.com)就不会发送这个Cookie。

    cookie的路径 Path

    Path属性可以为服务器特定文档指定Cookie,这个属性设置的url且带有这个前缀的url路径都是有效的。

    例如:m.zhuanzhuan.58.com 和 m.zhaunzhuan.58.com/user/这两个url。 m.zhuanzhuan.58.com 设置cookie

    Set-cookie: id="123432";domain="m.zhuanzhuan.58.com";

    m.zhaunzhuan.58.com/user/ 设置cookie:

    Set-cookie:user="wang", domain="m.zhuanzhuan.58.com"; path=/user/

    但是访问其他路径m.zhuanzhuan.58.com/other/就会获得

    cookie: id="123432"

    如果访问m.zhuanzhuan.58.com/user/就会获得

      cookie: id="123432"
      cookie: user="wang"

    二、cookie实现单点登录

    正常情况下,同一个一级域名下的两个二级域名也不能交互使用Cookie,比如test1.mcrwayfun.com和test2.mcrwayfun.com,因为二者的域名不完全相同。如果想要mcrwayfun.com名下的二级域名都可以使用该Cookie,需要设置Cookie的domain参数为.mcrwayfun.com,这样使用test1.mcrwayfun.com和test2.mcrwayfun.com就能访问同一个cookie。

    在这种单点登录模型中,登录系统采用cookie保存登录信息,并设置cookie domain域为一级域名,在访问二级域名时,服务端也会获得用户的登录信息,实现了统一登录的功能。

    三、session和cookie

    cookie 缺点

    1. 每次请求都会携带全部的 Cookie 信息,容易造成不必要的带宽浪费。

    2. 每个浏览器对 Cookie 在同一个域名下的个数和每个 Cookie 的总大小(4kb)都有相应的限制。

    3. 数据存储在客户端,容易被拦截篡改,不安全。

    4. 客户端可以禁用 Cookie,导致功能失效。

    5. 难于管理,大型系统里每个应用都会有自己的 Cookie,加上以上的限制,容易出现数据被截取,导致数据丢失。

    Session(PHP 后端为例,java是一样的)

    1. 服务端(默认设置)接受请求时,先查看 $_COOKIE 中是否存在 name 为 PHPSESSID 的键值对(value 为服务端生成的 SESSION_ID:bebfaf6c745c1a6e5f341baf2178113b)。

    2. 若不存在(第一次请求),将会自动生成 SESSION_ID,写入到 $_COOKIE 数组中(name 为 PHPSESSID, value 为 SESSION_ID),然后通过响应头返回给浏览器。

    3. 若存在(非第一次请求),则根据 value 值到设置的目录下获取相应的文件,反序列化并写入到 $_SESSION 数组供后续使用。

    4. 当服务端进行设置时,此$_SESSION 只会维持在内存中。当脚本执行结束时,将会自动把 $_SESSION 序列化后写入到 SESSION_ID 对应的文件中(创建或覆盖)。(脚本执行即http访问)

    5. 通过POST获取用户的用户名密码,当验证成功后,将用户身份信息保存在$_SESSION中,通过cookie和session实现用户登录。

    优点

    1. 只将会话标识符放到 Cookie 里,减少带宽的传输。

    2. 可以存储大量的信息,基本没有空间上的限制。

    3. 数据存放在服务端,安全。

    4. 可以统一集中式管理。

    联系

    SESSION 为了识别会话,需要传输一个唯一的 SESSION_ID 到客户端。一般情况下,都是借助 Cookie 来传递,当然也可以通过其他方式进行传递。

    Session 安全延伸

    以上分析得出, Session 需要通过 SESSION_ID 来识别客户端,这就会导致一个安全隐患。当 SESSION_ID 落入第三者时,服务端将无法分辨出是否是合法的请求。SESSION_ID通常为一串随机值,除了防止泄露,还要防止通过暴力方式猜解出来。

    一个简单的防御措施就是:每次请求时都生成一个新的 SESSION_ID 关联到对应的数据并将老的 SESSION_ID 删除。

    百度BDUSS可以看做是一个session_id,HttpOnly属性保证javascript不能修改这个值。

    四、全局Session

    采用Redis替代中间件session存储,实现单点登录。

    五、采用cookie实现统一登录架构

    1. 登录login页面,生成用户信息并保存,保存在持久化UserInfo_Server数据库或Redis中。
    2. 将用户信息的key,写入到cookie中,设置domain为顶级域名。
    3. 浏览其他页面,会携带cookie发布到后端server,server向UserInfo_Server查询用户认证信息,实现用户登录。
    4. 用户注销,任一个server都可以设置cookie的有效期为0,删除cookie,并调用UserInfo_Server接口修改数据库中的认证信息。
    5. key的生成要随机,UserInfo接口校验数字签名,保证信道加密cookie不能被窃取

     

     

    参考:

    1. 这一次带你彻底了解Cookie https://www.cnblogs.com/zhuanzhuanfe/p/8010854.html 
    2. 深入理解Cookie https://www.jianshu.com/p/6fc9cea6daa2
    3. Cookie 和 Session https://segmentfault.com/a/1190000008928086
    4. SpringBootSecurity学习(09)网页版登录配置Session共享 https://segmentfault.com/a/1190000020544320

     

    展开全文
  • 一文学会微信WEB第三方登录

    千次阅读 2020-08-10 14:53:51
    今天给大家分享一下微信WEB第三方登录: 一、提前工作(提前工作省略,腾讯要你提供什么,你就提供什么就好了。): 1.1、申请微信开放平台账号(https://open.weixin.qq.com/): 1.2、企业开发平台认证 ...
  • html css制作的web前端登录界面

    千次阅读 多人点赞 2020-08-02 23:15:28
    web前端登录界面
  • java web 通用返回json 统一返回结果

    千次阅读 2018-12-10 03:10:41
    开源个自己封装的通用json返回吧,自我感觉写的还行…emmm… ... * web专用返回 * @author Orange * @date 2018/10/28 */ public class Response&amp;lt;T&amp;gt; { private int code; ...
  • OAUTH2不仅支持认证,还具备授权功能, 比如通过QQ登录获取用户头 像,基本资料等。 OAuth2角色 resource owner : 资源所有者,具备访问该资源的实体, 如果是某个人, 被称为end-user。 resources se
  • 统一认证,单点登录

    千次阅读 2021-10-18 14:59:16
    1、单点登录的三种实现方式 1.1 以cookie作为凭证 缺点: cookie不安全 由于cookie只针对单个域,所以无法跨域访问 1.2 jsonp方式 此种方式可解决第一种方法中无法跨域免登的问题。 什么是jsonp: web页面可以...
  • 另外需要特别注意,如果开发者有在多个公众号,或在公众号、移动应用之间统一用户帐号的需求,需要前往微信开放平台(open.weixin.qq.com)绑定公众号后,才可利用UnionID机制来满足上述需求,文档: ...
  • Web功能测试(登录功能测试用例)

    千次阅读 2020-05-07 11:23:18
    Web功能测试(登录功能测试用例) 【UI测试】 布局是否合理,输入框和按钮是否对齐,界面规范是否统一,是否与原型图保持一致 【功能测试】 1.用户名和密码文本框长度是否做了限制(边界值法) 2.用户名和密码输入...
  • Web API系列(三)统一异常处理

    千次阅读 2018-08-17 17:00:03
    作为内部或者是对外提供的统一webapi 接口,统一的异常处理,把正确的信息返回给调用者很重要。这样可以让接口开发人员,了解具体的原因所在,这样可以得到有效的错误处理。  需要注意的是,webapi异常的状态码,...
  • 使用JWT实现微服务统一登录认证

    千次阅读 2020-08-25 00:06:16
    例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。 缺点是什么?...
  • 公司子系统整合统一登录的架构

    千次阅读 2018-12-27 11:49:13
    如下图是公司的统一登录界面: 众多子系统的登录页面不再使用,所有登录走统一登录页面,登录时选择你要登录的系统,这里以我改造的安全管理系统为例。 在安全管理系统项目中加入一个整合的jar包,其实就是一个...
  • Kingbase数据库web统一管理平台

    千次阅读 2019-08-03 21:35:15
    1、安装Kingbase金仓数据库后,通过打开web管理平台,可以方便的进行远程维护。 示例地址:https://192.168.0.1:54328/webstudio 2、输入用户名密码登录。 3、包括:数据库服务器管理,交互式SQL工具,性能...
  • 各大系统如何接入统一登录认证

    万次阅读 2016-01-08 16:53:26
    现在系统一般采用sso架构,统一接入登录,有比较出名的开源框架CAS,对于各种语言都做了单点登录接入的支持,当然你也可以有自己的登录认证接口,然后在代码里面实现登录认证就好了。。。 为什么我要写这篇文章,...
  • JEECG 集成KiSSO单点登录实现统一身份认证 JEECG 如何为其他第三方系统实现统一身份认证服务,实现单点登录? 第三方系统如何对接呢? 今天为大家揭开这层面纱,让大家了解实质,使用它更快速的构建大家需要的...
  • springcloud springboot 基于token的登录认证,redis存放token,filter统一验证登录状态 项目完善04
  • 转载来源 :华为网络工程师 | ensp中的华为防火墙设备如何能实现web登录做配置 :https://mp.weixin.qq.com/s/vpnO8bCbLjLXbiCFsBrJjQ 防火墙技术 防火墙技术,最初是针对 Internet 网络不安全因素所采取的一种保护...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 105,937
精华内容 42,374
关键字:

web统一登录