springmvc 集成 shiro 主要关键点说明

--校验部分、授权部分、realm部分:

自定义 AuthUserDetails 对象用来对应权限框架中的认证授权用户
自定义SourceUsernamePasswordToken 扩展UsernamePasswordToken 处理用户名/密码方式校验(最基础的是AuthenticationToken,HostAuthenticationToken, RememberMeAuthenticationToken分别扩展自它,BearerAuthenticationToken也扩展自它,UsernamePasswordToken扩展自host、rember这俩)********************************************************************************
自定义类扩展FormAuthenticationFilter,控制所有请求来源需要经过shirofilter

public class JcaptchaFormAuthenticationFilter extends FormAuthenticationFilter {

    //在自定义JcaptchaFormAuthenticationFilter中重写onAccessDenied方法
    //如果是未登录的会被拦截跳转登录页登录
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        如果是未登录且地址不是登录请求地址会指向登录页,如果是登录请求指向executeLogin
    }

    //在自定义JcaptchaFormAuthenticationFilter中重写executeLogin方法
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        这里根据提交的验证码是否正确等信息用部分处理;
        在这里执行:subject.login(token);   //---验证该用户信息在各个域中是否正确
        如果失败指向 onLoginFailure
        如果成功指向 onLoginSuccess
    }


    //在自定义JcaptchaFormAuthenticationFilter中重写onLoginFailure方法
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        执行失败后的记录,跳转操作
    }


    //在自定义JcaptchaFormAuthenticationFilter中重写onLoginSuccess方法
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
        执行成功后的记录,跳转操作
    }

}


********************************************************************************

*******源码内容,追踪 subject.login(token) 该方法从域中获取用户信息,校验用户密码
 

org.apache.shiro.realm.AuthenticatingRealm.getAuthenticationInfo(AuthenticationToken token){
    通过调用实现了 doGetAuthenticationInfo 这个方法的地方获取用户(数据库中)
    通过调用实现了 assertCredentialsMatch 这个方法的 assertCredentialsMatch 这里的 doCredentialsMatch 方法地方校验用户
}

********************************************************************************
自定义类ShiroJdbcRealm,告诉使用哪个realm控制用户信息来源

public class ShiroJdbcRealm extends AuthorizingRealm {
    //重写 doGetAuthenticationInfo 进行获取用户
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
        认证回调函数,登录时调用
    }


    //在ShiroJdbcRealm扩展类中重写doGetAuthorizationInfo方法
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        在这里给当前用户赋予权限
        SimpleAuthorizationInfo info =new SimpleAuthorizationInfo();
        info.addRole(“for循环插入-角色code”)
        info.addStringPermission("for循环插入--权限集合code")
    }


    //在ShiroJdbcRealm中要初始化定义CredentialsMatcher使用那种加密方式校验
    public void initCredentialsMatcher() {
        //在这里定义使用那种加密方式setCredentialsMatcher(new RetryLimitHashedCredentialsMatcher("MD5"))
        RetryLimitHashedCredentialsMatcher matcher = new RetryLimitHashedCredentialsMatcher(PasswordService.HASH_ALGORITHM);
        setCredentialsMatcher(matcher);
    }

}

自定义类,告诉使用哪个来进行密码校验

public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher
{
    //重写doCredentialsMatch进行密码校验
    public boolean doCredentialsMatch(AuthenticationToken authcToken, AuthenticationInfo info) {
        //在这里根据登录方式 密码/无密码方式 进行校验
        boolean matches = super.doCredentialsMatch(authcToken, info);
    }

}

 

配置文件部分:
*******************web.xml*******************
//这里的filter-name 对应spring-shiro.xml中 bean id为 shiroFilter的

 <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>


 源码分析:对应关系

 org.springframework.web.filter.DelegatingFilterProxy中的
 protected void initFilterBean() throws ServletException {
    this.delegate = initDelegate(wac);//
}


protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
        //getTargetBeanName() <--- shiroFilter
        //获得org.apache.shiro.spring.web.ShiroFilterFactoryBean这个实例
        Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
        if (isTargetFilterLifecycle()) {
            delegate.init(getFilterConfig());
        }
        return delegate;
    }


