一、权限概述

1.1 认证与授权

  认证:系统提供的用于识别用户身份的功能,通常登录功能就是认证功能-----让系统知道你是谁??

  授权:系统授予用户可以访问哪些功能的许可(证书)----让系统知道你能做什么??

1.2 常见的权限控制方式

  【URL拦截权限控制】——底层基于拦截器或者过滤器实现

  【方法注解权限控制】——底层基于代理技术实现,为Action创建代理对象,由代理对象进行权限校验

二、apache shiro框架简介

2.1 shiro简介

  Apache Shiro(发音为“shee-roh”,日语“堡垒(Castle)”的意思)是一个强大易用的Java安全框架(官方网站:shiro.apache.org),提供了认证、授权、加密和会话管理功能,可为任何应用提供安全保障 - 从命令行应用、移动应用到大型网络及企业应用。

  以下是你可以用 Apache Shiro所做的事情:

  • 验证用户
  • 对用户执行访问控制。如: 判断用户是否拥有角色admin;判断用户是否拥有访问的权限
  • 在任何环境下使用 Session API。例如CS程序。
  • 可以使用多个用户数据源。例如一个是oracle用户库,另外一个是mysql用户库。
  • 单点登录(SSO)功能。
  • “Remember Me”服务 ,类似购物车的功能,shiro官方建议开启。

2.2 体系结构

  

  Shiro的4大部分——身份验证,授权,会话管理和加密:

  • Authentication:身份认证/登录,验证用户的身份信息;
  • Authorization:授权,哪个用户拥有什么样的权限给其进行授权。
  • Session Management:会话管理,用户登录后未退出时其信息都存在会话里。
  • Cryptography:加密,对用户密码进行加密,防止明文密码出现。

  除了以上功能,shiro还提供很多扩展:

  • Web Support:Web支持,可以非常容易的集成到Web环境;
  • Caching:缓存可以使应用程序运行更有效率。
  • Concurrency:多线程相关功能。
  • Testing:帮助我们进行测试相关功能
  • "Run As":一个允许用户假设为另一个用户身份(如果允许)的功能,有时候在管理脚本很有用。
  • Remember Me:记住用户身份,这个功能开启后下次登录就不能重新登录了,类似购物车功能。

2.3 shiro的工作流程

  工作流程是这样的:前台将用户名/密码通过Subject与shiro的核心管理器(shiro securitymanager)进行交互来获取权限和认证,核心管理器从Realm中获取用户的权限信息。

  • Subject:主体,代表了当前“用户”,这个用户可以是人也可以是某个机器。所有Subject 实例都必须绑定到一个SecurityManager上。
  • SecurityManager:安全管理器;这是shiro的核心,它管理着所有Subject,初始化时协调各个模块运行。然而,一旦 SecurityManager协调完毕,SecurityManager 会被单独留下,且我们只需要去操作Subject即可,无需操作SecurityManager 。 但是我们得知道,当我们正与一个 Subject 进行交互时,实质上是 SecurityManager在处理 Subject 安全操作。
  • Realm:Shiro从Realm获取安全数据(如用户、角色、权限),可以把Realm看成DataSource,即安全数据源。他获取安全数据来判断subject是否能够登录,subject拥有什么权限。他有点类似DAO。在配置realms时,需要至少一个realm。而且Shiro提供了一些常用的 Realms来连接数据源,如LJDBC数据源的JdbcRealm,properties文件数据源的PropertiesRealm,等等。我们也可以插入自己的 Realm实现来代表自定义的数据源。 像其他组件一样,Realms也是由SecurityManager控制

  

三、使用shiro实现登录安全认证

  shiro的优势,不需要在代码里面判断是否登录,是否有执行的权限,实现了从前端页面到后台代码的权限的控制非常的灵活方便。

  传统的登录认证方式是,从前端页面获取到用户输入的账号和密码之后,直接去数据库查询账号和密码是否匹配和存在,如果匹配和存在就登录成功,没有就提示错误。

  而shiro的认证方式则是,从前端页面获取到用户输入的账号和密码之后,传入给一个UsernamePasswordToken对象也就是令牌,然后再把令牌传给subject,subject会调用自定义的 realm,realm做的事情就是用前端用户输入的用户名,去数据库查询出一条记录(只用用户名去查,查询拿到返回用户名和密码),然后再把两个密码进行对比,不一致就抛出异常。也就是说如果subject.login(token);没有抛出异常,就表示用户名和密码是匹配的,表示登录成功

  【实现步骤】:

  第一步:在父工程的pom.xml中引入shiro框架相关的jar

    <!-- 引入shiro框架的依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.2</version>
