1.关于Shiro在Spring中的配置
1.直接在HIbernate中使用Ehcache的配置
2.在Shiro中使用了Ehcache的配置
securityManager:这个属性是必须的。
loginUrl :没有登录的用户请求需要登录的页面时自动跳转到登录页面,不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。
successUrl :登录成功默认跳转页面,不配置则跳转至”/”。如果登陆前点击的一个需要登录的页面,则在登录自动跳转到那个需要登录的页面。不跳转到此。
unauthorizedUrl :没有权限默认跳转的页面。
===============其权限过滤器及配置释义=======================
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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表示需要认证(登录)才能使用,没有参数
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没有参数表示必须存在用户,当登入操作时不做检查
注:anon,authcBasic,auchc,user是认证过滤器,
perms,roles,ssl,rest,port是授权过滤器
2.参考Shiro框架权限实践
.spring集成shiro
- <?xml version="1.0" encoding="UTF-8"?>
- <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
- version="2.5">
- <welcome-file-list>
- <welcome-file>login.jsp</welcome-file>
- </welcome-file-list>
- <!-- 加载spring的配置****begin -->
- <listener>
- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
- </listener>
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>classpath*:config/spring/appCtx-*.xml</param-value>
- </context-param>
- <!-- 加载spring的配置****end -->
- <!-- 加载Log4j的配置****begin -->
- <context-param>
- <param-name>log4jConfigLocation</param-name>
- <param-value>/WEB-INF/classes/log4j.properties</param-value>
- </context-param>
- <listener>
- <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
- </listener>
- <!-- 加载Log4j的配置****end -->
- <!--
- 解决Hibernate的Session的关闭与开启问题
- 功能是用来把一个Hibernate Session和一次完整的请求过程对应的线程相绑定。目的是为了实现"Open Session in View"的模式。例如: 它允许在事务提交之后延迟加载显示所需要的对象
- -->
- <filter>
- <filter-name>openSessionInViewFilter</filter-name>
- <filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>openSessionInViewFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
- <!-- 加载shiro的配置*********begin***** -->
- <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>
- </filter-mapping>
- <!-- 加载shiro的配置*********end***** -->
- <!-- 加载struts2的配置******begin****** -->
- <filter>
- <filter-name>struts2</filter-name>
- <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>struts2</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
- <!-- 加载struts2的配置*******end********* -->
- </web-app>
2.shiro的主要配置文件shiro.xml文件:
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xmlns:tx="http://www.springframework.org/schema/tx"
- xmlns:util="http://www.springframework.org/schema/util"
- xmlns:context="http://www.springframework.org/schema/context"
- xsi:schemaLocation="
- http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
- http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
- ">
- <!-- 自动扫描加载spring的bean*****begin********* -->
- <context:annotation-config />
- <context:component-scan base-package="com" />
- <!-- 自动扫描加载spring的bean*****end********* -->
- <!-- 加载spring的properties文件*****begin********* -->
- <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
- <property name="fileEncoding" value="utf-8" />
- <property name="locations">
- <list>
- <value>classpath*:/config/properties/deploy.properties</value>
- </list>
- </property>
- </bean>
- <!-- 加载spring的properties文件*****end******** -->
- <!-- 加载数据库的相关连接****************begin********** -->
- <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
- <!-- 基本属性 url、user、password -->
- <property name="url" value="${datasource.url}" />
- <property name="username" value="${datasource.username}" />
- <property name="password" value="${datasource.password}" />
- <property name="driverClassName" value="${datasource.driverClassName}"></property>
- <!-- 配置初始化大小、最小、最大 -->
- <property name="initialSize" value="${druid.initialPoolSize}" />
- <property name="minIdle" value="${druid.minPoolSize}" />
- <property name="maxActive" value="${druid.maxPoolSize}" />
- <!-- 配置获取连接等待超时的时间 -->
- <property name="maxWait" value="${druid.maxWait}" />
- <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
- <property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}" />
- <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
- <property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}" />
- <property name="validationQuery" value="${druid.validationQuery}" />
- <property name="testWhileIdle" value="${druid.testWhileIdle}" />
- <property name="testOnBorrow" value="${druid.testOnBorrow}" />
- <property name="testOnReturn" value="${druid.testOnReturn}" />
- <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
- <property name="poolPreparedStatements" value="${druid.poolPreparedStatements}" />
- <property name="maxPoolPreparedStatementPerConnectionSize" value="${druid.maxPoolPreparedStatementPerConnectionSize}" />
- <!-- 配置监控统计拦截的filters,如需防御SQL注入则加入wall -->
- <property name="filters" value="${druid.filters}" />
- <property name="connectionProperties" value="${druid.connectionProperties}" />
- </bean>
- <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
- <property name="dataSource" ref="dataSource"/>
- <!-- <property name="packagesToScan">-->
- <!-- <list>-->
- <!-- <value>com.wenc.*.po</value>-->
- <!-- </list>-->
- <!-- </property>-->
- <property name="packagesToScan"
- value="com.wenc.core.po" />
- <!-- <property name="mappingLocations"> 此处添加Java类和数据库表的映射关系|mappingLocations代替mappingResources -->
- <!-- <list>-->
- <!-- <value>classpath:/com/wec/po/**/*.hbm.xml</value> -->
- <!-- </list>-->
- <!-- </property>-->
- <property name="hibernateProperties">
- <props>
- <prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext</prop>
- <prop key="hibernate.dialect">${hibernate.dialect}</prop>
- <prop key="hibernate.hbm2ddl.auto">update</prop>
- <prop key="hibernate.show_sql">true</prop>
- <prop key="hibernate.format_sql">true</prop>
- <prop key="hibernate.query.substitutions">${hibernate.query.substitutions}</prop>
- <prop key="hibernate.default_batch_fetch_size">${hibernate.default_batch_fetch_size}</prop>
- <prop key="hibernate.max_fetch_depth">${hibernate.max_fetch_depth}</prop>
- <prop key="hibernate.generate_statistics">${hibernate.generate_statistics}</prop>
- <prop key="hibernate.bytecode.use_reflection_optimizer">${hibernate.bytecode.use_reflection_optimizer}</prop>
- <prop key="hibernate.cache.use_second_level_cache">${hibernate.cache.use_second_level_cache}</prop>
- <prop key="hibernate.cache.use_query_cache">${hibernate.cache.use_query_cache}</prop>
- <prop key="hibernate.cache.region.factory_class">${hibernate.cache.region.factory_class}</prop>
- <prop key="net.sf.ehcache.configurationResourceName">${net.sf.ehcache.configurationResourceName}</prop>
- <prop key="hibernate.cache.use_structured_entries">${hibernate.cache.use_structured_entries}</prop>
- </props>
- </property>
- </bean>
- <!-- 加载数据库的相关连接****************end********** -->
- <!-- spring的事务控制****************begin********** -->
- <!-- 开启AOP监听 只对当前配置文件有效 -->
- <aop:aspectj-autoproxy expose-proxy="true"/>
- <!-- 开启注解事务 只对当前配置文件有效 -->
- <tx:annotation-driven transaction-manager="transactionManager"/>
- <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
- <property name="sessionFactory">
- <ref bean="sessionFactory" />
- </property>
- <property name="globalRollbackOnParticipationFailure" value="true" />
- </bean>
- <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
- <tx:attributes>
- <tx:method name="do*" propagation="REQUIRED" />
- <tx:method name="save*" propagation="REQUIRED" />
- <tx:method name="up*" propagation="REQUIRED" />
- <tx:method name="del*" propagation="REQUIRED" />
- <tx:method name="sear*" propagation="REQUIRED" read-only="true" />
- <tx:method name="search*" propagation="REQUIRED" read-only="true" />
- <tx:method name="find*" propagation="REQUIRED" read-only="true" />
- <tx:method name="get*" propagation="REQUIRED" read-only="true" />
- </tx:attributes>
- </tx:advice>
- <aop:config expose-proxy="true" proxy-target-class="true">
- <aop:pointcut id="txPointcut" expression="execution(* com.wenc.*.service.*.*(..))" />
- <aop:advisor advice-ref="transactionAdvice" pointcut-ref="txPointcut" order="1"/>
- </aop:config>
- <!-- spring的事务控制****************end********** -->
- <!-- shiro的配置*************************begin********** -->
- <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
- <!-- 自定义的realm -->
- <property name="realm" ref="sampleRealmService"/>
- </bean>
- <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
- <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
- <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
- <property name="securityManager" ref="securityManager"/>
- <!-- 登陆页面的连接 -->
- <property name="loginUrl" value="/login.jsp"/>
- <!-- 身份验证后跳转的连接 -->
- <property name="successUrl" value="/loginAction.action"/>
- <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
- <property name="filters">
- <util:map>
- <entry key="authc">
- <bean class="org.apache.shiro.web.filter.authc.PassThruAuthenticationFilter"/>
- </entry>
- </util:map>
- </property>
- <!-- 指定过滤器
- Anon:不指定过滤器,不错是这个过滤器是空的,什么都没做,跟没有一样。
- Authc:验证,这些页面必须验证后才能访问,也就是我们说的登录后才能访问。
- 这里还有其他的过滤器,我没用,比如说授权
- -->
- <property name="filterChainDefinitions">
- <value>
- /loginAction.action=anon
- /** = authc
- </value>
- </property>
- </bean>
- <!-- shiro的配置*************************end********** -->
- </beans>
3.主要的实现类有三个分别是PersonAction,UserPermissionInterceptor,SampleRealmService,这三个之间的相互协作完成了shiro的整个认证和授权过程,下面我们来看各个类的作用:
- package com.wenc.test.service.web;
- @Controller
- public class PersonAction extends BaseAction implements ModelDriven<User> {
- private static Logger logger =Logger.getLogger(SampleRealmService.class);
- @Autowired
- private PersonService personService;
- public String login()throws Exception{
- //对用户输入的密码进行MD5加密
- String newPassword = CipherUtil.MD5Encode(info.getPassword());
- logger.info(info.getUsername()+"="+info.getPassword());
- Subject currentUser = SecurityUtils.getSubject();
- UsernamePasswordToken token = new UsernamePasswordToken( info.getUsername(), newPassword);
- //token.setRememberMe(true); //是否记住我
- try {
- /**currentUser.login(token) 提交申请,验证能不能通过,也就是交给shiro。这里会回调reaml(或自定义的realm)里的一个方法
- protected AuthenticationInfo doGetAuthenticationInfo() */
- currentUser.login(token);
- } catch (AuthenticationException e) { //验证身份失败
- logger.info("验证登陆客户身份失败!");
- this.addActionError("用户名或密码错误,请重新输入!");
- return "fail";
- }
- /**Shiro验证后,跳转到此处,这里判断验证是否通过 */
- if(currentUser.isAuthenticated()){ //验证身份通过
- return SUCCESS;
- }else{
- this.addActionError("用户名或密码错误,请重新输入!");
- return "fail";
- }
- }
- }
这个类的login方法是当我们输入用户名和密码之后,点击登录按钮所执行的方法,由于在数据库中用户的密码是密文形式,所以在进行用户身份验证,我们必须以同样的加密方式来加密用户在页面上输入的密码,然后将用户名和加密后的密码放入令牌(也就是token中),之后shiro会通过比对token中的用户名和密码是否与数据库中存放的真正的用户名和密码来确定用户是否为合法用户,而这个验证过程是shiro为我们完成的,当执行currentUser.login(token)方法的时候会触发验证过程,但是通常情况下这个验证过程是通过我们来自定义完成的,为此我们必须自己写一个realm类来继承shiro的AuthorizingRealm类并覆盖其AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)方法,来看SampleRealmService类,这个就是继承AuthorizingRealm并覆盖其方法后的类:
- package com.wenc.core.service;
- @Component
- public class SampleRealmService extends AuthorizingRealm {
- private static Logger logger =Logger.getLogger(SampleRealmService.class);
- @Autowired
- private PersonDAO personDAO;
- public SampleRealmService() {
- logger.info("-------AAA1------------------");
- setName("sampleRealmService");
- // setCredentialsMatcher(new Sha256CredentialsMatcher());
- }
- /**
- * 身份验证
- * @param authcToken 登陆Action封装的令牌
- */
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
- UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
- /**查询对应的用户是否存在*/
- User user =personDAO.getUser(token.getUsername(), token.getPassword().toString());
- logger.info(user);
- if( user != null ) {
- return new SimpleAuthenticationInfo(user.getId(), user.getPassword(), getName());
- } else {
- return null;
- }
- }
- /**
- * 授权
- * 注意:统一在struts的拦截器中处理,见UserPermissionInterceptor.java
- */
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
- Integer userId = (Integer) principals.fromRealm(getName()).iterator().next();
- logger.info("用户ID:"+userId);
- User user = personDAO.getUser(userId);
- if( user != null ) {
- SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
- for( Role role : user.getRoles() ) {
- info.addRole(role.getName());
- Set<Perms> set= role.getPermissions();
- logger.info(set);
- for(Perms perm:set){
- info.addStringPermission(perm.getActionName());
- }
- }
- return info;
- } else {
- return null;
- }
- }
- }
如同上面介绍的那样执行验证的过程就进入了身份认证方法体中,也就是在这里讲数据库中查询出来的真实的用户信息和token中的用户信息进行比对,当验证成功后跳转至strut.xml中配置的index.jsp页面,截图如下:
至此我们完成了用户身份验证过程,接下来我们介绍授权过程和通过shiro标签来介绍细粒度的权限控制。当我们点击“主页2”这个超链接的时候会被struts.xml文件中定义的拦截器拦截,拦截器UserPermissionInterceptor代码如下:
- package com.wenc.core.web.interceptor;
- public class UserPermissionInterceptor extends AbstractInterceptor {
- private static final long serialVersionUID = -2185920708747626659L;
- private static final Log logger = LogFactory.getLog(UserPermissionInterceptor.class);
- @Override
- public String intercept(ActionInvocation invocation) throws Exception {
- ActionContext ac = invocation.getInvocationContext();
- Map map = ac.getParameters();
- String actionName = ac.getName();
- String methodName = "";
- String[] _methodName = (String[]) map.get("method");
- if (_methodName != null) {
- methodName = _methodName[0];
- }
- logger.info("actionName:"+actionName+",方法名:"+methodName);
- Subject currentUser = SecurityUtils.getSubject();
- /**判断是否已经授权*/
- if(!currentUser.isPermitted(actionName)){
- logger.info("没有有权限");
- }
- return invocation.invoke();
- }
- }