Shiro
一:Shiro基础知识
学习Shiro之前需要了解的知识:权限管理
1:什么是权限管理?
只要有用户管理参与的系统一般都需要有权限管理,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。
对权限的管理分为两大类别:
- 用户认证
- 用户授权
1.1:用户认证
用户认证,用户去访问系统,系统要验证用户身份的合法性
最常用的用户身份验证的方法:1、用户名密码方式、2、指纹打卡机、3、基于证书验证方法。。系统验证用户身份合法,用户方可访问系统的资源。
从用户认证可以抽取出这么几个概念:
- subject主体:理解为用户,可能是程序,都要去访问系统的资源,系统需要对subject进行身份认证
- principal身份信息:通常是唯一的,一个主体有多个身份信息,但是都有一个主身份信息
- credential凭证信息:可以是密码,证书,指纹。
1.2:用户授权
用户授权,简单理解为访问控制,在用户认证通过后,系统对用户访问资源进行控制,用户具有资源的访问权限才可以访问
授权的过程:主体认证后,系统进行访问控制
subject必须具备资源的访问权限才可以访问该资源。
权限/许可(permission):针对资源的权限和许可,subject具有permission访问资源,如何访问/操作需要定义permission,权限例如:添加用户,商品删除
资源可以分为两种:
- 资源类型:系统的用户信息就是资源类型,相当于java类。
- 资源实例:系统中id为001的用户就是资源实例,相当于new的java对象。
1.3:分配权限
用户需要分配相应的权限才可以访问相应的资源。
通常给用户分配资源权限需要将权限信息持久化,比如:存储在数据库中。
1.3.1:基于角色的访问控制
// 判断subject如果有admin角色
if(subject.hasRole("admin")) {
System.out.println("有admin这个角色");
} else {
System.out.println("有admin这个角色");
}
基于角色的访问控制是不利于系统维护(可扩展性不强)。
1.3.2:基于资源的访问控制
资源在系统中是不变的,比如资源有:类中的方法,页面中的按钮。
// 判断是否有search这个权限
if (subject.isPermitted("search")) {
System.out.println("有search权限");
} else {
System.out.println("无search权限");
}
// 判断是否具有多个权限
if (subject.isPermittedAll("add","update")) {
System.out.println("有\"add\",\"update\"权限");
} else {
System.out.println("没有\"add\",\"update\"权限");
}
建议使用基于资源的访问控制实现权限管理。
二:粗粒度和细粒度权限
粗粒度权限例子:超级管理员可以访问用户添加页面,用户信息等全部页面。部门管理员可以访问用户信息页面包括页面中的全部按钮。
细粒度权限例子:对资源实例的权限管理。资源实例就是资源类型的具体化,例如:用户id为001的修改连接,1110班的用户信息。细粒度权限管理就是数据级别的权限管理。
系统有一个用户列表查询页面,对用户列表查询分权限, 如果粗颗粒管理,张三和李四都有用户列表查询的权限,张三和李四都可以访问用户列表查询。 进一步进行细颗粒管理,张三(行政部)和李四(开发部)只可以查询自己本部门的用户信息。 张三只能查看行政部 的用户信息,李四只能查看开发部门的用户信息。 细粒度权限管理就是数据级别的权限管理。
1:如何实现粗粒度权限管理
粗粒度权限管理比较容易将权限管理的代码抽出来在系统架构级别处理。比如:通过**springmvc的拦截器实现授权
对细粒度权限管理在数据级别是没有共性可言,针对细粒度权限管理就是系统业务逻辑的一部分,在业务层去处理相对比较简单
1.1:基于URL拦截
Web系统
-
通过filter过滤器实现url的拦截
<filter> <filter-name>ShiroFilter</filter-name> <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class> </filter> <filter-mapping> <filter-name>ShiroFilter</filter-name> <url-pattern>*.html</url-pattern> </filter-mapping>
-
通过springmvc的拦截器实现url的拦截
<!--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>*.html</url-pattern> </filter-mapping>
三:什么是Shiro
shiro是apache的一个开源框架,是一个权限管理的框架,实现 用户认证、用户授权。
1:Apache Shiro Features 特性
- **Authentication(认证):**用户身份识别,通常被称为用户“登录”
- **Authorization(授权):**访问控制。比如某个用户是否具有某个操作的使用权限。
- **Session Management(会话管理):**特定于用户的会话管理,甚至在非web 或 EJB 应用程序。
- **Cryptography(加密):**在对数据源使用加密算法加密的同时,保证易于使用。
- **Web支持:**Shiro的Web支持API有助于保护Web应用程序。
- **缓存:**缓存是Apache Shiro API中的第一级,以确保安全操作保持快速和高效
- **并发性:**Apache Shiro支持具有并发功能的多线程应用程序。
- **测试:**存在测试支持,可帮助您编写单元测试和集成测试,并确保代码按预期得到保障。
- **“运行方式”:**允许用户承担另一个用户的身份(如果允许)的功能,有时在管理方案中很有用
- **“记住我”:**记住用户在会话中的身份,所以用户只需要强制登录即可。
Shiro不会去维护用户、维护权限,这些需要我们自己去设计/提供,然后通过相应的接口注入给Shiro
2:High-Level Overview 高级概述
在概念层,Shiro 架构包含三个主要的理念:Subject,SecurityManager和 Realm
- **Subject:**当前用户,Subject 可以是一个人,但也可以是第三方服务、守护进程帐户、时钟守护任务或者其它–当前和软件交互的任何事件。
- **SecurityManager:**管理所有Subject,SecurityManager 是 Shiro 架构的核心,配合内部安全组件共同组成安全伞。
- **Realms:**用于进行权限信息的验证,我们自己实现。Realm 本质上是一个特定的安全 DAO:它封装与数据源连接的细节,得到Shiro 所需的相关的数据。在配置 Shiro 的时候,你必须指定至少一个Realm 来实现认(authentication)和/或授权(authorization)。
3:身份验证流程
- 首先调用
Subject.login(token)
进行登录,其会自动委托给Security Manager
,调用之前必须通过SecurityUtils.setSecurityManager()
设置。 SecurityManager
负责真正的身份验证逻辑;它会委托给Authenticator
进行身份验证Authenticator
才是真正的身份验证者,ShiroAPI中核心的身份认证入口点,此处可以自定义插入自己的实现Authenticator
可能会委托给相应的AuthenticationStrategy
进行多Realm身份验证,默认ModularRealmAuthenticator
会调用AuthenticationStrategy
进行多Realm身份验证Authenticator
会把相应的token传入Realm,从Realm获取身份验证信息,如果没有返回/抛出异常表示身份验证失败。此处可以配置多个Realm,并按照相应的顺序及策略进行访问
四:Shiro认证
1:导入依赖环境
<!--shiro的核心库-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.3</version>
</dependency>
<!--shiro的Web库-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
2:Shiro的认证
2.1:Shiro的配置文件:*.ini
2.1:通过配置文件创建工厂
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
2.2:创建SecurityManager
SecurityManager securityManager = factory.getInstance();
2.3:将SecurityUtils
设置当前运行环境中
SecurityUtils.setSecurityManager(securityManager);
2.4:从SecurityUtils
里创建一个subject
Subject subject = SecurityUtils.getSubject();
2.5:在认证提交前准备token
(令牌),并且输入账号,密码
UsernamePasswordToken token = new UsernamePasswordToken("root", "123456");
2.6:执行认证的提交
try {
subject.login(token);
if(subject.isAuthenticated()) {
System.out.println("登录成功");
if(subject.hasRole("admin")) {
System.out.println("有admin这个角色");
} else {
System.out.println("有admin这个角色");
}
if (subject.isPermitted("search")) {
System.out.println("有search权限");
} else {
System.out.println("无search权限");
}
if (subject.isPermittedAll("add","update")) {
System.out.println("有\"add\",\"update\"权限");
} else {
System.out.println("没有\"add\",\"update\"权限");
}
}
} catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("用户名或密码错误,登陆失败");
}
3:小结—详解流程
- 通过配置文件
*.ini
创建securityManager
- 调用
subject.login()
方法主体提交认证,提交的token
securityManager
进行认证,securityManager
最终由ModularRealmAuthenticator
进行认证ModularRealmAuthenticator
调用IniRealm
(给realm
传入token
)去*.ini
配置文件中查询用户信息IniRealm
根据输入的token
(‘UsernamePasswordToken’)从*.ini
查询用户信息,根据账号查询用户信息- 如果查不到用户信息,就给
ModularRealmAuthenticator
返回用户信息 - 如果查询不到,就给
ModularRealmAuthenticator
返回null
- 如果查不到用户信息,就给
ModularRealmAuthenticator
接受IniRealm
返回Authentication
认证信息- 如果返回的认证信息是
null
,ModularRealmAuthenticator
抛出异常(org.apache.shiro.authc.UnknownAccountException)
- 如果返回的认证信息不是
null
,对IniRealm
返回用户密码和token
中的密码进行对比,如果不一致抛出异常(org.apache.shiro.authc.IncorrectCredentialsException)
- 如果返回的认证信息是
五:自定义Realm
在开发中需要让Realm
去数据库中比对,因此,需要自定义realm
1:自定义realm
package com.shiro.test.javase;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
// 自定义realm需要继承AuthorizingRealm类
public class MyRealm2 extends AuthorizingRealm {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
//权限验证调用
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("权限验证");
String sql = "select ROLE_NAME from shiro_user_role where USER_NAME = ?";
String username = (String) principalCollection.getPrimaryPrincipal();
List<String> roles = jdbcTemplate.queryForList(sql,String.class,username);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRoles(roles);
return info;
}
//登陆的时候调用
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("doGetAuthenticationInfo");
String sql = "select PASSWORD from shiro_user where USER_NAME = ?";
// 从token中取出身份信息
String username = (String) authenticationToken.getPrincipal();
// 从数据库中查询到的密码
String password = jdbcTemplate.queryForObject(sql, String.class, username);
// 如果查询到,则返回认证信息AuthenticationInfo
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, null, getName());
return info;
}
}
2:配置realm
需要在*.ini
配置realm
注入到securityManager
[main]
dataSource=org.springframework.jdbc.datasource.DriverManagerDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/shiro
dataSource.username=root
dataSource.password=99999999
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
#是否检查权限
jdbcRealm.permissionsLookupEnabled=true
jdbcRealm.dataSource=$dataSource
#重写sql语句
#更具用户名查询出密码
jdbcRealm.authenticationQuery = select PASSWORD from shiro_user where USER_NAME = ?
#根据用户名查询出角色名
jdbcRealm.userRolesQuery = select ROLE_NAME from shiro_user_role where USER_NAME = ?
#根据角色名查询出权限
jdbcRealm.permissionsQuery = select PERM_NAME from shiro_role_permission where ROLE_NAME = ?
securityManager.realms=$jdbcRealm