</dependency>

  第二步:在web.xml中配置spring框架提供的用于整合shiro框架的过滤器

<!--配置过滤器,用于整合shiro-->
<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>

  第三步:在spring配置文件中配置bean,id为shiroFilter

<!--配置shiro框架的过滤器工厂bean-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--shiro的核心安全接口-->
<property name="securityManager" ref="securityManager"/>
<!--没有登录的用户请求需要登录的页面时自动跳转到登录页面,不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。-->
<property name="loginUrl" value="/login.jsp"/>
<!--登录成功默认跳转页面,不配置则跳转至"/"。-->
<property name="successUrl" value="/index.jsp"/>
<!--未授权时跳转的页面-->
<property name="unauthorizedUrl" value="unauthorized.jsp"/>
<!--指定URL级别拦截策略-->
<property name="filterChainDefinitions">
<value>
/css/** = anon
/js/** = anon
/images/** = anon
/login.jsp* = anon
/validatecode.jsp* = anon
/userAction_login = anon
        <!-- 拦截page_base_staff.action这个方法必须有staff-list权限才能使用 -->
/page_base_staff.action = perms["staff-list"]
/* = authc
</value>
</property>
</bean>

  shiro框架提供的过滤器:

  

  anon:例子/admins/**=anon 没有参数,表示可以匿名使用,无需校验权限。

  authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数。

  perms:例如/page_base_staff.action=perms["staff-list"]表示需要有"staff-list"这个权限才能查看;参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。

  roles:例子/admins/user/**=roles[admin],当前用户是否有这个角色权限。

  第四步:配置安全管理器

<!--配置安全管理器-->
<bean id = "securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"/>

  第五步:编写登录方法

  • 传统的登录方法:

    /**
    * 用户登录
    */
    public String login(){
    //从Session中获取生成的验证码
    String validatecode = (String) ServletActionContext.getRequest().getSession().getAttribute("key");
    //校验验证码是否输入正确
    if(StringUtils.isNotBlank(checkcode) && checkcode.equals(validatecode)){
    //输入的验证码正确
    User user = userService.login(model);
    if(user != null){
    //登录成功,将user对象放入session,跳转到首页
    ServletActionContext.getRequest().getSession().setAttribute("loginUser", user);
    return HOME;
    }else{
    //登录失败,,设置提示信息,跳转到登录页面
    //输入的验证码错误,设置提示信息,跳转到登录页面
    this.addActionError("用户名或者密码输入错误!");
    return LOGIN;
    }
    }else{
    //输入的验证码错误,设置提示信息,跳转到登录页面
    this.addActionError("输入的验证码错误!");
    return LOGIN;
    }
    }
  • shiro的登录认证方法

    /**
    * 用户登录,使用shiro框架提供的方式进行认证操作
    */
    public String login() {
    // 从session中获取生成的验证码
    String validateCode = (String) ServletActionContext.getRequest().getSession().getAttribute("key");
    // 验证验证码是否正确
    if (StringUtils.isNotBlank(checkcode) && checkcode.equals(validateCode)) {
    // 输入的验证码正确
    // 使用shiro框架提供的方式进行认证
    Subject subject = SecurityUtils.getSubject(); //获得当前登录用户对象,现在状态为"未认证"
    // 用生成令牌(传入用户输入的账号和密码)
    AuthenticationToken token = new UsernamePasswordToken(model.getUsername(), MD5Utils.md5(model.getPassword()));
    // 认证登录
    try {
    // 这里会加载自定义的realm
    subject.login(token); //把令牌放到login里面进行查询,如果查询账号和密码时候匹配,如果匹配就把user对象获取出来,失败就抛异常
    //获取登录成功的用户对象(以前是直接去service里面查)
    User user = (User) subject.getPrincipal();
    ServletActionContext.getRequest().getSession().setAttribute("loginUser", user);
    return "home";
    } catch (Exception e) {
    //认证登录失败抛出异常
    this.addActionError("用户名和密码错误");
    return LOGIN;
    }
    } else {
    // 输入的验证码错误,设置提示信息,跳转到登录页面
    this.addActionError("输入的验证码错误");
    return LOGIN;
    }
    }

  第六步:自定义realm

