1.核心类ShiroFilterFactoryBean
<!-- 安全认证过滤器 -->
<bean id="shiroFilter" class="com.dq.shiro.security.MyShiroFilterFactoryBean">
<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"/>
<!-- 表单校验过滤器 ref-->
<entry key="authc" value-ref="formAuthenticationFilter"/>
</map>
</property>
<property name="filterChainDefinitions">
<ref bean="shiroFilterChainDefinitions"/>
</property>
</bean>
重要引用
①securityManager(shiro安全管理器)
②filterChainDefinitions(可以定义指定url需要的认证方式)
<bean name="shiroFilterChainDefinitions" class="java.lang.String">
<constructor-arg>
<value>
/static/** = anon
${adminPath}/login = authc
${adminPath}/logout = logout
${adminPath}/** = user
</value>
</constructor-arg>
</bean>
③filters中定义各种自定义filters
注意在这个Filter中,username password rememberMe是写死的,所以前台传递参数时需要注意比对,也可以在配置文件中修改
<!-- 表单校验过滤器 -->
<bean id="formAuthenticationFilter" class="com.dq.shiro.security.FormAuthenticationFilter">
<!-- 表单中账号的input名称 -->
<property name="usernameParam" value="username" />
<!-- 表单中密码的input名称 -->
<property name="passwordParam" value="password" />
</bean>
public class FormAuthenticationFilter extends AuthenticatingFilter {
//TODO - complete JavaDoc
public static final String DEFAULT_ERROR_KEY_ATTRIBUTE_NAME = "shiroLoginFailure";
public static final String DEFAULT_USERNAME_PARAM = "username";
public static final String DEFAULT_PASSWORD_PARAM = "password";
public static final String DEFAULT_REMEMBER_ME_PARAM = "rememberMe";
比如重写的formAuthenticationFilter
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_USERNAME_PARAM = "username";
public static final String DEFAULT_PASSWORD_PARAM = "loginPwd";
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;
private String usernameParam = "username";
private String passwordParam = "loginPwd";
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
String username = getUsername(request);
String password = getPassword(request);
if (password==null){
password = "";
}
boolean rememberMe = isRememberMe(request);
String host = com.dq.base.web.utils.WebUtils.getIpAddr((HttpServletRequest)request);
String captcha = getCaptcha(request);
boolean mobile = isMobileLogin(request);
return new UsernamePasswordToken(username, password.toCharArray(), rememberMe, host, captcha, mobile);
}
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
*/
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 && StringUtils.startsWith(e.getMessage(), "msg:")){
message = StringUtils.replace(e.getMessage(), "msg:", "");
}
else{
message = "系统出现点问题,请稍后再试!";
e.printStackTrace(); // 输出到控制台
}
request.setAttribute(getFailureKeyAttribute(), className);
request.setAttribute(getMessageParam(), message);
return true;
}
protected String getPassword(ServletRequest request) {
return WebUtils.getCleanParam(request, getPasswordParam());
}
public String getUsernameParam() {
return this.usernameParam;
}
public void setUsernameParam(String usernameParam) {
this.usernameParam = usernameParam;
}
public String getPasswordParam() {
return this.passwordParam;
}
public void setPasswordParam(String passwordParam) {
this.passwordParam = passwordParam;
}
}
shiro进行初始化时,会调用ShiroFilterFactoryBean中的createInstance方法
@Override
protected AbstractShiroFilter createInstance() throws Exception {
org.apache.shiro.mgt.SecurityManager securityManager = getSecurityManager();
if (securityManager == null) {
String msg = "SecurityManager property must be set.";
throw new BeanInitializationException(msg);
}
if (!(securityManager instanceof WebSecurityManager)) {
String msg = "The security manager does not implement the WebSecurityManager interface.";
throw new BeanInitializationException(msg);
}
FilterChainManager manager = createFilterChainManager();
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
chainResolver.setFilterChainManager(manager);
return new MySpringShiroFilter((WebSecurityManager) securityManager,
chainResolver);
}
初始化SecurityManager
并且执行Filter链
2.SecurityManager
<!-- 定义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>
重要引用
①realm
包含两个重要方法 认证+授权
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
realm中,可以修改
进行密码的验证方式修改(使用sha-1算法,进行1024次散列),这个方法就是盐
public void initCredentialsMatcher() {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(HASH_ALGORITHM);
matcher.setHashIterations(HASH_INTERATIONS);
setCredentialsMatcher(matcher);
}
这是在ShiroFilterFacotoryBean初始化时就已经设置好的
注意如果不进行这个initCredentialsMatcher那个realm是不知道盐的算法的,需要在spring中配置
②sessionManager(会话管理器)处理会话的各种事物,包括session的存储方式,session失效处理,过期处理等
③cacheManager(缓存管理器)
3.SessionManager 会话管理器
<!-- 自定义会话管理配置 -->
<bean id="sessionManager" class="com.dq.shiro.security.SessionManager">
<property name="sessionDAO" ref="sessionDAO"/>
<!-- 配置会话监听器 -->
<property name="sessionListeners" ref="mySessionListener" />
<!-- 会话超时时间,单位:毫秒 -->
<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>
重要子类
①sessionDAO 用于对session信息的crud操作,即可以进行session持久化,并且指定用什么方式去保存session(redis echcache memcached)
②sessionListener 可以用于监听会话
public class MySessionListener implements SessionListener{
private Logger logger = LoggerFactory.getLogger(MySessionListener.class);
@Override
public void onStart(Session session) {
logger.info("会话**********************************");
logger.info("会话**********************************");
logger.info("会话创建:sessionId为" + session.getId());
logger.info("会话**********************************");
logger.info("会话**********************************");
}
@Override
public void onStop(Session session) {
logger.info("会话**********************************");
logger.info("会话**********************************");
logger.info("会话退出:sessionId为" + session.getId());
logger.info("会话**********************************");
logger.info("会话**********************************");
}
@Override
public void onExpiration(Session session) {
logger.info("会话**********************************");
logger.info("会话**********************************");
logger.info("会话过期:sessionId为" + session.getId());
logger.info("会话**********************************");
logger.info("会话**********************************");
}
}
4.SessionDAO
<!-- 自定义Session存储容器 -->
<bean id="sessionDAO" class="com.dq.shiro.security.CacheSessionDAO">
<property name="sessionIdGenerator" ref="sessionIdGen" />
<property name="activeSessionsCacheName" value="activeSessionsCache" />
<property name="cacheManager" ref="shiroCacheManager" />
</bean>
主要子类
①sessionIdGenerator
生成sessionId 可以自定义
public class SessionIdGen implements SessionIdGenerator {
/**
* @Description: TODO(Session ID 生成)
* @param: @param session
* @param: @return
* @throws
*/
@Override
public Serializable generateId(Session session) {
return IdGen.uuid();
}
}
②cacheManager(缓存管理器)
5.cacheManager缓存管理器
<!-- 定义授权缓存管理器 -->
<!-- <bean id="shiroCacheManager" class="com.minstone.common.security.shiro.cache.SessionCacheManager" // -->
<bean id="shiroCacheManager" class="com.dq.shiro.cache.RedisCacheManager">
<property name="redisManager" ref="redisManager" />
</bean>
public class RedisCacheManager implements CacheManager {
private static final Logger logger = LoggerFactory
.getLogger(RedisCacheManager.class);
// fast lookup by name map
@SuppressWarnings("rawtypes")
private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();
private RedisManager redisManager;
/**
* The Redis key prefix for caches
*/
private String keyPrefix = "shiro_redis_cache:";
/**
* Returns the Redis session keys
* prefix.
* @return The prefix
*/
public String getKeyPrefix() {
return keyPrefix;
}
/**
* Sets the Redis sessions key
* prefix.
* @param keyPrefix The prefix
*/
public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public <K, V> Cache<K, V> getCache(String name) throws CacheException {
logger.debug("获取名称为: " + name + " 的RedisCache实例");
Cache c = caches.get(name);
if (c == null) {
// initialize the Redis manager instance
redisManager.init();
// create a new cache instance
c = new RedisCache<K, V>(redisManager, keyPrefix);
// add it to the cache collection
caches.put(name, c);
}
return c;
}
public RedisManager getRedisManager() {
return redisManager;
}
public void setRedisManager(RedisManager redisManager) {
this.redisManager = redisManager;
}
}
自定义的缓存处理器需要实现org.apache.shiro.cache.CacheManager
实现的方法
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public <K, V> Cache<K, V> getCache(String name) throws CacheException {
logger.debug("获取名称为: " + name + " 的RedisCache实例");
Cache c = caches.get(name);
if (c == null) {
// initialize the Redis manager instance
redisManager.init();
// create a new cache instance
c = new RedisCache<K, V>(redisManager, keyPrefix);
// add it to the cache collection
caches.put(name, c);
}
return c;
}
6.redisManager
一个直接通过jedisPool操作jedis的类
无须赘述
7.运行流程
登录项目
首先会被Shiro核心Filter(ShiroFilterFacotryBean)中的Filter进行拦截
tips:过滤链的注释和例子
过滤器简称 对应的java类
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表示需要认证(登录)才能使用,FormAuthenticationFilter是表单认证,没有参数
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没有参数表示必须存在用户, 身份认证通过或通过记住我认证通过的可以访问,当登入操作时不做检查