关于shiro 的 session和无状态同时存在时实现思路

以前概念里,关于客户端登录,没有cookieId,需要手动记录保存sessionid。
在看了shiro教程以后,了解了无状态的web应用集成
即服务端无状态,服务器端不会存储像会话这种东西,而是每次请求带上相应的用户名进行登录。
具体实现:
1、教程上讲,客户端每次申请一个token,token一次性,只能用一次。
2、服务器记录下这些token,如果之前用过就是非法请求。
关于token,本文用的是无限制的,可以用很多次。当然,一个token只能使用一次的更为安全。目前想到的,
使用一次的方案为:时间戳加固定字符串进行编码。服务端进行解码验证,
判断时间戳的时效性来判断token是否真实有效。

第一步 创建无状态使用的token

目的,根据token调用不同的relam。

	public class NoStatelessToken implements AuthenticationToken {
		private String username;
		private String params;
		.....
		
	}

第二步 写mobileUserRealm 和userRealm

	public class MobileUserRealm extends BaseAuthorizingRealm {

		//注入用到的所有服务		

		public MobileUserRealm() {
        	//根据realm绑定的token不同,登录时自动调用相关realm进行登录
        	super();
       		setAuthenticationTokenClass(NoStatelessToken.class);
   		 }
   		protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        NoStatelessToken noStatelessToken = (NoStatelessToken) token;
        String username= noStatelessToken.getusername();
        //使用jwt初始化验证得到用户
        User user = jwtService.getusername(username);
        if (user.getStatus() == Status.DENY || user.getStatus() == Status.DELETE) {
            throw new DisabledAccountException("帐号被禁用");
        }

        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, authToken, getName());
        return info;
    	}
	
	}
public class UserRealm extends BaseAuthorizingRealm {
	public UserRealm() {
      setAuthenticationTokenClass(UsernamePasswordToken.class);
    }
     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();
        String password = new String((char[]) authenticationToken.getCredentials());
        User user = userService.findByPhone(username);
        if (user == null) {
            throw new MyException("用户不存在");
        }
        boolean check = true;
       
        if (username.equals(TEST_PHONE) && TEST_CODE.equals(password)) {
            check = true;
        }
        if (!check) {
            throw new UnknownAccountException("验证码错误!");
        }
        if (user.getStatus() == Status.DENY || user.getStatus() == Status.DELETE) {
            throw new DisabledAccountException("帐号被禁用");
        }
        logger.debug("用户{}登录成功", username);
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
        //同步user信息到,这一步可以不必做,上一步登录成功会自动同步。
        UserUtil.setUser(user);
        return info;
    }
}

public abstract class BaseAuthorizingRealm extends AuthorizingRealm {

		//授权
 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        if (principalCollection == null) {
            throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
        }
        User user = (User) principalCollection.getPrimaryPrincipal();
        User one = userService.get(user.getId());
        List<String> roleList = new ArrayList<>();
        if(one.getStatus() == Status.ALLOW){
            roleList = roleService.listRoleSnByUser(user.getId(), Status.ALLOW.getStatus());
        }
        logger.info("----BaseAuthorizingRealm + roleList----"+roleList);
        List<String> authorityList = new ArrayList<>();
      
            List<Integer> list =  roleService.listIdByUserIdAndFirstDepIdAndUserType(user.getId(), user.getFirstDepId(),Status.ALLOW.getStatus(),user.getUserType());
            if (list != null && list.size() > 0) {
                for (Integer integer : list) {
                    List<String> params = authorityService.listParamsByRoleId(integer,  Status.ALLOW.getStatus());
                    authorityList.addAll(params);
                }
            }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setRoles(new HashSet<>(roleList));
        info.setStringPermissions(new HashSet<>(authorityList));
        return info;
    }
		//清除权限缓存
	   public void refreshCache(RolePermissionEvent event){
        switch (event.getType()){
            case PERMISSION:
                Cache<Object, AuthorizationInfo> authorizationCache = getAuthorizationCache();
                if(authorizationCache != null){
                    getAuthorizationCache().clear();
                }
                logger.info("------------清除全部用户权限----------"+authorizationCache);
                break;
            case USER:
                User user = userService.get(event.getUserId());
                String realmName = getName();
                PrincipalCollection principals = new SimplePrincipalCollection(user, realmName);
                if(principals != null){
                    clearCachedAuthorizationInfo(principals);
                }
                logger.info("------------清除单个用户权限----------"+user);
                break;
            default:
                break;
        }
    }
}

第三步重写sessionfactory


public class MyselfSubjectFactory extends DefaultWebSubjectFactory {



