权限控制的方式

从类别上分,有两大类: 
- 认证:你是谁?–识别用户身份。 
- 授权:你能做什么?–限制用户使用的功能。

权限的控制级别

从控制级别(模型)上分: 
- URL级别-粗粒度 
- 方法级别-细粒度 
- 页面级别-自定义标签(显示) 
- 数据级别-最细化的(数据)

URL级别的权限控制-粗粒度

在web.xml中配置一个过滤器filter,在过滤器中,对请求的地址进行解析,字符串截取: 
url.substring()…把上下文前面的路径都截取掉,剩下user_login.action。 
过滤器代码: 
以通过查询数据库,来判断,当前登录用户,是否可以访问user_login.action。 
url级别控制,每次请求过程中只控制一次 ,相比方法级别权限控制 是粗粒度的 !URL级别权限控制,基于Filter实现。

方法级别的权限控制-细粒度

aop面向切面的编程,在方法执行之前,进行权限判断,如果没有权限,抛出异常,终止方法的继续运行。 
自定义注解 在需要权限控制方法上, 添加需要的权限信息 
代理 (Spring AOP ),在目标方法运行时 进行增强 ,通过反射技术获取目标方法上注解中权限 , 查询数据库获取当前登陆用户具有权限,进行比较。 
相比URL级别权限控制, 可以控制到服务器端执行的每个方法,一次请求中可以控制多次。

页面(显示)级别的权限控制-自定义标签

页面显示的权限控制,通常是通过 自定义标签来实现

数据级别的权限控制

在每条数据上增加一个字段,该字段记录了权限的值。数据和权限绑定。 
代码,你在查询数据的时候,需要去权限和用户对应表中,通过当前登录用户的条件,查询出你的数据权限。然后再将数据权限作为一个条件,放到业务表中进行查询。从而限制了数据的访问。

权限系统的数据表设计

  • 资源:用户要访问的目标,通常是服务中的程序或文件
  • 权限:用户具有访问某资源的能力
  • 角色:权限的集合,为了方便给用户授权。
  • 用户:访问系统的’人’。

表对象实体: 
- 用户(User)表:访问系统的用户,比如用户登录要用 
- 权限(Function)表:系统某个功能允许访问而对应的权限 
- 角色(Role)表:角色是权限的集合(权限组),方便用户授权。

表对象之间的关系: 
- 用户和角色关系表:一个用户对应N个角色,一个角色可以授予N个用户—》多对多关系 
- 角色和权限关系表:一个角色包含N个权限,一个权限可以属于N个角色—》多对多关系

完整的权限相关表: 
URL级别权限控制包含:资源表、权限表、角色表、用户表,以及相关关系(都是多对多),共7张表。 
方法级别的权限控制包含:功能权限、角色、用户,以及相关关系(都是多对多),共5张表。

但Apache Shiro框架支持的URL级别权限控制,是将资源和资源权限对应关系配置到了配置文件中,不需要表的支撑,只需要5张表了。

Apache Shiro权限控制

Apache Shiro 可以不依赖任何技术使用, 可以直接和web整合,通常在企业中和Spring 结合使用。

Authentication: 认证 — 用户登录 
Authorization : 授权 —- 功能权限管理

通过引入Maven坐标导入shiro

官方建议:不推荐直接引入shiro-all,依赖比较多,原因怕有jar冲突。官方推荐根据需要单独导入jar。

Shiro基本原理

Shiro的框架的体系结构: 

Shiro权限控制流程的原理: 

  • 应用代码 —- 调用Subject (shiro的Subject 就代表当前登陆用户) 控制权限 —- Subject 在shiro框架内部 调用 Shiro SecurityManager 安全管理器 —– 安全管理器调用 Realm (程序和安全数据连接器 )。
  • Subject要进行任何操作,都必须要调用安全管理器(对我们来说是自动的)。 
    而安全管理器会调用指定的Realms对象,来连接安全数据。
  • Realms用来编写安全代码逻辑和访问安全数据,是连接程序和安全数据的桥梁。

URL级别的权限控制

配置整合和url级别认证

配置过滤器web.xml:放在struts的前端控制器之前配置,但放在openEntitymanage之后。

    <!-- shiro权限过滤器 -->