当getBean()方法调用的时候,最终会调用到org.apache.shiro.spring.web.ShiroFilterFactoryBean的getObject()方法,
这个方法就是连接spring与shiro的桥梁,这样就关联起来了;

 

 

*******************spring-shiro.xml*******************

这里面使用了分布式解决方案,将session信息存储到redis

<!-- 配置redis池,依次为最大实例数,最大空闲实例数,(创建实例时)最大等待时间,(创建实例时)是否验证 -->
	<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
		<property name="maxTotal" value="${redis.maxTotal}"/>
		<property name="maxIdle" value="${redis.maxIdle}"/>
		<property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
		<property name="testOnBorrow" value="${redis.testOnBorrow}"/>
	</bean>

	<!-- Redis Manager [start]  <bean id="redisShiroManager" class="org.crazycake.shiro.RedisManager">-->
	<bean id="redisShiroManager" class="自定义的ShiroRedisManager">
		<property name="host" value="${shiroSession.redis.urlStr}" />
		<property name="password" value="${shiroSession.redis.pass}" />
		<property name="database" value="${shiroSession.redis.database}" />
		<property name="jedisPoolConfig" ref="jedisPoolConfig" />
	</bean>
	<!-- Redis Manager [end] -->

	<!-- Redis session DAO [start] -->
	<!--<bean id="redisSessionDAO" class="org.crazycake.shiro.RedisSessionDAO">  使用重写ShiroRedisSessionDAO-->
	<bean id="redisSessionDAO" class="自定义的ShiroRedisSessionDAO">
		<property name="redisManager" ref="redisShiroManager" />
		<property name="expire" value="1800" />
	</bean>

	<!-- shiro cache using EhCache 设置depends-on="cacheManager",确保共享模式下优先加载Spring CacheManager -->
	<bean id="ehCacheManager" class="自定义的SharedEhCacheManager" depends-on="cacheManager">
		<property name="cacheManagerConfigFile" value="classpath:ehcache-config.xml" />
		<property name="shared" value="true" />
	</bean>


	<bean id="shiroJdbcRealm" class="自定义的ShiroJdbcRealm">
		<property name="passwordService" ref="passwordService" />
		<property name="userService" ref="userService" />
	</bean>

	<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
		<property name="authenticationStrategy">
			<bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy" />
		</property>
	</bean>

	<!-- Shiro's main business-tier object for web-enabled applications -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="authenticator" ref="authenticator" />
		<property name="realms">
			<list>
				<ref bean="shiroJdbcRealm" />
			</list>
		</property>

		<property name="cacheManager" ref="ehCacheManager" />

		<!-- 注入session管理器 -->
		<property name="sessionManager" ref="sessionShiroManager" />
		<!-- 记住我 -->
		<property name="rememberMeManager" ref="rememberMeManager" />
	</bean>

	<!-- session会话管理器 -->
	<bean id="sessionShiroManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
		<!-- session过期时间 单位毫秒  配置为30分钟(30 * 60 * 1000)-->
        <property name="globalSessionTimeout" value="1800000" />
		<!-- 删除失效的session -->
		<property name="deleteInvalidSessions" value="true" />
		<!-- url上带sessionId 默认为true -->
		<property name="sessionIdUrlRewritingEnabled" value="false"/>

		<property name="sessionDAO" ref="redisSessionDAO" />

		<!-- 自定义session监听器 -->
		<property name="sessionListeners" ref="shiroSessionListener" />

		<!-- 定时清理失效会话, 清理用户直接关闭浏览器造成的孤立会话   -->
		<property name="sessionValidationSchedulerEnabled" value="false"/>

		<!--<property name="sessionValidationScheduler"  ref="sessionValidationScheduler"/>-->
	</bean>

	<!-- 配置session的定时验证检测程序类,以让无效的session释放 -->
	<!--<bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler">-->
	<!--<bean id="sessionValidationScheduler" class="自定义的ExecutorServiceShiroSessionValidationScheduler">
		&lt;!&ndash; 设置session的失效扫描间隔,单位为毫秒  配置为30分钟(30 * 60 * 1000)&ndash;&gt;
		<property name="interval" value="1800000"/>
		&lt;!&ndash; 随后还需要定义有一个会话管理器的程序类的引用 &ndash;&gt;
		<property name="sessionManager" ref="sessionShiroManager"/>
	</bean>-->

	<bean id="shiroSessionListener" class="自定义的ShiroSessionListener"></bean>

	<!-- rememberMeManager管理器,写cookie,取出cookie生成用户信息 -->
	<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
		<property name="cookie" ref="rememberMeCookie" />
	</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="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
		<constructor-arg value="rememberMe" />
		<property name="httpOnly" value="true" />
		<!-- 30天 单位为秒 2592000-->
		<property name="maxAge" value="2592000" />
	</bean>