    @Override
    public Subject createSubject(SubjectContext context) {
    	//没有经过shirofilter的请求,无法强转为webSubjectContext。涉及到shiro自带的包装
        if (!(context instanceof WebSubjectContext)) {
            return super.createSubject(context);
        }
        WebSubjectContext webSubjectContext = (WebSubjectContext)context;
        HttpServletRequest request = (HttpServletRequest) webSubjectContext.getServletRequest();
        //登录状态下,不创建session不保存用户信息。 如果不加这个,会导致request为null,这个有疑点,探究后再来完善。
        if (!context.isAuthenticated()) {
        if (UserAgentUtil.isClient( request)) {
        		//不创建session
                context.setSessionCreationEnabled(false);
            } else {
           		//创建session
                context.setSessionCreationEnabled(true);

            }
        }
        return super.createSubject(context);
    }

第四步 创建拦截请求的filter

目的,区分开pc和客户端,pc还是用最基础的有session的方式,用浏览器自带cookie存储session实现用户信息会话范围内共享。

protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        //手机端进行拦截,pc端直接放过
        if (UserAgentUtil.isClient((HttpServletRequest) request))) {
            //判断该请求是否登录可选,将其放入request中以做后续处理
            request.setAttribute(OPTIONAL,mappedValue==null?false:true);
//           if (mappedValue!=null){
//               return true;
//           }
            //进入下一步处理
            return false;
        } else {
            return true;
        }
    }
 protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
 	//业务处理,解析token
 	//根据token信息拿到用户信息
 	//进行subject.login 登录  NoStatelessToken 是自己定义的token,定义这个token为了区分开pc的usernamepasswordtoken,
 	//然后调用不同的realm
 	 getSubject(request, response).login(new NoStatelessToken(token, siteDto));
}

第五步 配置shiro.xml

shiro基本配置不再重复,新增的配置展示如下:

	<bean id ="subjectFactory" class="自己项目路径的MyselfSubjectFactory"/>
    <bean id="statelessAuthcFilter" class="自己项目路径的MobileFilter"/>
	<bean id="userRealm" class="自己项目路径的UserRealm">
        <!--<property name="rolePermissionResolver" ref="userRolePermissionResolver"/>-->
        <property name="credentialsMatcher" ref="credentialsMatcher"/>
    </bean>
	 <bean id="mobielUserRealm" class="自己项目路径的MobileUserRealm">
        <property name="credentialsMatcher" ref="credentialsMatcher"/>
        <property name="authorizationCachingEnabled" value="false"/>
    </bean>
	  <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realms">
            <list>
                <ref bean="userRealm"/>
                <ref bean="mobielUserRealm"/>
            </list>
        </property>
        <property name="subjectDAO">
        //稍后解释,为什么重写sessionStorageEvaluator
            <bean class="org.apache.shiro.mgt.DefaultSubjectDAO">
                <property name="sessionStorageEvaluator" ref="customeSessionStorge"/>
            </bean>
        </property>
        <property name="sessionManager" ref="sessionManager"/>
        <property name="cacheManager" ref="redisCacheManager"/>
        <property name="subjectFactory" ref="subjectFactory"/>
    </bean>
				
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/login"/>
        <property name="filters">
            <util:map>
                <entry key="statelessAuthc" value-ref="statelessAuthcFilter"/>
            </util:map>
        </property>
        <property name="filterChainDefinitions">
            <value>
                # some example chain definitions:
                # more URL-to-FilterChain definitions here
                /m = anon
                /news/list = statelessAuthc[optional]
                # 所有过shirofilter的请求,都走pc和客户端的验证 authc 默认验证是userrealm
                /** = statelessAuthc,authc
            </value>
        </property>
    </bean>

配置请求用的/**方式,使用下来觉得不妥,还是给方法加统一前缀,这样配置起来更简单明了。

第六步 配置web.xml

	<filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <async-supported>true</async-supported>
        <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>

为什么我要把web.xml的配置贴出来,是因为,这个shiroFilter拦截的所有的请求,项目路径下面的静态文件也过了shirofilter,造成静态资源也创建了subject,还是能创建session的subject,并且缓存了用户的登录后 的信息。

重写的sessionStorge

在项目里只用到无状态的情况下,这个可以参考shiro教程的方法,配置shiro.xml即可,但是有两种情况时既有无状态,又有有session的,
需要重写区分修改配置文件如下:

	//配置文件的方式
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
	<property name="realm" ref="statelessRealm"/>
	 //在这里设置
	<property name="subjectDAO.sessionStorageEvaluator.sessionStorageEnabled" value="false"/>
	<property name="subjectFactory" ref="subjectFactory"/>
	<property name="sessionManager" ref="sessionManager"/>
	</bean> 
	//重写sessionstorage的方式
public class CustomeSessionStorge extends DefaultWebSessionStorageEvaluator{

    @Override
    public boolean isSessionStorageEnabled(Subject subject) {
        if(subject instanceof WebSubject){
            HttpServletRequest request = (HttpServletRequest) ((WebSubject) subject).getServletRequest();
            if (UserAgentUtil.isClient( request) ) {
                return false;
            }
        }
        return super.isSessionStorageEnabled(subject);
    }
}

贴下源码,默认的是如何存储的。。。app不带token时却能拿到身份信息,原因就在这里,存储了用户信息。

 @SuppressWarnings({"SimplifiableIfStatement"})
    @Override
    public boolean isSessionStorageEnabled(Subject subject) {
        if (subject.getSession(false) != null) {
            //use what already exists
            return true;
        }
		//没有session,切为false的时候才不存储用户身份信息,实则一一个坑。
        if (!isSessionStorageEnabled()) {
            //honor global setting:
            return false;
        }

        //SHIRO-350: non-web subject instances can't be saved to web-only session managers:
        //since 1.2.1:
        if (!(subject instanceof WebSubject) && (this.sessionManager != null && !(this.sessionManager instanceof NativeSessionManager))) {
            return false;
        }

        return WebUtils._isSessionCreationEnabled(subject);
    }

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值