<filter>
<!-- 这里的 filter-name 要和 spring 的 applicationContext-shiro.xml 里的 org.apache.shiro.spring.web.ShiroFilterFactoryBean
的 bean name 相同 -->
<filter-name>shiroSecurityFilter</filter-name>
<!-- spring的代理过滤器类:以前的过滤器 -->
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 -->
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroSecurityFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping> 配置ApplicationContext.xml:(shiro权限控制过滤器+ shiro安全管理器) <!-- shiro权限控制过滤器bean -->
<bean id="shiroSecurityFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- shiro 的核心安全接口 -->
<property name="securityManager" ref="securityManager" />
<!-- 要求登录时的链接 -->
<property name="loginUrl" value="/login.jsp" />
<!-- 登陆成功后要跳转的连接 -->
<property name="successUrl" value="/index.jsp" />
<!-- 未授权时要跳转的连接,权限不足的跳转路径 -->
<property name="unauthorizedUrl" value="/unauthorized.jsp" />
<!-- shiro 连接约束配置(URL级别的权限控制),即URL和filter的关系,URL控制规则:路径=规则名 -->
<property name="filterChainDefinitions">
<value>
<!--按需求配置-->
/login.jsp = anon
/validatecode.jsp = anon
/js/** = anon
/css/** = anon
/images/** = anon
/user_login.action* = anon
/page_base_staff.action = anon
/page_base_region.action = perms["user"]
/page_base_subarea.action = roles["operator"]
/** = authc
</value>
</property>
</bean>
<!-- shiro安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 注入 Realm连接安全数据-->
</bean>

配置shiroFilter 其实是一个过滤器链,含有10个Filter(校验功能)。

常用: 
认证 
- anon不用认证(登录)就能访问(单词注意大小写) 
- authc: 需要认证(登录)才能使用,例如/admins/user/**=authc,没有参数。 
授权: 
- perms:需要拥有某权限才能使用,如具体允许的权限:/page_base_region.action =perms[“user”],如果要访问该action,当前登录用户必须拥有user名字的权限。 
- roles:需要拥有某角色才能使用,如具体允许的角色:/page_base_subarea.action = roles[“operator”]如果要访问该action,当前用户必须拥有operator权限。

用户认证(登录)—自定义Realm

Shiro实现登录逻辑 
用户输入用户名和密码 —- 应用程序调用Subject的login方法 —- Subject 调用SecurityManager的方法 —- SecurityManager 调用Realm的认证方法 —- 认证方法根据登录用户名查询密码 ,返回用户的密码 —- SecurityManager 比较用户输入的密码和真实密码是否一致 。

编写Shiro的认证登录逻辑

        @Action(value="user_login",results={@Result(name=SUCCESS,type="redirect",location="/index.jsp"),@Result(name=LOGIN,location="/login.jsp")})
@InputConfig(resultName="login")
public String login() throws Exception {
//shrio:登陆逻辑
//获取认证对象的包装对象
Subject subject = SecurityUtils.getSubject(); //获取一个认证的令牌:
//直接获取页面的用户和密码进行校验
AuthenticationToken authenticationToken = new UsernamePasswordToken(model.getUsername(),MD5Utils.md5(model.getPassword()));
//认证过程
try {
// 如果成功,就不抛出异常,会自动将用户放入session的一个属性
subject.login(authenticationToken);
//成功,返回首页
return SUCCESS;
}catch(UnknownAccountException e){
//用户名错误
addActionError(getText("UserAction.usernamenotfound"));
//返回登陆页面
return LOGIN;
}catch (IncorrectCredentialsException e) {
//密码错误
addActionError(getText("UserAction.passwordinvalid"));
//返回登陆页面
return LOGIN;
}
catch (AuthenticationException e) {
//认证失败
e.printStackTrace();
//页面上进行提示
addActionError(getText("UserAction.loginfail"));
//返回登陆页面
return LOGIN;
}
}

编写Realm,给SecurityManager提供

JdbcRealm和jndiLdapRealm,直接连接jdbc或jndi或ldap。 
相当于dao和reaml整合了,能直接读取数据库,逻辑代码都实现好了。

        /**
* 实现认证和授权功能
*自定义的realm,作用从数据库查询数据,并返回数据库认证的信息
*/
@Component("bosRealm")
public class BosRealm extends AuthorizingRealm{ //注入ehcache的缓存区域
@Value("BosShiroCache")//注入缓存具体对象的名字,该名字在ehcache.xml中配置的
public void setSuperAuthenticationCacheName(String authenticationCacheName){
super.setAuthenticationCacheName(authenticationCacheName);
} //注入service
@Autowired
private UserService userService; //注入角色dao
@Autowired
private RoleDao roleDao; //注入功能的dao
@Autowired
private FunctionDao functionDao; //授权方法:获取用户的权限信息
//授权:回调方法
//如果返回null,说明没有权限,shiro会自动跳到<property name="unauthorizedUrl" value="/unauthorized.jsp" />
//如果不返回null,根据配置/page_base_subarea.action = roles["weihu"],去自动匹配
//给授权提供数据的
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //给当前用户授权的权限(功能权限、角色)
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//两种方式:
//方式1:工具类来获取(首长-)
// User user=(User)SecurityUtils.getSubject().getPrincipal();
//方式2:通过参数获取首长(推荐)
User user = (User) principals.getPrimaryPrincipal(); //实际:需要根据当前用户的角色和功能权限来构建一个授权信息对象,交给安全管理器 if (user.getUsername().equals("admin")) {
//如果是超级管理员
//查询出所有的角色,给认证信息对象
List<Role> roleList = roleDao.findAll();
for (Role role : roleList) {
authorizationInfo.addRole(role.getCode());
}
//查询出所有的功能权限,给认证对象
List<Function> functionList = functionDao.findAll();
for (Function function : functionList) {
authorizationInfo.addStringPermission(function.getCode());
}
} else {
//如果是普通用户
List<Role> roleList = roleDao.findByUsers(user);
for (Role role : roleList) {
authorizationInfo.addRole(role.getCode());
//导航查询,获取某角色的拥有的功能权限
Set<Function> functions = role.getFunctions();
for (Function function : functions) {
authorizationInfo.addStringPermission(function.getCode());
}
}
} return authorizationInfo;//将授权信息交给安全管理器接口。
} //认证:回调,认证管理器会将认证令牌放到这里(action层的令牌AuthenticationToken)
//发现如果返回null,抛出用户不存在的异常UnknownAccountException
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//用户名密码令牌(action传过来)
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//调用业务层来查询(根据用户名来查询用户,无需密码)
User user = userService.findByUsername(upToken.getUsername());
//判断用户是否存在
if (user == null) {
//用户不存在
return null;//抛出异常
} else {
//用户名存在
//参数1:用户对象,将来要放入session,数据库查询出来的用户
//参数2:凭证(密码):密码校验:校验的动作交给shiro
//参数3:当前使用的Realm在Spring容器中的名字(bean的名字,自动在spring容器中寻找)
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), super.getName());
return authenticationInfo;//密码校验失败,会自动抛出IncorrectCredentialsException
}
} }