public class BOSRealm extends AuthorizingRealm {
@Autowired
private IUserDao userDao; // 授权方法
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// TODO Auto-generated method stub
return null;
} //认证方法
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 获取令牌
UsernamePasswordToken mytoken = (UsernamePasswordToken) token;
// 得到账号和密码
String username = mytoken.getUsername();
// 根据用户名查询数据库是否存在用户,如果存在返回对象(账号和密码都有的对象)
User user = userDao.findUserByUsername(username);
if (user == null) {
// 用户名不存在
return null;
}
// 如果能查询到,再由框架对比数据库中查询到的密码与页面提交的密码是否一致
// 参数1:用户认证的对象(subject.getPrincipal();返回的对象)
// 参数2.从数据库根据用户名查询到的用户的密码
// 参数3.把当前自定义的realm对象传给SimpleAuthenticationInfo,在配置文件需要注入
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());
return info;
}
}

  第七步:在安全管理器里面注入自定义的realm

<!--配置安全管理器-->
<bean id = "securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--注入realm到安全管理器进行密码匹配-->
<property name="realm" ref="bosRealm"/>
</bean> <!--注册自定义realm-->
<bean id = "bosRealm" class="cn.itcast.bos.realm.BOSRealm"/>

四、使用shiro为用户授权

4.1 添加权限的四种方式

  • URL拦截权限控制——基于过滤器实现

    <!-- 配置URL拦截规则 -->
    <property name="filterChainDefinitions">
    <value>
    /css/** = anon
    /js/** = anon
    /images/** = anon
    /validatecode.jsp* = anon
    /login.jsp* = anon
    /User_login.action= anon
       <!-- 拦截page_base_staff.action这个方法必须有staff权限才能使用 -->
    /page_base_staff.action = perms["staff"] /** = authc
    </value>
    </property>
  • 方法注解权限控制——基于代理技术实现

  第一步:在spring配置文件中开启shiro注解支持

<!--开启shiro框架注解支持-->
<bean id="defaultAdvisorAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
<!--必须使用cglib方式为Action对象创建代理对象-->
<property name="proxyTargetClass" value="true"/>
</bean> <!--配置shiro框架提供的切面类,用于创建代理对象-->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"></bean>

   第二步:在Action的方法上使用shiro注解

/**
* 取派员批量删除
* @return
*/
@RequiresPermissions("staff-delete") //执行这个方法,需要当前用户具有staff-delete权限
public String deleteBatch() {
staffService.deleteBatch(ids);
return "list";
}

  第三步:在struts.xml中配置全局异常捕获,当shiro框架抛出权限不足异常时,跳转到权限不足提示页面

<!--全局结果集定义-->
<global-results>
<result name="login">/login.jsp</result>
<result name="unauthorized">/unauthorized.jsp</result>
</global-results> <global-exception-mappings>
<exception-mapping exception="org.apache.shiro.authz.UnauthorizedException" result="unauthorized"></exception-mapping>
</global-exception-mappings>
  •  页面标签权限控制——标签技术实现

  第一步:在jsp页面中引入shiro的标签库

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

  第二步:使用shiro的标签控制页面元素展示

<!--有staff-delete权限才能显示此an按钮-->
<shiro:hasPermission name="staff-delete">
{
id : 'button-delete',
text : '删除',
iconCls : 'icon-cancel',
handler : doDelete
},
</shiro:hasPermission>
  • 代码级别权限控制(几乎不用)——基于代理技术实现

  在要设置权限的代码中添加一下两行代码就可以了

    //修改
public String edit()
{
Subject subject = SecurityUtils.getSubject();
subject.checkPermission("staff.edit");//要运行此方法下面的代码,必须要拥有staff.edit的权限
//更新model
staffService.update(model);
return "staff";
}

4.2 授权

  • 手动授权和认证

  因为要授权的权限太多,所以需要一张权限表

