参考了http://blog.csdn.net/frankcheng5143/article/details/50836619这篇文章。感谢GW_Cheng大神的启发。
参考shiro官方文档将shiro和spring集成。spring的基本配置默认都懂,不说了。项目用maven管理,这个也不细说了,大不了启动的时候看报错缺什么文件写什么依赖吧- -哈哈。后面我会做一个完整项目放在github上。
1、首先确定自己的权限方案是:为用户分配角色,角色拥有某种权限。而且是用数据库存储用户信息,包括用户资料、角色、权限信息。因此要建立5张表。分别是:
用户表,存储用户的相关信息
角色表,存储角色的相关信息
权限表,存储权限的相关信息
用户角色表,存储用户和角色的对应关系 一对多
角色权限表,存储角色和权限的对应关系 一对多
详情看大神的博客吧。
2、在web.xml中定义shiro的过滤器
<filter>
<filter-name>shiroFilterfilter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
<init-param>
<param-name>targetFilterLifecycleparam-name>
<param-value>trueparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>shiroFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
2、spring-shiro.xml
这个文件会 import到spring的核心配置文件中。有人叫applicationcontext.xml,有人叫spring-core.xml……主要看自己习惯吧。反正web.xml里面写对了就能识别了。spring-core.xml里面记得配一个数据库连接池,druid、c3p0皆可。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/login.jsp" />
<property name="filterChainDefinitions">
<value>
/admin/**=authc
value>
property>
bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor">bean>
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.Md5CredentialsMatcher">bean>
<bean id="shiroCacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager">bean>
<bean id="jdbcRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm">
<property name="credentialsMatcher" ref="credentialsMatcher">property>
<property name="permissionsLookupEnabled" value="true">property>
<property name="dataSource" ref="dataSource">property>
<property name="authenticationQuery"
value="SELECT password FROM sec_user WHERE user_name = ?">property>
<property name="userRolesQuery"
value="SELECT role_name from sec_user_role left join sec_role using(role_id) left join sec_user using(user_id) WHERE user_name = ?">property>
<property name="permissionsQuery"
value="SELECT permission_name FROM sec_role_permission left join sec_role using(role_id) left join sec_permission using(permission_id) WHERE role_name = ?">property>
bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="jdbcRealm">property>
<property name="cacheManager" ref="shiroCacheManager">property>
bean>
beans>
3、springmvc配置,让springmvc控制权限认证后的跳转。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd ">
<import resource="classpath:spring/spring-shiro.xml" />
<context:component-scan base-package="com.test.controller">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
context:component-scan>
<mvc:view-controller path="/" view-name="index" />
<mvc:annotation-driven />
<mvc:resources location="/assets/" mapping="/assets/**">mvc:resources>
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
bean>
<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>
<bean
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.apache.shiro.authz.UnauthorizedException">
/error/unauthorized
prop>
<prop key="org.apache.shiro.authz.UnauthenticatedException">
/error/unauthorized
prop>
props>
property>
bean>
beans>
4、shiro的一些功能会用到缓存(比如remenber me),先配置好。(这一块我没有详细理解)
spring-cache.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache-3.1.xsd">
<cache:annotation-driven cache-manager="cacheManager"/>
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cacheManager-ref="ehcache"/>
<bean id="ehcache"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
p:configLocation="classpath:ehcache.xml"/>
beans>
ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://www.ehcache.org/ehcache.xsd">
<diskStore path="java.io.tmpdir/ehcache" />
<defaultCache
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false" />
<cache name="userIsExists"
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
memoryStoreEvictionPolicy="LRU" />
<cache name="getUserById"
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
memoryStoreEvictionPolicy="LRU" />
ehcache>
配置部分到此结束,暂时没有用到MyBatis。
5、下面分析代码。主要是2个controller。
我们在shiro的配置里面使用默认的jdbcRealm,所以这个也不用自己写了,直接用。
登录的controller:
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
token.setRememberMe(true);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
if (subject.isAuthenticated()) {
request.getSession().setAttribute("user",user);
SavedRequest savedRequest = WebUtils.getSavedRequest(request);
// 获取保存的URL
if (savedRequest == null || savedRequest.getRequestUrl() == null) {
return "admin/home";
} else {
return "forward:" + savedRequest.getRequestUrl();
}
} else {
return "login";
}
} catch (IncorrectCredentialsException e) {
msg = "登录密码错误. Password for account " + token.getPrincipal() + " was incorrect.";
model.addAttribute("message", msg);
System.out.println(msg);
}
在其他的Controller中,使用注解来做权限控制访问。
// 只用同时具有user:view和user:create权限才能访问
@RequiresPermissions(value = { "user:view", "user:create" }, logical = Logical.AND)
@RequestMapping(value = "/admin/auth")
public String adminWithAuth() {
return "admin/withauth";
}
当没有权限就去访问时,会抛出对应的异常。
6、学习心得体会。
1)文中查到一个用户之后,怎么知道他的权限呢?其过程是这样的:
id="jdbcRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm">
<property name="credentialsMatcher" ref="credentialsMatcher">property>
<property name="permissionsLookupEnabled" value="true">property>
<property name="dataSource" ref="dataSource">property>
<property name="authenticationQuery"
value="SELECT password FROM sec_user WHERE user_name = ?">property>
<property name="userRolesQuery"
value="SELECT role_name from sec_user_role left join sec_role using(role_id) left join sec_user using(user_id) WHERE user_name = ?">property>
<property name="permissionsQuery"
value="SELECT permission_name FROM sec_role_permission left join sec_role using(role_id) left join sec_permission using(permission_id) WHERE role_name = ?">property>
jdbcrealm里面写了一些查询角色和授权的sql语句,在使用subject.login(token);登录之后就会自动根据数据库查到的信息,为用户分配角色和授权。
角色和权限实现的方式有很多种,文中只是方案之一。希望自己在未来深入了解之后,也能写出属于自己的解决方案吧。
2)在实现自定义的realm时(这个是后续学习的关键),可以使用mybatis来查数据库中的数据,自己做权限的分配。
3)密码如果使用了salt混淆来生成md5,jdbcRealm里面的相应配置我还没有弄清除具体怎么操作。如果saltStyle配了Column属性,sql数据库里user表似乎就要多加一列salt了。或者用external属性,将username作为salt。时间有限,先直接用没有混淆的MD5加密密码吧。