Shiro
integrating `Shiro` into SSM to control the authority management :bowtie:
Install / Use
/learn @codingXiaxw/ShiroREADME
Shiro整合Web项目及整合后的开发
将Shiro框架整合到新的web项目中很简单,就是在web项目中导入Shiro的相关jar包以及整合jar包即可完成整合(是不是很简单...哈哈就是这么简单)。难的就是整合了Shiro框架后的web项目该如何进行开发,关于这一点,我将在下方通过一个demo演示用户的登录与退出及登录后的权限管理带你入门加入了Shiro框架后的web项目开发。
博客上也放了详细解讲,点击这里前往我的博客
**写在前边的话:**我在github上已经放了一个整合了Spring+SpringMVC+Mybatis的web项目(就是关于商品的增、删、改、查操作),接下来我要讲解的就是如何在这个项目中整合进我的Shiro框架,整合Shiro框架前的项目源码请点击这里前往我的github,并讲解了整合了Shiro框架后的web项目该如何进行开发,整合了Shiro框架后的完整源码请点击这里前往我的github。
用于创建表的sql语句见github上src包下的sql包。
开发环境
IDEA Spring3.x+SpringMVC+Mybatis+Shiro 没有用到maven管理工具。
1.需求
在一个整合了Spring+SpringMVC+Mybatis三个框架的web项目中再整合进Shiro框架,实现基于Shiro的权限管理机制。
2.导入jar包
在原先的项目基础上只需导入三个jar包即可:1.shiro-spring.jar。2.shiro-web.jar。3.shiro-core.jar。jar包见我github上的源代码。成功导入jar包,好,下一步,整合完毕。
项目相关jsp页面请在github上自行下载,我们这里只进行web后端功能的讲解。接下来在原先的项目基础上通过增加用户登录和退出的功能对用户进行权限管理来讲解如何使用Shiro 进行开发。
3.在web.xml中配置shiro的filter
在web系统中,shiro也是通过filter进行拦截的。filter拦截后将操作权交给Spring中配置的filterChain(过滤链儿),shiro提供了很多的filter。
在web.xml中配置shiro的filter,加入如下内容:
<!--在这里配置shiro的filter-->
<!-- shiro过虑器,DelegatingFilterProxy通过代理模式将spring容器中的bean和filter关联起来 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!-- 设置true由servlet容器控制filter的生命周期 -->
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<!-- 设置spring容器filter的bean id,如果不设置则找与filter-name一致的bean-->
<init-param>
<param-name>targetBeanName</param-name>
<param-value>shiroFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
4.applicationContext-shiro.xml
在src包下的config包下创建applicationContext-shiro.xml,在applicationContext-shiro.xml中配置web.xml中fitler对应spring容器中的bean以及SecurityManeger和自定义Realm的配置。内容如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-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 ">
<!--web.xml中shiro的filter对应的bean-->
<!-- Shiro 的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 -->
<property name="loginUrl" value="/login.action" />
<!--认证成功统一跳转到first.actio,建议不配置,不配置的话shiro认证成功会自动到上一个请求路径-->
<property name="successUrl" value="/first.action"/>
<property name="unauthorizedUrl" value="/refuse.jsp" />
<!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 -->
<property name="filterChainDefinitions">
<value>
<!--对静态资源设置匿名访问-->
/images/**=anon
/js/**=anon
/style/**=anon
<!--/**=anon 表示所有的url都可以匿名访问,anon是shiro中一个过滤器的简写,关于shiro中的过滤器介绍见-->
/**=anon
</value>
</property>
</bean>
<!--securityManage-->
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="customRealm" />
</bean>
<!--自定义realm-->
<bean id="customRealm" class="shiro.CustomRealm">
</bean>
</beans>
在applicationContext-shiro.xml的配置文件中,我们对系统的任何资源进行拦截,即通过/**=anon设置系统的任何资源都可以进行匿名访问。运行程序,在浏览器中输入http://localhost:8080/Shiro/即可访问系统,发现没有任何拦截即可以正常访问系统,因为shiro的过滤器没有对系统任何资源进行拦截,若想进行拦截,可以在上述配置文件中的<value></value>标签之间加入相应的拦截语句。下面就通过增加用户的登录实现通过Shiro的filter进行认证拦截的功能。即当访问被shiro拦截的系统资源时,系统会自动跳转到登录页面提醒用户需要经过用户登录认证后才能正常访问。
5.用Shiro实现登录认证
5.1原理
用户登录是在一个表单进行的,所以这里我们需要通过shiro的一个表单过滤器(FormAuthenticationFilter)进行实现,原理如下:
用户没有认证时,请求loginurl进行认证,输入用户名和密码点击登录时将用户身份和用户密码提交数据到loginurl,然后FormAuthenticationFilter进行拦截取出request中的username和password(FormAuthenticationFilter源码中将username和password两个参数名称写死了,而我们今后是可以将这两个参数名称写在配置文件中的),然后FormAuthenticationFilter会调用realm传入一个token(将username和password传入到token中),realm认证时根据username在数据库中查询用户信息(将在数据库中查询到的信息保存在在Activeuser.java对象中,包括 userid、usercode、username、menus),然后返回一个authenticationInfo。如果查询不到,realm就返回null,同时FormAuthenticationFilter会向request域中填充一个参数(记录了异常信息)。
5.2登录的代码实现
可想而知该代码在控制器Controller中实现,创建一个LoginController.java,代码如下:
@Controller
public class LoginController
{
@RequestMapping("/login")
public String login(HttpServletRequest request) throws Exception
{
//如果登录失败从request中获取认证异常信息,shiroLoginFailure就是shiro异常类的全限定名
String exceptionClassName= (String) request.getAttribute("shiroLoginFailure");
//根据shiro返回的异常类路径判断,抛出指定异常信息
if(exceptionClassName!=null){
if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
//最终会抛给异常处理器
throw new CustomException("账号不存在");
} else if (IncorrectCredentialsException.class.getName().equals(
exceptionClassName)) {
throw new CustomException("用户名/密码错误");
} else if("randomCodeError".equals(exceptionClassName)){
throw new CustomException("验证码错误");
} else{
throw new Exception();//最终在异常处理器生成未知错误
}
}
}
5.3配置认证拦截过滤器
在applicationContext.xml的<bean>标签中加入如下标签配置:
<!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 -->
<property name="loginUrl" value="/login.action" />
并在<value>标签之间加入相应的拦截语句:
<!-- -/**=authc 表示所有的url都必须认证通过才可以访问- -->
/** = authc
<!--/**=anon 表示所有的url都可以匿名访问-->
可以匿名访问的页面我们以后再配置
运行服务器,访问系统首页发现系统会对我们访问的资源进行拦截并退回到登录页面,但是这里会有个问题发现登录页面的静态资源也被拦截了,所以我们应在<value>标签之间加入对静态资源设置匿名访问的设置:
<!--对静态资源设置匿名访问-->
/images/**=anon
/js/**=anon
/style/**=anon
<!--请求这个地址就自动退出-->
/logout.action=logout
<!--商品查询需要商品查询权限-->
/items/queryItems.action=perms[item:query]
/items/editItems.action=perms[item:edit]
<!-- -/**=authc 表示所有的url都必须认证通过才可以访问- -->
/** = authc
<!--/**=anon 表示所有的url都可以匿名访问-->
可以匿名访问的页面我们以后再配置
然后运行程序,访问系统资源时系统发现用户信息没有得到认证所以会退回到登录页面让你进行登录,你只有输入了密码为111111后才能成功完成登录,因为我们在自定义CustomRealm.java文件中只是模拟从数据库中查到的数据(我们设置查到的密码为111111)。登录成功后便可进行系统的访问了,但是登录成功后只能访问系统的首页,因为我们还没有对该用户进行权限分配指定该用户可以对系统的哪些资源进行操作了,所以这里当然只能访问系统首页。当然运行程序之前你得完成自定义CustomRealm的代码,我们采用前篇文章的自定义CustomRealm的内容,如下:
public class CustomRealm extends AuthorizingRealm
{
//注入service
@Autowired
private SysService sysService;
//设置realm的名称
@Override
public void setName(String name) {
super.setName("customRealm");
}
//用于认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//token是用户输入的
//第一步:丛token中取出身份信息
String userCode= (String) token.getPrincipal();
//第二步:根据用户输入的userCode丛数据库查询
//模拟丛数据库查询到的密码
String password="111111";
//如果查不到返回null,
//如果查询到,返回认证信息AuthenticationInfo
///将activeUser设置到simpleAuthenticationInfo
SimpleAuthenticationInfo simpleAuthenticationInfo=new
SimpleAuthenticationInfo(userCode,password,this.getName());
return simpleAuthenticationInfo;
}
//用于授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
}
这样便完成用户的认证功能,接下来是退出功能。
6.退出
退出功能就是当用户点击退出按钮时清楚保存在session中信息,这个功能不用我们实现,交给Shiro的LogoutFilter过滤器即可实现:当我们访问一个退出的url时,由LogoutFilter拦截住,然后清楚session。
6.1配置退出过滤器
在applicationContext-shiro.xml的<value>标签中加入如下内容:
<!--请求这个地址就自动退出-->
/logout.action=logout
即完成清楚session即退出系统的功能。
7.实现用户成功登录后将认证信息显示在页面上
需求:1.认证后用户菜单在首页显示。2.认证后用户的信息(例如用户名)在页头显示。
7.1修改自定义Realm设置完整的认证信息
先前我们通过realm在数据库中通过用户名查询到的用户信息只有密码,而现在我们需要查询到的数据包括用户可以操作的用户菜单、usercode用户id、username用户名等。
我们先将这些信息用静态代码实现(即仍然没有涉及到数据库的查询):
//模拟丛数据库查询到的密码
String password="111111";
//activeUser就是用户的身份信息
ActiveUser activeUser=new ActiveUser();
activeUser.setUserid("zhangsan");
activeUser.setUsercode("zhangsan");
activeUser.setUsername("张三");
//根据用户id取出菜单
//通过service取出菜单
List<SysPermission> menus=null;
try {
menus=sysService.findMenuListByUserId("zhangsan");
} catch (Exception e) {
e.printStackTrace();
}
//将用户菜单设置到activeUser
activeUser.setMenus(menus);
//如果查不到返回null,
//如果查询到,返回认证信息AuthenticationInfo
///将activeUser设置到simpleAuthenticationInfo
SimpleAuthenticationInfo simpleAuthenticationInfo=new
SimpleAuthenticationInfo(activeUser,password,this.getName());
然后修改first.action(在控制器FirstActio