ApplicatonContext.xml:

    <!-- service需要spring扫描 -->
<context:component-scan base-package="cn.aric.bos.service,cn.aric.bos.web,cn.aric.bos.auth.realm" /> <!-- shiro安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 注入 Realm连接安全数据-->
<property name="realm" ref="bosRealm"></property>
<!-- 注入shiro的缓存管理器 -->
<property name="cacheManager" ref="shiroCacheManager"/>
</bean>

用户认证(退出)以及修改密码

        /**
* 用户退出登录
* @return
* @throws Exception
*/
@Action(value="user_logout",results={@Result(name=LOGIN,type="redirect",location="/login.jsp")})
public String logout() throws Exception {
//shiro退出
Subject subject = SecurityUtils.getSubject();
subject.logout();
//跳转登陆页面
return LOGIN;
} /**
* 用户修改密码
* @return
* @throws Exception
*/
// @Action(value="user_editPassword",results={@Result(name=JSON,type=JSON)})
@Action("user_editPassword")
public String editPassword() throws Exception {
//获取Principal就是获取当前用户
User loginUser = (User) SecurityUtils.getSubject().getPrincipal();
model.setId(loginUser.getId()); //页面结果
HashMap<String,Object> resultMap = new HashMap<String,Object>();
try {
//调用service进行修改密码
userService.updateUserPassword(model);
//修改成功
resultMap.put("result", true);
} catch (Exception e) {
e.printStackTrace();
//修改失败
resultMap.put("result", false);
}
//将结果压入栈顶
ActionContext.getContext().getValueStack().push(resultMap);
//转换为json
return JSON;
}

用户授权(授权)—自定义Ream

数据库数据添加,applicationContext.xml配置

        <property name="filterChainDefinitions">