public class BOSRealm extends AuthorizingRealm {
@Autowired
private IUserDao userDao; // 授权方法
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 为用户授权
info.addStringPermission("staff");//为page_base_staff.action请求授权staff权限
info.addStringPermission("staff.delete");//为page_base_staff.action请求授权staff权限
info.addStringPermission("staff.edit");
return info;
} //认证方法
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 获取令牌
UsernamePasswordToken mytoken = (UsernamePasswordToken) token;
// 得到账号和密码
String username = mytoken.getUsername();
// 根据用户名查询数据库是否存在用户,如果存在返回对象(账号和密码都有的对象)
User user = userDao.findUserByUsername(username);
if (user == null) {
// 用户名不存在
return null;
}
// 如果能查询到,再由框架对比数据库中查询到的密码与页面提交的密码是否一致
// 参数1:用户认证的对象(subject.getPrincipal();返回的对象)
// 参数2.从数据库根据用户名查询到的用户的密码
// 参数3.把当前自定义的realm对象传给SimpleAuthenticationInfo,在配置文件需要注入
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());
return info;
}
}
  • 遍历数据库授权

  根据当前登录用户查询数据库,获取实际对应的权限

// 授权方法
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 获取当前登录用户对象
User user = (User) SecurityUtils.getSubject().getPrincipal();
// 根据当前登录用户查询数据库,获取实际对应的权限
List<Function> list = null;
if (user.getUsername().equals("admin")) {
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Function.class);
// 超级管理员内置用户,查询所有权限
list = functionDao.findByCriteria(detachedCriteria);
} else {
list = functionDao.findFunctionByUserId(user.getId());
}
for (Function function : list) {
info.addStringPermission(function.getCode());
} return info;
}

五、使用ehcache缓存权限数据

  ehcache是专门缓存插件,可以缓存Java对象,提高系统性能。

5.1 配置ehcache缓存

  第一步:在pom.xml文件中引入ehcache的依赖

<!-- 引入ehcache的依赖 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.6</version>
</dependency>

  第二步:在项目中提供ehcache的配置文件(ehcache.xml)

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">

    <diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
</ehcache>

  第三步:在spring配置文件中配置缓存管理器对象,并注入给安全管理器对象

<!--配置安全管理器-->
<bean id = "securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--注入realm到安全管理器进行密码匹配-->
<property name="realm" ref="bosRealm"/>
<!--注入缓存管理器-->
<property name="cacheManager" ref="cacheManager"/>
</bean> <!--注册缓存管理器-->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<!--注入ehcache的配置文件-->
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>

5.2 测试缓存的作用

  上面我们已经配置好了缓存,那么我们怎么证明缓存是否起作用了呢?我们可以通过给Realm打断点的方式来测试:

  

  没配置ehcache缓存前,每次点击查询页面,都会执行这个方法。

  而配置了缓存后,只有当我们第一次访问这个查询页面的时候,才会执行一次这个方法,即只用查询一次数据库,以后就不用查(权限数据)了。

  那么什么时候会再次查数据库呢?我们可以通过ehcache.xml配置它的有效时间,默认是(从不操作开始)2分钟后再次查询,我们可以修改相关配置来确定它的有效时间,(比如设置6秒后再次查询数据库):

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">

    <diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="6"
timeToLiveSeconds="6"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
</ehcache>

  如果,我们重新登录用户,有效时间也会重新生效。每次我们重新登录后,原先缓存的数据就没了,哪怕你设置的时间还没到。

参考:https://blog.csdn.net/liaomin416100569/article/details/78838900

  https://baijiahao.baidu.com/s?id=1591438032280398708&wfr=spider&for=pc

  https://www.cnblogs.com/AngeLeyes/p/7196956.html

