Shiro的认证和权限控制
权限控制的方式
从类别上分,有两大类:
- 认证:你是谁?–识别用户身份。
- 授权:你能做什么?–限制用户使用的功能。
权限的控制级别
从控制级别(模型)上分:
- 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的认证和权限控制的更多相关文章
- springboot+mybatis+shiro——登录认证和权限控制
转载:https://z77z.oschina.io/ 一.引入依赖 shiro-all包含shiro所有的包.shiro-core是核心包.shiro-web是与web整合.shiro-spring ...
- spring boot(十四)shiro登录认证与权限管理
这篇文章我们来学习如何使用Spring Boot集成Apache Shiro.安全应该是互联网公司的一道生命线,几乎任何的公司都会涉及到这方面的需求.在Java领域一般有Spring Security ...
- Spring Boot 2.X(十八):集成 Spring Security-登录认证和权限控制
前言 在企业项目开发中,对系统的安全和权限控制往往是必需的,常见的安全框架有 Spring Security.Apache Shiro 等.本文主要简单介绍一下 Spring Security,再通过 ...
- JAVAEE——BOS物流项目11:在realm中授权、shiro的方法注解权限控制、shiro的标签权限控制、总结shiro的权限控制方式、权限管理
1 学习计划 1.在realm中进行授权 2.使用shiro的方法注解方式权限控制 n 在spring文件中配置开启shiro注解支持 n 在Action方法上使用注解 3.★使用shiro的标签进行 ...
- 用Python建设企业认证和权限控制平台
目前大家对Python的了解更多来源是数据分析.AI.运维工具开发,在行业中使用Python进行web开发,同样也是非常受欢迎的,例如:FaceBook,豆瓣,知乎,饿了么等等,本文主要是介绍是利用P ...
- shiro框架的四中权限控制方式
https://www.cnblogs.com/cocosili/p/7103025.html 一.在自定义的realm中进行权限控制 在applicationContext.xml文件中添加 /a ...
- JavaWeb项目:Shiro实现简单的权限控制(整合SSM)
该demo整合Shiro的相关配置参考开涛的博客 数据库表格相关设计 表格设计得比较简单,导航栏直接由角色表auth_role的角色描述vRoleDesc(父结点)和角色相关权限中的权限描述(标记为 ...
- MySQL用户认证及权限控制
一.MySQL用户认证: 登录并不属于访问控制机制,而属于用户身份识别和认证: 1.用户名—user 2.密码—password 3.登录mysqld主机—host 实现用户登录MySQL,建立连接. ...
- shiro框架学习-7- Shiro权限控制注解和编程方式
讲解权限角色控制 @RequiresRoles, @RequiresPermissions等注解的使用和编程式控制 配置文件的方式 使用ShiroConfig 注解方式 @RequiresRoles( ...
随机推荐
- XAF创建一个DashBoard
1.首先启动windows程序之后点击DashBoard导航栏 2.接着点击新建按钮,开始创建一个DashBoard 3.接着根据你的数据来源选择数据源,这里我选择了数据库 4.接着填好你的服务器和数 ...
- JMeter在linux服务器上使用
环境部署: 1.在Linux服务器先安装jdk:此步骤省略,可参考百度经验:https://jingyan.baidu.com/article/6b18230980c294ba59e15967.htm ...
- ccf-炉石传说-201609-3
大概是CCF 第三题比较简洁的一道题吧 尽量设计好一个数据结构: node t[2][10]: 存双方的英雄和随从 int num[2]: 存隋朝的数量 用p来实现双方的切换,因为有统一 的接口 ...
- 在进行多次scanf时,printf输出错误
随便一处代码,经过改正后,输出正确的 ''' #include <stdio.h> int main(){ int T; scanf("%d",&T ...
- javaWeb-Servlet工作原理
1.客户发出请求—>Web 服务器转发到Web容器Tomcat: 2.Tomcat主线程对转发来用户的请求做出响应创建两个对象:HttpServletRequest和HttpServletRes ...
- 添加本地jar包到maven仓库
mvn install:install-file -Dfile=D:\video-lib\log4j-.jar -DgroupId=video -DartifactId=log4j -Dversion ...
- [C]最大公约数和最小公倍数
/*求最大公约数和最小公倍数 编写程序,在主函数中输入两个正整数 a,b,调用两个函数 fun1() 和 fun2(),分别求 a 和 b 的最大公约数和最小公倍数,在主函数中输出结果. */ #in ...
- Java CAS同步机制 实践应用
利用CAS实现原子操作类AtomicInteger (这是自定义的AtomicInteger:java有封装好的原子操作AtomicInteger类): class AtomicInteger { p ...
- Visual Studio 2017 Android 调试无法连接到虚拟机
输出窗口输出如下: 1>Starting deploy 4.5" KitKat (4.4) HDPI Phone ... 1>Starting emulator 4.5" ...
- influxdb 全家桶运行
一个简单的demo,集成了telegraf,influxdb,chronograf,kapacitor,nginx,一张来自官方的参考图 组件集成图 环境准备 使用docker-compose doc ...