Shiro权限框架
认证: 判断用户是否存在 判断账号和密码是否正确
授权: 就是给你一定的权限,规定你能访问和操作哪些资源,授权之前一般先进行认证
- 什么是权限管理
权限管理,一般指根据系统设置的安全规则或者安全策略,用户可以 访问而且只能访问自己被授权的资源。权限管理几乎出现 在任何系统里面。常用于后台管理系统.
通用权限管理,一般指的是5张表:权限表,角色表,用户表,用户角色表.,角色权限表.
权限表与角色表,用户表与角色表是多对多关系;角色权限表,用户角色表是它们的中间表
用户登录时,通过用户名–>含有的角色–>查询含有的权限–>可以操作的资源
权限管理一般包括用户身份认证和授权两个部分,简称认证授权
1. 认证
- 概念
身份认证指的就是判断一个用户是否为合法用户的处理过程.
最常用的简单身份认证就是通过用户名和密码和数据库存储的用户名和密码来判断用户身份是否正确.
- 身份认证的流程
用户对某一个资源进行访问,后台先判断该资源是否可以匿名访问,如果可以放行,如果不可以就判断它是否已经进行了认证,如果已经进行认证了就放行,如果没有进行认证,跳转到认证页面让用户进行登录认证,后台根据用户输入的账号和密码与数据库存储的信息进行对比,如果正确就放行,如果不正确就跳转到认证页面.
- 关键对象
Subject: 主体 , 访问系统的用户. 一般需要进行认证的都称之为主体
Principal: 身份信息, 是主体进行身份认证的一种表示,这个身份一般具有唯一性.
credential: 凭证信息, 只有主体才知道的安全信息.比如密码,密钥,口令
2. 授权
- 概念
授权即访问控制,就是控制谁能够访问哪些资源
主体进行身份认证后需要分配权限才可以访问系统资源
- 流程
- 关键对象
授权可以理解为: 谁对什么进行了怎样操作
谁: subject:主体,需要访问系统的资源
什么:Resourc,资源的类型和实例,比如,资源的类型:我是一个q群管理员,我可以删除群成员;资源的实例:删除具体的成员
怎样:Permission:权限或者许可,规定了主体对资源的操作许可
注意:权限离开了资源是没有意义的,通过权限可知主体对系统中哪些资源有哪些操作许可.
- 权限分为粗颗粒和细颗粒
粗颗粒权限是指定资源类型的权限,细颗粒权限是指资源实例的权限
- 权限模型
主体(用户)表: 账号和密码
资源表:资源名称和地址
权限表:权限名称和资源id
角色表:角色名称
角色权限表:角色id和权限id
用户角色表:角色id和用户id
一般会把资源表和权限表合并成一张表
- 权限分配
对主体分配权限,主体只允许在自己权限范围内对资源进行操作
权限分配的数据需要进行持久化,根据模型图搭建并将用户权限信息存储到数据库中
- 权限控制
用户拥有权限就可以操作权限范围内的资源,对于系统来说,它不知道主体是否有访问权限,需要对主体的访问进行控制
- 权限控制的模式
- 基于角色的权限控制
RBAC基于角色的访问控制,它是以角色为中心进行访问控制.
缺点:角色进行访问控制的时候,它的颗粒度比较粗,如果主体角色发生变更,那么需要修改源代码,可扩展性较差
- 基于资源的权限控制
RBAC是基于资源的权限控制,那么它是以资源为中心进行访问控制
优点: 权限标识是提前设定好的.当某个资源放开权限时,只需要在权限表添加对应的权限即可.不需要修改源代码,可扩展性好
3. 权限管理解决方案
-
使用粗颗粒和细颗粒进行控制
-
基于url拦截
基于url拦截是企业中比较常用的一种解决方案
实现思路:
将系统操作的每一个url配置到权限列表中
将权限对应到角色,将角色分配给用户,用户访问系统资源通过Filter进行拦截
过滤器获取到用户访问url,只要访问的url是用户分配角色中的url,则放行,让用户继续访问
- 使用权限管理框架
使用权限管理框架开发可以节省人力成本,提供了完善的认证和授权功能,有利于系统的维护,方便系统的扩展.
4. Shiro
Shiro是apache旗下的一款免费开源的安全框架.它将软件系统的安全认证相关的功能抽离出来,实现用户身份认证,权限授权,加密,会话管理等功能,组成了一个通用的安全认证框架.
使用Shiro可以快速的完成认证,授权等功能的开发,降低系统成本.
shiro使用非常广泛,既可以在web应用中使用,也可以在非web应用中使用,在集群分布式中使用也比较多
Java领域中的Security使用稍微复杂一点,需要依赖Spring环境,而Shiro相对独立
4.1 Shiro的架构和介绍
- Subject 主体
subject用于和外部应用进行交互,记录了当前操作用户
这里的用户可以理解为浏览器请求的用户,也可以理解为正在运行的程序
Subject是Shiro框架的一个接口,该接口定义了许多关于认证和授权的方法
外部程序可以通过Subject进行认证和授权,而Subject是通过SecurityManager安全管理器进行认证授权
- SecurityManager安全管理器
对所有的Subject进行安全管理,它是整个Shiro的核心.
通过SecurityManager可以完成对Subject进行认证授权.
实质上真正上进行安全认证是通过它里面的模块Authenticator
授权是通过Authorizer来实现的
SecurityManager也是一个接口,它继承了Authenticator,Authorizer,SessionManger这三个接口
- Authenticator认证器
主要是对用户进行身份认证,它是一个接口,shiro提供了Authenticator的实现类,通过实现类基本可以解决大部分的问题;也可以自定义认证器
- Authorizer授权器
用户通过认证器认证通过,在访问系统资源是需要通过授权器判断用户是否有资源的操作权限
realm领域
相当于一个数据库,用来存储信息的.通过SecurityManager进行安全认证,需要通过读取realm中的信息,判断用户是否有此权限资源
用户身份数据要么在数据库存储,要么在realm中存储
如果数据是在数据库中存储,那么realm需要获取用户的身份信息
- SessionManager会话管理器
shiro框架不依赖于web容器中的session,shiro可以在非web应用中使用,也可以在分布式应用中使用.
如果是在分布式应用中使用可以帮助应用实现单点登录
- SessionDAO
它可以将session存储到数据库中,也可以通过JDBC将session存储到数据库中
- CachManager缓存管理器
将用户权限数据存储到缓存中,提供访问性能
5. Shiro快速入门
5.1 认证
subject:主体身份信息,账号,密码
securityManager 安全管理器
reaml存储权限数据
- 引入依赖
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.0</version>
</dependency>
- 在资源包创建shiro.ini
[users]
#用户名=密码
root=123456
admin=123456
- 在test包创建TestShiro类
@Test
public void test01() {
//构建SecurityManagerFactory对象
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//通过factory获取SecurityManager对象
SecurityManager securityManager = factory.getInstance();
//将SecurityManager添加到运行环境中
SecurityUtils.setSecurityManager(securityManager);
//获取主体
Subject subject = SecurityUtils.getSubject();
//创建token令牌,该令牌包含用户的账户和密码
UsernamePasswordToken token = new UsernamePasswordToken("root", "123456");
//进行认证
try {
subject.login(token);
System.out.println("认证通过");
}catch (UnknownAccountException e) {
System.out.println("账户不存在"+e.getMessage());
}catch (IncorrectCredentialsException e) {
System.out.println("密码错误"+e.getMessage());
}
//判断是否认证成功
boolean authenticated = subject.isAuthenticated();
System.out.println(authenticated);//true成功,false失败
}
自定义Realm
- 创建MyRealm类
public class MyRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1. 先获取token的身份信息 用户输入的令牌
String username =(String) authenticationToken.getPrincipal();
//2. 认证
// 连接数据库,对比账号 select password from user u_name = username;
/* boolean authenticated = true/false;
if (authenticated == false)
return null;*/
//假设查到了,获取密码值
String password = "123456";
return new SimpleAuthenticationInfo(username,password,"myRealm");
}
}
- 创建shiro_myRealm.ini
[main]
myRealm=MyRealm类的全路径
securityManager.realms=$myRealm
- 在测试类里
@Test
public void test02() {
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro_myRealm.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("root", "123456");
try {
subject.login(token);
System.out.println("认证通过");
}catch (UnknownAccountException e) {
System.out.println("账户不存在"+e.getMessage());
}catch (IncorrectCredentialsException e) {
System.out.println("密码错误"+e.getMessage());
}
//判断是否认证成功
boolean authenticated = subject.isAuthenticated();
System.out.println(authenticated);//true成功,false失败
}
5.2 授权
- 创建资源文件shiro_privilege.ini
[users]
#账户名=密码值,[角色名称1,角色名称2]
root=123456,role1,role2
admin=123456,role2
[roles]
#角色名称=权限资源
#写法:
# 1. /user/addUser.do,/user/deleteUser.do
# 2. user:addUser.do user:deleteUser.do
role1=/user/addUser.do,/user/deleteUser.do,/user/insertUser.do
role2=/user/selectAllUser.do
- 在测试类中
@Test
public void test03() {
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro_privilege.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("root", "123456");
//授权的前提是先认证
try {
subject.login(token);
System.out.println("认证通过");
}catch (UnknownAccountException e) {
System.out.println("账户不存在: "+e.getMessage());
}catch (IncorrectCredentialsException e) {
System.out.println("密码错误: "+e.getMessage());
}
//判断是否认证成功
boolean authenticated = subject.isAuthenticated();
System.out.println(authenticated);//true成功,false失败
//授权 查看该用户是否有此资源的权限
/* boolean[] permitted = subject.isPermitted("/user/addUser.do","/user/insertUser.do");
for (boolean b : permitted) {
System.out.println(b);
}*/
String[] strings = {"/user/addUser.do","/user/insertUser.do"};
boolean[] permitted = subject.isPermitted(strings);
for (int i = 0; i < permitted.length; i++) {
System.out.println(strings[i]+"权限:"+permitted[i]);
}
//System.out.println("授权是否拥有权限"+permitted);
}
自定义MyRealm
- 修改MyRealm的doGetAuthorizationInfo方法
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//1.获取身份信息
String username =(String) principalCollection.getPrimaryPrincipal();
//2.连接数据库查询该账号下的所有角色和所拥有的权限 user,role,user_role,role,role_authority ,authority
//假设查询到的角色
ArrayList<String> roles = new ArrayList<>();
roles.add("CTO");
roles.add("CEO");
//假设根据角色查询到所拥有的权限资源
ArrayList<String> privileges = new ArrayList<>();
privileges.add("/user/addUser.do");
privileges.add("/user/selectAllUser.do");
//3.把角色和权限进行绑定
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRoles(roles);
info.addStringPermission(String.valueOf(privileges));
return info;
}
- 测试
@Test
public void test04() {
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro_myRealm.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("root", "123456");
//授权的前提是先认证
try {
subject.login(token);
System.out.println("认证通过");
}catch (UnknownAccountException e) {
System.out.println("账户不存在: "+e.getMessage());
}catch (IncorrectCredentialsException e) {
System.out.println("密码错误: "+e.getMessage());
}
//查看该用户拥有哪些角色信息
boolean ceo = subject.hasRole("CEO");
boolean cto = subject.hasRole("CTO");
System.out.println(ceo);
System.out.println(cto);
String[] strings = {"/user/addUser.do","/user/insertUser.do"};
boolean[] permitted = subject.isPermitted(strings);
for (int i = 0; i < permitted.length; i++) {
System.out.println(strings[i]+"权限:"+permitted[i]);
}
}
6 spring整合shiro
- 依赖
已经在上面引入了
-在web.xml添加过滤器
<!--添加spring整合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的核心配置文件applicationContext.xml
<!--配置shiroFilter bean对象-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--1.注入安全管理器securityManager-->
<property name="securityManager" ref="securityManager"/>
<!--2.注入登录的url-->
<property name="loginUrl" value="admin/login.do"/>
<!--3.注入登录成功的url-->
<property name="successUrl" value="/success.jsp"/>
<!--4.注入认证成功后,由于权限不足,无法访问系统资源url-->
<property name="unauthorizedUrl" value="unauthorized.jsp"/>
<!--5.注入url的拦截规则,使用shiro自带的过滤器-->
<property name="filterChainDefinitions">
<value>
<!--具体的拦截规则-->
<!--可以匿名访问该url的资源-->
/admin/login.do=anon
</value>
</property>
</bean>
<!--构建安全管理器对象-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"/>
常用的三个拦截规则
anon 允许匿名访问
authc 需要登录验证才能访问
perms 需要有相应的权限才能访问