shiro安全框架

Posted kz2017

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了shiro安全框架相关的知识,希望对你有一定的参考价值。

 

 

应用:

认证 授权 加密 会话管理(单点登录)  缓存 与web集成

技术分享图片

Subject主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;

SecurityManager安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;

Realm域,Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源

 

使用步骤:

 

过滤器的配置:

共有10个过滤器 但是实际使用的时候只要配置一个核心过滤器就行了。

<!--注意:如果是和struts2整合的话,shiro的filter必须在struts2的filter之前,否则action无法创建-->
<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>

过滤器种类:

技术分享图片

 

产生代理类的方式:

<!--放在applicationContext.xml事务管理器声明前-->
<!--产生Shiro控制器的方式 使用cglib生成代理-->
<aop:aspectj-autoproxy proxy-target-class="true" />

 

applicationContext-shiro.xml(web层):

<?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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<description>Shiro与spring整合的配置文件(天然整合)</description>
<!-- SecurityManager配置 -->
<!-- 配置Realm域 -->
<!-- 密码比较器 -->
<!-- 代理如何生成? 用工厂来生成Shiro的相关过滤器 -->
<!-- 配置缓存:ehcache缓存 -->
<!-- 安全管理 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
2<property name="realm" ref="authRealm" /><!-- 引用自定义的realm -->
<!-- 二级缓存 -->
<property name="cacheManager" ref="shiroEhcacheManager" />
</bean>
<!-- 自定义权限认证 -->
<bean id="authRealm" class="com.zixue.shiro.AuthRealm">
<!-- class由程序员定义 -->
<property name="userService" ref="userService" />
<!-- 自定义密码加密算法 -->
<property name="credentialsMatcher" ref="passwordMatcher" />
<!-- ref由程序员定义 -->
</bean>
<!-- 设置密码加密策略 md5hash -->
<bean id="passwordMatcher" class="com.zixue.shiro.CustomCredentialsMatcher" />
<!-- class由程序员定义 -->
<!-- filter-name这个名字的值来自于web.xml中filter的名字 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><!--这个才是生产过滤器的根源  web.xml的DelegatingFilterProxy并不能直接生成-->
<property name="securityManager" ref="securityManager" />
<!--登录页面 -->
<!--接收loginAction中的subject.login(token)方法,调用了Realm域中的认证方法(反射来实现调用)-->
<property name="loginUrl" value="/index.jsp"></property>
<!-- 登录成功后 -->
<property name="successUrl" value="/home.action"></property>
<property name="filterChainDefinitions">
<!-- /**代表下面的多级目录也过滤 -->
<value>
/index.jsp* = anon <!-- (过滤器名) -->
/home* = anon
/sysadmin/login/login.jsp* = anon
/sysadmin/login/logout.jsp* = anon
/login* = anon
/logout* = anon
/components/** = anon
/css/** = anon
/images/** = anon
/js/** = anon
/make/** = anon
/skin/** = anon
/stat/** = anon
/ufiles/** = anon
/validator/** = anon
/resource/** = anon
/** = authc     <!--根目录下所有资源及子目录 -->
/*.* = authc       <!-- 本目录所有资源 -->
</value>
</property>
</bean>
<!-- 用户授权/认证信息Cache, 采用EhCache 缓存 -->
<bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml" />
<!-- 引入ehcache-shiro.xml -->
</bean>
<!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<!-- 生成代理,通过代理进行控制 -->
<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>
</beans>

 

 

在applicationContext.xml文件中加载shiro配置文件:

<import resource="classpath:spring/applicationContext-shiro.xml"></import>

ehcache-shiro.xml 缓存(和applicationContext.xml一起)

<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="shiroCache">
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"/>
</ehcache>

 

编写密码比较器:

1.Encrypt.java(使用Md5Hash算法  放在工具包下)

public class Encrypt {
/*
* 散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,
* 常见的散列算法如MD5、SHA等。一般进行散列时最好提供一个salt(盐),比如加密密码“admin”,
* 产生的散列值是“21232f297a57a5a743894a0e4a801fc3”,
* 可以到一些md5解密网站很容易的通过散列值得到密码“admin”,
* 即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,
* 如用户名和ID(即盐);这样散列的对象是“密码+用户名+ID”,这样生成的散列值相对来说更难破解。
*/
//高强度加密算法,不可逆
public static String md5(String password, String salt){
return new Md5Hash(password,salt,2).toString();
}
public static void main(String[] args) {
System.out.println(new Md5Hash("123456","tony",2).toString());
}
}

 

2.密码比较器 CustomCredentialsMatcher.java(和web并级的shiro包下)

public class CustomCredentialsMatcher extends SimpleCredentialsMatcher {
/**
* 提供一个专门用于进行密码比较的方法
* token:代表用户在界面输入的用户名密码
* AuthenticationInfo info: 代表数据库中的用户信息
* 
* Md5Hash算法
* 将用户在界面上输入的明文-----------------加密成密文
* 将用户在数据库中的密文获取出来---------------跟用户输入的明文加密后的结果进行比较------------如果相同证明密码也正确,此时返回值就是true
* 
* 返回值:  true代表密码比较成功
*        false代表密码比较失败,此时程序也会报出异常
*/
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
//1.向下转型 
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//2.将用户输入的原始密码进行加密    Md5Hash的参数说明:第一个参数是明文     第二个参数:代表盐     第三个参数:迭代次数
//new Md5Hash(new String(upToken.getPassword()),upToken.getUsername(),2);
String encryptPwd = Encrypt.md5(new String(upToken.getPassword()), upToken.getUsername());
//3.跟数据库串密文进行比较
String pwd = info.getCredentials().toString();
//return encryptPwd.equals(pwd);
return super.equals(pwd, encryptPwd);
}
}

 

