shiro权限框架学习
目录
- 1.Apache Shiro 简述
- 2.shiro与Spring的整合
- 3.总结
1.Apache Shiro 简述
1.1概念
Apache Shiro是Java的一个安全框架。它不仅功能强大,而且使用简单,为开发人员提供了一个直观全面的认证、授权、加密、会话管理等解决方案。
1.2与Spring Security对比
- shiro配置更加简单易懂,Spring Security的配置相对复杂一些;
- shiro可以被整合到各种框架,可以独立运行,Spring Security不能脱离Spring框架;
- shiro不仅限于web中使用,可以工作在其他应用环境中;
- Spring Security的权限细粒度更高。
1.3基本功能
主要功能:
Authentication
身份认证/登录,验证用户是不是拥有相应的身份;
Authorization
授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Manager
会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
Cryptography
加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
其他功能:
Web Support
Web支持,可以非常容易的集成到Web环境;
Caching
缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
Concurrency
shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
Testing
提供测试支持;
Run As
允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me
记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
1.4核心组件
三个核心组件:Subject, SecurityManager 和 Realm
1.Subject即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。
Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
2.SecurityManager它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
3.Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。
2.shiro与Spring的整合
shiro整合步骤
- 1.引入shiro依赖jar包
- 2.在web.xml里配置shiro的Fliter
- 3.引入spring-shiro.xml整合文件,利用shiro配置过滤路径
- 4.自定义realm,在reaml里写登录认证的代码和授权的代码
- 5.测试
2.1引入shiro依赖jar包
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.8.3</version>
</dependency>
2.2在web.xml里配置shiro的Fliter
<!-- shiro和Spring整合后,shiroFilter要交给Spring来管理,Spring要生成一个shiroFilter
的bean,然后把这个filterbean交给servlet,从而生效 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<!--目标filter的生命周期 将spring生成的filter交给web容器管理 -->
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<!-- 如果用shiro做拦截所有的操作,这里要用"/*",不能用"/",它不认识 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
DelegatingFilterProxy类的作用:
这个类是spring-web模块里的包,间接的继承了Servlet的Filter,作用是当容器启动的时候,按Spring配置文件里配置的Filter名字实例化某个Filter类的实体bean。也就是说,Spring配置文件的bean的id名要和web.xml里的filtername配置的名字要一致。
targetFilterLifecycle=true的作用:
targetFilterLifecycle默认值为false,表示被DelegatingFilterProxy代理生成的Filter的生命周期由Spring容器来管理。如果为true,表示让Servlet容器管理。我们现在需要shiro的Filter被代理生成之后,就立即被容器加载,所以应该设置为true,让Serlvet容器来管理其生命周期。
2.3引入spring-shiro.xml整合文件,利用shiro配置过滤路径
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<!--shiro和spring整合之后,shiro的组件需要以bean的方式交给spring来管理,即shiro的bean的生命周期托付给bean。先声明下这个类-->
<beanid="lifeCycleBeanProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean>
<!-- Spring容器通过动态代理的方式,为lifeCycle生成代理对象,所以需要用depeds-on指定一下 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifeCycleBeanProcessor">
<!-- proxyTargetClass 默认值是false,表示只能用jdk代理
如果是true的话,表示启用cglib代理-->
<property name="proxyTargetClass" value="true"></property>
</bean>
<!--虽然lifeCycle是基于接口的,理应被jdk代理,但是,设计者要求必须被cglib代理,要不然shiro的很多组件不能被生成,所以需要强制开启cglib代理-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
<!-- 配置shiro的 安全管理器,shiro会自动帮你做登录认证和权限认证,但是你得给shiro提供登录的资料(比如用户名和密码)以及权限认证的资料(比如用户拥有哪些权限)
这些提供资料的代码,会写在一个类里,这个类需要开发人员自己写,一般把这个类叫做AuthRealm,
这个类必须得继承一个类,叫做AuthorizingRealm,之后继承这个类,并且在安全管理器里配置一下,shiro才能知道有这么个东西,才能给你干活
-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--注册自己写的AuthRealm, property 的name是realm,固定,写死 -->
<property name="realm" ref="AuthRealm"></property>
</bean>
<bean id="AuthRealm" class=" shiro.AuthRealm">
<!--添加加密的算法 能够自动的去调用 然后与用户传入的token对比 -->
<property name="credentialsMatcher" ref="authEnctype"></property>
</bean>
<!-- 配置自己定义的加密管理器 -->
<bean id="authEnctype" class=" shiro.AuthCredential"></bean>
<!-- 权限管理器,想干活,得让领导(安全管理器)知道,所以需要配置一下 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"></property>
</bean>
<!-- 配置shrioFilter bean 目的是让Spring容器代理生成Filter
这个名字得和web.xml里配置shiroFilter的名字一致,因为Spring是根据bean的id来管理的
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"></property>
<!--配置欢迎页-->
<property name="loginUrl" value="/login.jsp"></property>
<!--配置url过滤路径-->
<property name="filterChainDefinitions">
<value>
<!--关键字 authc拦截
anon 表示放行
-->
/validate/doLogin=anon <!--配置登录验证的访问路径放行,要不然接受不到传过来的登录名和密码-->
/staticfile/**=anon <!--静态资源文件需要放行,因为需要展示图片,css,和js等-->
/**=authc <!--除了上述两个路径资源被放行外,其他资源全部拦截-->
</value>
</property>
</bean>
</beans>
注意:
- shiroFilter名称和web.xml中定义名称相同
- 需要创建自定义reaml类和加密算法(即上述代码中AuthRealm和AuthCredential)
2.4自定义realm,在reaml里写登录认证的代码和授权的代码
2.4.1登录认证
登录Controller:
@RequestMapping("/validate/doLogin")
public String login(String username,String password){
//1.做非空校验
//2.subjec代表当前登录的用户,之所以用subject,是因为在安全领域
//登录系统的对象并不是一个真正的用户,可能是一个线程,也可能是一个真实的用户,
//所以subject的含义更广
Subject subject=SecurityUtils.getSubject();
if(StringUtils.isEmptyOrWhitespaceOnly(username)||
StringUtils.isEmptyOrWhitespaceOnly(password)){
subject.getSession().setAttribute("loginFailed", 2);
return "redirect:/";
}
//3.usernamePasswordToken,我们一般管它叫做令牌,令牌里装的就是用户提交的用户名和密码
//一会,subject就会拿着这个令牌去做安全验证,如果通过的话,就跳转到欢迎页面,如果失败,就返回登录页面
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
//4.当subject执行login的方法的时候,会去AuthRealm里去执行登录验证的方法。因为在配置文件里
//有配置过这个AuthRealm,所以它能找到。login方法会抛异常,当认证失败的时候,它会抛异常
try {
subject.login(token);
subject.getSession().setAttribute("username", username);
return "forward:/index";
} catch (Exception e) {
subject.getSession().setAttribute("loginFailed", 1);
return "redirect:/";
}
}
AuthRealm(登录认证)代码:
public class AuthRealm extends AuthorizingRealm{
@Autowired
private UserService userService;
//认证管理 登陆认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//强制转换
/**
* 验证思路
* 通过用户传入的usename 去数据库中查找对象
* 把对象传给shiro shiro自动的根据对象中的密码做匹配
*/
UsernamePasswordToken loginToken = (UsernamePasswordToken) token;
//获得用户传入的username
String username = loginToken.getUsername();
//通过username查询出对象
User user = userService.findUserByUserName(username);
/**
* shiro安全认证的参数
* 1.查询回来的对象
* 2.正确的密码 user.getPassword()
* 3.当前info对象的名称
*/
AuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
//当info对象创建完成后shiro 会在内部做比较 如果传入的密码和数据中的密码相同则放行 否则抛出异常
return info;
}
}
2.4.2权限管理
页面添加shiro标签:
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>
<shiro:hasPermission name="系统首页">
<span id="topmenu" onclick="toModule('home');">系统首页</span><span id="tm_separator"></span>
</shiro:hasPermission>
权限管理代码:
public class AuthRealm extends AuthorizingRealm{
@Autowired
private UserService userService;
@Override //权限管理的资料的提交
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//1.在前台页面加上shiro的标签,怎么用,长什么样,请见页面
//2.根据subject的session,拿到当前已登录用户的用户名,根据这个用户名,去查询它到底具有什么权限
//3.把这些权限的名字拿出来,一个一个的装到集合里
//4.把shiro的权限管理员,叫出来,让它干活
List<String> list=new ArrayList<>();
Subject subject=SecurityUtils.getSubject();
String username=(String) subject.getSession().getAttribute("username");
//通过登录用户名查找对应的用户,并且获得其拥有的权限
User user=userService.findUserHasModules(username);
List<Module> moduleList=user.getModules();
System.out.println(username+":"+moduleList.size());
for(Module m:moduleList){
list.add(m.getName());
}
//当前的list里,就装了用户所有拥有的权限标识名,
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
info.addStringPermissions(list);
return info;
}
}
2.4.3加密
public class AuthCredential extends SimpleCredentialsMatcher{
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
//1.从token拿出 登录用户输入的用户名密码
//2.利用hashMd5加密算法,把用户名和密码输入进去,得到加密后的密码,
//3.把加密后的密码放回到token,找到shiro的密码校验管理员,它会自动的帮你做验证
UsernamePasswordToken loginToken=(UsernamePasswordToken) token;
String username=loginToken.getUsername();
String password=String.valueOf(loginToken.getPassword());
//这步得到的密码是根据用户在页面传来的用户名和密码加密得的密码
String enctypePassword=Enctype.md5(password,username);
loginToken.setPassword(enctypePassword.toCharArray());
//现在 loginToken里装的密码是用户在页面上输入的用户名+密码得到的加密密码
//info,装的是数据库用户的的真实密码,这个真实密码是通过AuthReaml里的user.getPassword传进来的
//new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
//最后,还差一步,需要在shiro-spring的整合文件,指定一下当前所用的加密管理类
return super.doCredentialsMatch(loginToken, info);
}
}
2.5测试
3.总结
从上文可以看出shiro配置简单,使用方便,功能强大,是一个优秀的框架。当然,它还远远不止这些,本文只是简单介绍概念和理清思路。若想熟练掌握shiro,还需要更深入的学习。