之前用shiro参考官网写了简单的示例,对于shiro我们大致可以了解其主要的构造,网上有很多这样的示例,但是对于开发者来说,我们需要做的很少,可以说大部分模块都已经由框架本身帮助我们实现,我们需要关注的仅仅是框架开放且无法实现的模块,即验证模型构建,而java基本都是以面向接口未核心,那么我只需要完成核心接口的实现即可。
目前对于shiro的扩展主要包括以下几个部分:
1、过滤器的定义与实现。主要分为认证与鉴权两种,官方提供的认证过滤器有:anon、authc、authcBasic、user;鉴权包括:perms、port、rest、roles、ssl。我们可以根据自己的业务需求,对原有的过滤器进行扩展与重写。
2、token的自定义。一般用的token都是UsernamePasswordToken,这个基本可以帮我们实现用户名、密码这种模式的校验,但是有时候登录有多种参数,比如验证码、动态码等,可以通过自己扩展来实现。
3、realm的实现。这里就需要我们实现官方提供的接口AuthorizingRealm,里面有认证和授权的方法需要由我们实现,而我们需要做的就是构建认证与授权的模型,分别是AuthenticationInfo、AuthorizationInfo,具体的密码比对与角色权限验证都是框架实现,只要我们按照规范的模式进行模型的构造即可,这也是一种良好的设计思路。
4、session的管理。一般情况使用的是DefaultSessionManager,可以通过重定义其属性SessionDao来实现session的持久化管理,主要用于分布式保证会话一致性。
大体上学会这几个方面的内容也就可以针对实际业务通过shiro实现安全认证方面的功能了。
shiro和spring整合主要是由spring管理关键的对象,项目通过maven构建管理,相应pom文件主要引入shiro核心包与spring核心包
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.7</java.version>
<spring.version>4.2.6.RELEASE</spring.version>
<shiro.version>1.4.0</shiro.version>
<mysql.version>5.1.29</mysql.version>
</properties>
<dependencies>
<!--test-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0-RC2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<!--spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>-->
<!--aop-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.1</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!--db-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.18</version>
</dependency>
<!--commons-->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils-core</artifactId>
<version>1.8.3</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
<!--log-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.16</version>
</dependency>
<!--json-->
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
<classifier>jdk15</classifier>
</dependency>
<!--jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<!--servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
其次就是web.xml文件,主要设置相关的过滤器,这样才能在登录或访问页面时进入到相关的过滤器链中,实现登录权限校验:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/configs/spring-context.xml</param-value>
</context-param>
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/configs/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>/index.html</welcome-file>
</welcome-file-list>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>
使用的是springmvc,也引入了mvc的请求分发器。
最后就是spring上下文与shiro核心bean的引入了:
<bean id="formAuthc" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"/>
<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<property name="authenticationStrategy">
<!-- 多个Reaml认证中有一个认证成功即成功策略 -->
<bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
<!-- 第一个Reaml认证策略 -->
<!-- <bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy"></bean> -->
<!-- 必须所有Reaml都成功认证策略 -->
<!-- <bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"></bean> -->
</property>
<!-- <property name="realms">
<list>
<ref bean="otherRealm"/>
<ref bean="defaultRealm"/>
</list>
</property>-->
</bean>
<!-- 继承自AuthorizingRealm的自定义Realm,即指定Shiro验证用户登录的类为自定义的UserRealm.java -->
<bean id="defaultRealm" class="com.sucl.shirosecurity.security.DefaultRealm"/>
<bean id="otherRealm" class="com.sucl.shirosecurity.security.OtherRealm"/>
<!-- Shiro主过滤器本身功能十分强大,其强大之处就在于它支持任何基于URL路径表达式的、自定义的过滤器的执行 -->
<!-- Web应用中,Shiro可控制的Web请求必须经过Shiro主过滤器的拦截,Shiro对基于Spring的Web应用提供了完美的支持 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- Shiro的核心安全接口,这个属性是必须的 -->
<property name="securityManager" ref="securityManager"/>
<!-- 要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.html"页面 -->
<property name="loginUrl" value="/login.jsp"/>
<!-- 登录成功后要跳转的连接 -->
<property name="successUrl" value="/index.jsp"/>
<!-- 用户访问未对其授权的资源时,所显示的连接 -->
<!-- 若想更明显的测试此属性可以修改它的值,如unauthor.jsp,然后用[玄玉]登录后访问/admin/listUser.jsp就看见浏览器会显示unauthor.jsp -->
<!-- <property name="unauthorizedUrl" value="/no_permissions.jsp" /> -->
<!-- Shiro连接约束配置,即过滤链的定义 -->
<!-- 此处可配合我的这篇文章来理解各个过滤连的作用http://blog.csdn.net/jadyer/article/details/12172839 -->
<!-- 下面value值的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的 -->
<!-- anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种 -->
<!-- authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter -->
<property name="filterChainDefinitions">
<value>
<!-- anon表示此地址不需要任何权限即可访问 -->
/css/** = anon
/js/** = anon
/images/** = anon
/login.jsp = formAuthc
/authc.jsp = roles[admin]
/logout = logout
/** = authc
</value>
</property>
</bean>
<!-- Shiro默认会使用Servlet容器的Session,可通过sessionMode属性来指定使用Shiro原生Session -->
<!-- 即<property name="sessionMode" value="native"/>,详细说明见官方文档 -->
<!-- 这里主要是设置自定义的单Realm应用,若有多个Realm,可使用'realms'属性代替 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- Shiro 多Realm认证策略 -->
<property name="authenticator" ref="authenticator"></property>
<property name="sessionManager" ref="sessionManager"></property>
<!--<property name="realm" ref="defaultRealm"/>-->
<!-- 多Realm Bean 引用 -->
<property name="realms">
<list>
<ref bean="defaultRealm"></ref>
<ref bean="otherRealm"></ref>
</list>
</property>
<property name="rememberMeManager" ref="rememberManager"></property>
</bean>
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!--session超时时间-->
<property name="globalSessionTimeout" value="3600000"></property>
<!--session缓存-->
<property name="cacheManager" ref="cacheManager"></property>
<!--删除失效session-->
<property name="deleteInvalidSessions" value="true"></property>
<!--session校验器-->
<property name="sessionValidationSchedulerEnabled" value="true"></property>
<!--session本地存储-->
<property name="sessionDAO" ref="sessionDao"></property>
<!--是否将sessionId存储到cookie-->
<property name="sessionIdCookieEnabled" value="true"></property>
<!--默认cookie存储的值-->
<!--<property name="sessionIdCookie" value="sessionIdCookie"></property>-->
</bean>
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:encache.xml"></property>
</bean>
<bean id="sessionDao" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
<property name="cacheManager" ref="cacheManager"></property>
</bean>
<!--CookieRememberMeManager-->
<bean id="rememberManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cookie" ref="simpleCookie"></property>
<property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('2AvVhdsgUs0FSA3SDFAdag==')}"></property>
</bean>
<bean id="simpleCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<property name="httpOnly" value="true"></property>
<property name="maxAge" value="180000"></property>
<property name="name" value="rememberMe"></property>
</bean>
<!-- Shiro生命周期处理器 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 开启shiro注解-->
<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>
比较核心的有shiroFilter,这个和web.xml中名称要一致,其属性可以看该类的属性,对于必须的属性我们需要配置进去,有默认值的可以直接使用。
securityManager,这个作为shiro的核心,也需要我们进行管理或扩展,realm就是在这里管理的。
整体的流程如下:
1、定义登录请求与页面,比如定义了/login.jsp,可以配置其由authc拦截,也就是org.apache.shiro.web.filter.authc.FormAuthenticationFilter,页面中需要定义相关参数,如username、password,该名称必须固定;form请求必须为post类型,提交地址必须为login.jsp,或者为空。
2、登录请求提交后则由authc进行登录,主要是调用我们配置的realm其中的doGetAuthenticationInfo方法,该方法返回AuthenticationInfo对象,该对象主要包含principal、credential,其中principal可以随便定义,但是根据业务需求可以进行扩展,因为在SecurityUtils.getSubject().getPrincipals()中可以拿到,其次在权限校验的时候要用到该对象;credential必须与密码一致,这个框架会校验。
3、若有多个realm,即验证的方式有多样化,比如多表、数据库令牌验证等等,此时需要在shiro.xml中配置authenticator。关于多方式认证,一是可以通过构建多个realm,或者通过一个realm构建不同的principal,结合token实现。
本次示例地址:shiro-security
下次使用springboot与shiro整合,大体上应该差不多。