集成过程中,遇到的其他问题

1、切换角色;subject.runas(aaa) main用户不变,登录历史中index【0】的是最后操作用户

private static String RUN_AS_PRINCIPALS_SESSION_KEY = "org.apache.shiro.subject.support.DelegatingSubject.RUN_AS_PRINCIPALS_SESSION_KEY";
public static final String PRINCIPALS_SESSION_KEY = DefaultSubjectContext.class.getName() + "_PRINCIPALS_SESSION_KEY";

//使用A登录后,使用 subject.runAs(new SimplePrincipalCollection(new AuthUserDetails(), ""))切换过的用户列表
List<PrincipalCollection> principalCollectionList = (List<PrincipalCollection>) session.getAttribute(RUN_AS_PRINCIPALS_SESSION_KEY);
if(principalCollectionList != null && principalCollectionList.size() > 0){
	principals = principalCollectionList.get(0);//历史登录用户中最后一个账户
}else{
	principals = (PrincipalCollection)session.getAttribute(PRINCIPALS_SESSION_KEY);//最开始登录的用户
}

2、使用redis作为realm,分布式处理问题

3、60分钟校验失效用户 步长 问题 1W起步,用户数过大,造成处理缓慢,使用redis自动过期解决,弊端长期未操作用户为记录退出时间,补充:可监听rediskey失效,取决于数据重要与否

4、代码中需要统一系统中获取用户的方法

5、角色/权限 校验

//校验用户是否具有指定角色;
subject.hasRole("admin")  		

//校验用户是否包含集合的所有角色;
List<String> hasRoles = new ArrayList<>();
hasRoles.add("roleAdmin");
hasRoles.add("roleEdu");
subject.hasAllRoles(hasRoles)

//校验用户是否具有对应的权限
subject.isPermitted("user:edit");
subject.isPermitted("用户:编写");

例如:
-- java
@RequiresPermissions("用户:编写") /  @RequiresPermissions("user:edit") 
public void editUser(){

} 

-- jsp/页面部分
<shiro:hasPermission name="用户:编写">  显示该部分内容</shiro:hasPermission>

6、用户信息
 

String userLoginName = subject.getPrincipals();

 

其他:

**********我们将使用的API记住
IniSecurityManagerFactory : 用于加载配置文件,创建SecurityManager对象
SecurityManager :就是整个Shiro的控制对象
SecurityUtils :SecurityManager 工具类,用于获得Subject对象
Subject :身份类,存储返回的数据信息、提供了校验的权限的方法
UsernamePasswordToken 身份信息构建类 (Token 令牌,作用就是传入校验参数)
AuthorizingRealm 支持校验与授权的Realm
AuthenticationInfo 校验成功返回的信息的父接口
SimpleAuthenticationInfo 校验成功返回信息类
Md5Hash  Md5加密类
ByteSource  字节码处理工具类,我们在构造Md5加盐时使用到。
HashedCredentialsMatcher Md5算法校验器,用于支持Md5校验
AuthorizationInfo 授权成功返回的信息类的父接口
PrincipalCollection 授予是获得验证信息的类
SimpleAuthorizationInfo 授权成功返回的信息类的实现类

原有web项目,解决分布式session信息,笔记。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值