<value>
/login.jsp = anon
/validatecode.jsp = anon
/js/** = anon
/css/** = anon
/images/** = anon
/user_login.action = anon
/page_base_staff.action = anon
/page_base_region.action = perms["region"]
/page_base_subarea.action = roles["weihu"]
/page_qupai_noticebill_add.action = perms["noticebill"]
/page_qupai_quickworkorder.action = roles["kefu"]
/** = authc
</value>
</property>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

代码在上面的BosRealm的中,protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) 
RoleDao省略。

方法级别的权限控制

启用Shiro注解

需要 Shiro 的 Spring AOP 集成来扫描合适的注解类以及执行必要的安全逻辑。 
ApplicationContext.xml

        <!-- 开启权限控制的注解功能并且配置aop -->
<!-- 后处理器:通过动态代理在某bean实例化的前增强。:自己去找权限注解 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 切面自动代理:相当于以前的AOP标签配置
advisor:切面 advice:通知
-->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"> </bean> <!-- Advisor切面配置:授权属性的切面 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<!-- 注入安全管理器 -->
<property name="securityManager" ref="securityManager"/>
</bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

在需要权限控制的目标方法上面使用shiro的注解: 
@RequiresAuthentication 需要用户登录 
subject.isAuthenticated() 必须返回true 
@ RequiresUser 
subject.isAuthenticated() 返回true 或者subject.isRemembered() 返回true 
“Remember Me”服务: 
认证机制 基于 session 
被记忆机制 基于 cookie (subject.isAuthenticated() 返回 false )

@ RequiresGuest 与 @RequiresUser 相反,不能认证也不能被记忆。 
@ RequiresRoles 需要角色 
@RequiresPermissions 需要权限

异常

动态代理异常

解决方案: 
配置ApplicationContext.xml,设置代理为cglib代理(对目标类代理)

        <!-- 切面自动代理:相当于以前的AOP标签配置 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor" >
<!-- 设置aop的代理使用CGLIB代理 -->
<property name="proxyTargetClass" value="true"/>
</bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

方案二:

<aop:config proxy-target-class="true" />
  • 1

类型转换异常

解决方案:递归向上寻找泛型的类型。

    //递归向上 查找
Class actionClass =this.getClass();
//向父类递归寻找泛型
while(true){
//得到带有泛型的类型,如BaseAction<Userinfo>
Type type = actionClass.getGenericSuperclass(); if(type instanceof ParameterizedType){
//转换为参数化类型
ParameterizedType parameterizedType = (ParameterizedType) type;
//获取泛型的第一个参数的类型类,如Userinfo
Class<T> modelClass = (Class<T>) parameterizedType.getActualTypeArguments()[0];
//实例化模型对象
try {
model=modelClass.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
break;
}
//寻找父类
actionClass=actionClass.getSuperclass(); }

空指针异常

解决方案1:使用public 的Setter方法上的注解直接注入Service。

SubareaAction:

        //注入service
private SubareaService subareaService;
@Autowired
public void setSubareaService(SubareaService subareaService) {
this.subareaService = subareaService;
}

解决方案2: 
@Autowire还放到私有声明上, 
在struts.xml中覆盖常量(开启自动装配策略): 
值默认是false,struts2默认注入采用的是构造器注入(从spring中寻找的bean) 
改成true,struts2会采用setter方法注入

页面标签(实现页面内容定制显示)

        <!-- 引入Shiro标签 -->
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro"%>
页面拿Session中的user对象: 代表user对象。

程序中拿Session中的user对象:SecurityUtils.getSubject().getPrincipal()

资源通配符和权限通配符可便捷开发。

代码级别

使用代码编程的方式,直接在程序中使用Subject对象,调用内部的一些API。(有代码侵入)

    //代码级别的权限控制(授权):功能权限和角色权限:两套机制:boolean判断,异常判断
//授权的权限控制
//====布尔值判断
//功能权限
if(subject.isPermitted("staff")){
//必须拥有staff功能权限才能执行代码
System.out.println("我是一段代码。。。。。");
}
//角色权限
if(subject.hasRole("weihu")){
//必须拥有staff功能权限才能执行代码
System.out.println("我是一段代码。。。。。");
}
//====异常判断
//功能权限
try {
subject.checkPermission("staff");
//有权限
} catch (AuthorizationException e) {
// 没权限
e.printStackTrace();
}
//角色权限
try {
subject.checkRole("weihu");
//有权限
} catch (AuthorizationException e) {
// 没权限
e.printStackTrace();
}

Shiro的认证和权限控制的更多相关文章

  1. springboot+mybatis+shiro——登录认证和权限控制

    转载:https://z77z.oschina.io/ 一.引入依赖 shiro-all包含shiro所有的包.shiro-core是核心包.shiro-web是与web整合.shiro-spring ...

  2. spring boot(十四)shiro登录认证与权限管理

    这篇文章我们来学习如何使用Spring Boot集成Apache Shiro.安全应该是互联网公司的一道生命线,几乎任何的公司都会涉及到这方面的需求.在Java领域一般有Spring Security ...

  3. Spring Boot 2.X(十八):集成 Spring Security-登录认证和权限控制

    前言 在企业项目开发中,对系统的安全和权限控制往往是必需的,常见的安全框架有 Spring Security.Apache Shiro 等.本文主要简单介绍一下 Spring Security,再通过 ...

  4. JAVAEE——BOS物流项目11:在realm中授权、shiro的方法注解权限控制、shiro的标签权限控制、总结shiro的权限控制方式、权限管理

    1 学习计划 1.在realm中进行授权 2.使用shiro的方法注解方式权限控制 n 在spring文件中配置开启shiro注解支持 n 在Action方法上使用注解 3.★使用shiro的标签进行 ...

  5. 用Python建设企业认证和权限控制平台

    目前大家对Python的了解更多来源是数据分析.AI.运维工具开发,在行业中使用Python进行web开发,同样也是非常受欢迎的,例如:FaceBook,豆瓣,知乎,饿了么等等,本文主要是介绍是利用P ...

  6. shiro框架的四中权限控制方式

    https://www.cnblogs.com/cocosili/p/7103025.html 一.在自定义的realm中进行权限控制 在applicationContext.xml文件中添加  /a ...

  7. JavaWeb项目:Shiro实现简单的权限控制(整合SSM)

    该demo整合Shiro的相关配置参考开涛的博客 数据库表格相关设计  表格设计得比较简单,导航栏直接由角色表auth_role的角色描述vRoleDesc(父结点)和角色相关权限中的权限描述(标记为 ...

  8. MySQL用户认证及权限控制

    一.MySQL用户认证: 登录并不属于访问控制机制,而属于用户身份识别和认证: 1.用户名—user 2.密码—password 3.登录mysqld主机—host 实现用户登录MySQL,建立连接. ...

  9. shiro框架学习-7- Shiro权限控制注解和编程方式

    讲解权限角色控制 @RequiresRoles, @RequiresPermissions等注解的使用和编程式控制 配置文件的方式 使用ShiroConfig 注解方式 @RequiresRoles( ...

随机推荐

  1. vue 项目搭建步骤

    环境搭建步骤: 打开git ,运行 npm install --global vue-cli 这是安装vue的命令行 vue init webpack vue-demo 这是vue基于webpack的 ...

  2. python多进程拷贝数据

    from multiprocessing import Pool,Manager import os #完成拷贝文件 def copyFile(filename,oldname,newname,que ...

  3. 一起学python-语法

    1.print 输出 2.定义变量:就是给变量赋一个值 name ='haha' print (name) 3.注释代码:# 注释快捷键:Ctrl +/ 4.单双引号: 如果字符串里面有单引号,外面就 ...

  4. git 恢复本地误删文件

    git status git reset HEAD 路径(git status 会显示的路径) git checkout 路径

  5. Spark SQL External DataSource简介

    随着Spark1.2的发布,Spark SQL开始正式支持外部数据源.这使得Spark SQL支持了更多的类型数据源,如json, parquet, avro, csv格式.只要我们愿意,我们可以开发 ...

  6. 安装Oracle数据库心得

    学到Oracle数据库了,想在自己电脑上安装个Oracle数据库.在网上下载了一个Oracle18c版 下边是我安装Oracle18c版的数据库失败,后来在卸载过程中遇到的问题: 1.用Univers ...

  7. VS2017 C/C++输入密码显示*星号

    VS2017  C/C++输入密码显示*星号 _getch()函数使用时遇到的坑 参考: https://blog.csdn.net/guin_guo/article/details/46237905 ...

  8. MongoDB关键指标意义&各数值区间意义&部署

    ## part 1 mms图 What's MMS MongoDB Management Service (MMS) is a suite of services for managing Mongo ...

  9. (转)Unity_什么是Draw Call? 什么是Batch?

    開發遊戲時,一定被時時提醒要減少 Draw Call,當然UNITY也不例外,打開Game Window裡的 Stats,可以看到 Draw Call 與 Batched 的數字.但到底甚麼是 Dra ...

  10. 《Self-Attention Generative Adversarial Networks》里的注意力计算

    前天看了 criss-cross 里的注意力模型  仔细理解了  在: https://www.cnblogs.com/yjphhw/p/10750797.html 今天又看了一个注意力模型 < ...