配置(applicationContext-shiro.xml已配好 记得修改路径):

//设置密码加密策略 md5hash
<bean id="passwordMatcher" class="com.zixue.shiro.CustomCredentialsMatcher">

 

编写自定义Realm域:

作用:

1.调用业务层和CustomCredentialsMatcher密码比较器(所以需要注入属性)

2.进行认证和授权操作

 

AuthRealm.java(和web并级的shiro包下):

public class AuthRealm extends AuthorizingRealm {    
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}

/**
* 认证   (登录)
* AuthenticationInfo  返回值:代表用户的认证信息
 *                     如果返回值不为null,说明用户名正确,程序就会继续定位到密码比较器,进行密码的比较
*                     如果返回值为null,程序就会抛出异常
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//向下转型
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//1.调用业务方法,实现根据用户名查询
List<User> userList = userService.find("from User where userName=?", User.class, new String[]{upToken.getUsername()});
//2.如果用户存在,就一步验证密码是否正确
if(userList!=null && userList.size()>0){
User user = userList.get(0);//取出查询的用户对象
//用户名正确了    第一个参数:用户对象       第二个参数:代表用户的密码     第三个参数:realm的名字,只要是一个字符串就可以
return new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());//返回密码比较器的类(进入我们注入的密码比较器的对象)
}
//3.返回结果
return null;
}


/**
 * 授权---------------在jsp页面碰到shiro标签时,就要执行授权方法
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
//1.获取当前登录的用户信息
User user = (User)pc.fromRealm(this.getName()).iterator().next();
//2.通过对象导航,得到角色列表
Set<Role> roleSet = user.getRoles();
List<String> moduleList = new ArrayList<String>();
//3.遍历角色列表,得到每个角色 
for (Role role : roleSet) {
//通过对象导航,得到角色下面的模块列表
Set<Module> moduleSet = role.getModules();
for (Module module : moduleSet) {
if(!moduleList.contains(module.getName())){
moduleList.add(module.getName());
}
}
}
//设置返回值
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(moduleList);
return info;
}
}

 

将编写的AuthRealm域配置好(applicationContext-shiro.xml已配好 记得修改路径):

//自定义权限认证
<bean id="autoRealm" class="com.zixue.shiro.AuthRealm">
<property name="userService" ref="userService"/>
//自定义密码加密算法
<propetty name="credentialsMatcher" ref="passwordMatcher">
</bean>

 

登陆操作:

loginAction

作用:应用程序与Shiro框架进行交互的Subject对象获取

public class LoginAction extends BaseAction {
private static final long serialVersionUID = 1L;
private String username;
private String password;
//SSH传统登录方式
public String login() throws Exception {
//        if(true){
//            String msg = "登录错误,请重新填写用户名密码!";
//            this.addActionError(msg);
//            throw new Exception(msg);
//        }
//        User user = new User(username, password);
//        User login = userService.login(user);
//        if (login != null) {
//            ActionContext.getContext().getValueStack().push(user);
//            session.put(SysConstant.CURRENT_USER_INFO, login);    //记录session
//            return SUCCESS;
//        }
//        return "login";            if(UtilFuns.isEmpty(username)){                
return "login";
}
try {
//1.应用程序与Shiro框架进行交互的Subject对象如何获取 ?
Subject subject = SecurityUtils.getSubject();
//2.AuthenicationToken是接口  所以只能new子类的对象
AuthenticationToken token = new UsernamePasswordToken(username, password);
//3.实现登录操作
subject.login(token);//进入AuthRealm类中的方法doGetAuthenticationInfo
//4.从Shiro中获取登录的用户信息
User user = (User)subject.getPrincipal();
//5.将用户信息存入session中
session.put(SysConstant.CURRENT_USER_INFO, user);
} catch (Exception e) {
e.printStackTrace();
request.put("errorInfo", "用户名或密码错误!!");
return "login";
}
return SUCCESS;
}
//退出
public String logout(){
session.remove(SysConstant.CURRENT_USER_INFO);        //删除session
SecurityUtils.getSubject().logout();   //登出
return "logout";
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

 

测试:

当jsp页面上出现Shiro标签就会执行AuthRealm域中的授权方法

1.引入Shiro标签

<%@taglib uri="http:shiro.apache.org/tags" prefix="shiro" %>

2.使用Shiro标签进行授权判断

<shiro:hasPermission name="货运管理"><span id="topmenu" onclick="toModule(‘cargo‘);">货运管理</span><span id="tm_separator"></span></shiro:hasPermission>

技术分享图片

 

 

Shiro运行流程分析:

 技术分享图片

登陆流程:

技术分享图片

 

 

授权流程:

技术分享图片

 

关于进入自己账户后直接在地址栏输入权限不同的其他用户地址,依旧可以进入的

 

BUG解决:

使用perms过滤器进行权限拦截  

1.配置
/sysadmin/deptAction_* = perms["部门管理"]
2.方法加入注解
@RequiresPermissions("部门管理")
3.加入配置
<property name="unauthorizedUrl" value="/index.jsp"></property>

 

 

以上是关于shiro安全框架的主要内容,如果未能解决你的问题,请参考以下文章

Shiro 安全框架详解一(概念+登录案例实现)

SpringBoot实现基于shiro安全框架的,配合thymeleaf模板引擎的用户认证和授权

Shiro安全框架——快速入门登录拦截用户认证请求授权

权限框架之Shiro详解

Shiro 安全框架

shiro框架入门