--校验部分、授权部分、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">
<!– 设置session的失效扫描间隔,单位为毫秒 配置为30分钟(30 * 60 * 1000)–>
<property name="interval" value="1800000"/>
<!– 随后还需要定义有一个会话管理器的程序类的引用 –>
<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信息,笔记。