apache shiro学习笔记的更多相关文章

  1. Shiro学习笔记(5)——web集成

    Web集成 shiro配置文件shiroini 界面 webxml最关键 Servlet 測试 基于 Basic 的拦截器身份验证 Web集成 大多数情况.web项目都会集成spring.shiro在 ...

  2. shiro学习笔记_0600_自定义realm实现授权

    博客shiro学习笔记_0400_自定义Realm实现身份认证 介绍了认证,这里介绍授权. 1,仅仅通过配置文件来指定权限不够灵活且不方便.在实际的应用中大多数情况下都是将用户信息,角色信息,权限信息 ...

  3. Shiro学习笔记总结,附加" 身份认证 "源码案例(一)

    Shiro学习笔记总结 内容介绍: 一.Shiro介绍 二.subject认证主体 三.身份认证流程 四.Realm & JDBC reaml介绍 五.Shiro.ini配置介绍 六.源码案例 ...

  4. Apache Flink学习笔记

    Apache Flink学习笔记 简介 大数据的计算引擎分为4代 第一代:Hadoop承载的MapReduce.它将计算分为两个阶段,分别为Map和Reduce.对于上层应用来说,就要想办法去拆分算法 ...

  5. Apache Shiro学习-2-Apache Shiro Web Support

     Apache Shiro Web Support  1. 配置 将 Shiro 整合到 Web 应用中的最简单方式是在 web.xml 的 Servlet ContextListener 和 Fil ...

  6. [shiro学习笔记]第三节 使用myeclipse导入apache shiro中的QuikStart example例子

    本文地址:http://blog.csdn.net/sushengmiyan/article/details/40149131 shiro官网:http://shiro.apache.org/ shi ...

  7. Shiro 学习笔记(一)——shiro简介

    Apache Shiro 是一个安全框架.说白了,就是进行一下 权限校验,判断下这个用户是否登录了,是否有权限去做这件事情. Shiro 可以帮助我们完成:认证.授权.加密.会话管理.与web 集成. ...

  8. Shiro学习笔记四(Shiro集成WEB)

    这两天由于家里出了点事情,没有准时的进行学习.今天补上之前的笔记 -----没有学不会的技术,只有不停找借口的人 学习到的知识点: 1.Shiro 集成WEB 2.基于角色的权限控制 3.基于权限的控 ...

  9. Apache Shiro 学习记录5

    本来这篇文章是想写从Factory加载ini配置到生成securityManager的过程的....但是貌似涉及的东西有点多...我学的又比较慢...很多类都来不及研究,我又怕等我后面的研究了前面的都 ...

随机推荐

  1. 【原创】5. MYSQL++ mysql_type_info类型

    该类型是SQLBuffer的灵魂,它用来表示从SQL TYPE到C++ TYPE的相互转变.该类型被定义在type_info.h中.在这个头文件中,其实定义了三个类型,其中前两个都是在mysql_ty ...

  2. opennebula 开发记录

    /app/opennebula/var//datastores/1/12933297f0ffeba3e55bbccabcb3153b to 127.0.0.1:/app/opennebula/data ...

  3. vmware 安装不成功导致的问题解决以及右键菜单添加打开终端命令

    转自http://blog.csdn.net/puweilan/article/details/8609952 在VMware安装Ubuntu完成后,一直停留在VMware Easy Install, ...

  4. 11.BETWEEN 操作符

    BETWEEN 操作符在 WHERE 子句中使用,作用是选取介于两个值之间的数据范围. BETWEEN 操作符 操作符 BETWEEN ... AND 会选取介于两个值之间的数据范围.这些值可以是数值 ...

  5. CF 1091E New Year and the Factorisation Collaboration

    昨晚Good Bye 2018D题没做出来,车翻大了…… 官方题解      传送门 初赛知识:一个无向图所有顶点度数之和为偶数.然而这东西还有一个高端的名字:Handshaking lemma 但是 ...

  6. Flask框架 之 路由和视图详解

    路由+视图 我们之前了解了路由系统是由带参数的装饰器完成的. 路由本质:装饰器和闭包实现的. 路由设置的两种方式 来看个例子. @app.route('/index') def index(): re ...

  7. AIO和NIO的理解

    AIO: AIO 背后的基本思想是允许进程发起很多 I/O 操作,而不用阻塞或等待任何操作完成,可以继续做 另外的事情,等I/O操作完成,内核会通过函数回调或者信号机制通知用户进程.这样很大程度提高了 ...

  8. 个人项目:wc程序(java)

    Github项目地址:https://github.com/jat0824/wc.git 项目相关要求 wc.exe 是一个常见的工具,它能统计文本文件的字符数.单词数和行数.这个项目要求写一个命令行 ...

  9. UltraEdit 回车符替换空格

    查找和替换    输入 ^r^n   替换为:(空格)

  10. C#winform拖动无边框窗体

    private bool isMouseLeftKeyDown = false; private Point mousePointToClient = new Point();//相对于本窗体鼠标位置 ...