一、权限概述

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. Unity3d平台信息设置

    [Unity3d平台信息设置] 通过"Edit" -> "Project Settings" -> "Player"菜单选项.打 ...

  2. POJ2187(凸包+旋转卡壳)

    这道题目的大意是给出一组二维空间的顶点,计算其中距离最远的两个顶点之间的距离. 先说明凸包的概念和求法. 定义:对于多边形P,若将P中任意的两个点(包含边上)用一条线段连接,线段都落于该多边形中(含边 ...

  3. 客户端级别的渲染分析工具 dynaTrace

    dynaTrace Ajax Edition是一款很好的javaScript性能分析工具.目前支持IE和Firefox 2款浏览器. dynaTrace如图所示: 点击Click here to st ...

  4. “Device eth0 has different MAC address than expected, ignoring.”问题

    配IP后进行激活的时候提示如下错误:("Device eth0 has different MAC address than expected, ignoring.") 百度了下, ...

  5. java就业指南 zookeeper分布式系统 zookeeper实现分布式锁 有用

    目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题.分布式的CAP理论告诉我们“任何一个 分布式系统都无法同时满足一致性(Consistency).可用性 ...

  6. SpringMVC——数据转换 & 数据格式化 & 数据校验

    一.数据绑定流程 1. Spring MVC 主框架将 ServletRequest 对象及目标方 法的入参实例传递给 WebDataBinderFactory 实例,以创 建 DataBinder ...

  7. VC维的来龙去脉(转)

    本文转自VC维的来龙去脉 本文为直接复制原文内容,建议阅读原文,原文排版更清晰,且原网站有很多有意思的文章. 阅读总结: 文章几乎为台大林老师网课“机器学习可行性”部分串联总结,是一个很好的总结. H ...

  8. C# 关于接口与基类的理解(二者的区别)

    接口(接口的名称一般用大写字母I开头的)是把公共实例(非静态)方法和属性组合起来,以封装特定功能的一个集合.(其实,接口简单理解就是一种约定,使得实现接口的类或结构在形式上保持一致) 注意:使用接口可 ...

  9. 在使用webstorm打开本地项目文件夹的html文件时,浏览器提示404错误

    错误原因:在使用webstorm打开本地项目文件夹的html文件时,浏览器提示404错误. 错误分析:文件夹命名内包含“+”,此特殊符号导致浏览器解析错误. 改正方案:去掉特殊符号“+”

  10. UIView 和 CALayer区别 为啥有UIView还要CALayer?

    今天,被坑了,面试的时候没回答出来,特此记录一下 一.继承结构 1: UIView的继承结构为: UIResponder : NSObject UIResponder是用来响应事件的,也就是UIVie ...