引言:
本文主要分享了Shiro和权限管理相关的知识,包括权限管理的简介、权限认证、权限授权、权限系统(演变思路)、基于URL的拦截思路、Shiro的简介、Shiro的内部结构(7板块)、初始的Shiro案例、带有角色权限的案例、自定义安全策略的shiro应用案例、自定义安全策略的shiro加入MD5的应用案例(均附源码);
根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源不多不少;权限包括用户认证和授权两部分,对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问;
每个系统都有权限管理,只不过力度不同
功能:权限验证、用户注册、登录、分配权限、角色管理
授权(是否有权限,权限校验)、认证(处理登录)、加密、会话(用户状态信息的管理)
安全管理器
认证管理器权限管理器Session管理器加密Realms访问数据源的组件(访问用户数据源)认证就是判断一个用户是否是合法用户的一个过程,通过核对相应的信息,来与系统中的信息进行比较是否一致,其中包括用户名密码、指纹、人脸、二维码、刷卡…
认证流程:
访问系统资源判断是否可以匿名访问(可以继续访问,不可以进行下一步)判断用户是否登录(登录继续访问,没有登录进行下一步)输入用户名密码进行身份认证判断是否通过(通过认证继续访问,没有通过继续认证)授权就是访问控制,控制谁能访问那些资源,相当于检票的过程,访问控制就是主体进行身份认证后,需要分配权限,没有权限不可以访问;
授权流程:
访问系统资源进行身份认证判断是否通过认证(没有通过继续认证,通过进行下一步)权限控制(从数据拿出,分配权限)判断是否拥有访问权限(有权限继续访问,没有权限拒绝访问)资源访问分为三级:
匿名可访问认证可访问拥有特定权限可访问我们知道对象就是实体,包括用户、权限、资源、角色
权限系统涉及的关系:
用户–>权限:多对多用户–>角色:多对多角色–>权限:多对多权限–>资源:多对一(一个资源多个权限增删查改)RBAC基于角色的访问控制(Role-Based Access Control)以角色为中心进行访问控制;
缺点:
系统扩展性较差以角色进行访问控制粒度较粗RBAC基于资源的访问控制(Resouce-Based Access Control)以资源为中心进行访问控制;
优点:
系统设计时定义好权限标识系统可扩展性强基于url拦截是企业中常用的权限管理方法,将系统操作的每个url配置在权限表中,将权限对应到角色,将角色分配给用户,用户访问系统功能通过Filter进行过滤,Filter获取用户访问的url,只要访问的url是用户分配角色中的url则放行继续访问;
URL拦截的流程:
访问系统资源获取访问的url判断url是否是公开地址(是继续访问,不是进行下一步)判断是否存在session(没有输入用户名密码,进行身份认证,直到成功并将用户信息url记录到session中;存在进行下一步)授权过滤器拦截获取访问url判断url是否是公开地址(是:继续访问;不是:进行下一步)判断url是否是只要登录就可以访问,无需分配权限(是:继续访问;不是:进行下一步)判断url是否存在权限(存在:放行继续访问;不存在提示无权访问)Shiro是一个Java的安全框架,提供了认证、授权、加密和会话管理功能;是开发权限的快速框架,应用广泛(Web、非Web、集群分布式应用)
Subject:请求主体,外部应用与 subject进行交互,subject记录当前操作用户,用户就是当前操作的主体;外部程序通过subject进行认证授权,subject通过Security Manager安全管理器进行认证授权;SecurityManager:安全管理器,相当于SpringMVC中的DispatcherServlet是Shiro的心脏;所有的交互都通过SecurityManager进行控制,管理着所有Subject负责进行认证和授权、会话及缓存的管理;是一个接口,继承了Authenticator、Authorizer、SessionManager这三个接口;Authenticator:认证器,负责主体(subject)认证的,是一个接口,shiro提供了ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数的需求,也可以自定义认证器;Authorizer:授权器、访问控制器,用来决定主体是否有权限进行相应的操作,即控制着用户能访问应用中的哪些功能,在访问功能时需要通过授权器判断用户是否有此功能的操作权限;Realm:域,可以有1个或多个Realm;是安全实体数据源,用于获取安全实体的,Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;realm不只是从数据源取数据,在realm中还有认证授权校验相关的代码;SessionManager:会话管理,shiro框架提供了一套会话管理,它不依赖web容器的session,自己抽象了一个自己的Session来管理主体与应用之间交互的数据,所以shiro可以使用在非web环境中,也可以将分布式应用的会话集中在一点管理,可使它实现单点登录;SessionDAO:会话管理DAO,是对session会话管理操作的一套接口,比如要将session存储到数据库,可以使用jdbc将会话存储到数据库;此外SessionDAO中可以使用Cache进行缓存,以提高性能;CacheManager:缓存管理,将用户权限存储在缓存,可以提高性能;Crypography:密码管理,Shiro提供了一套加密/解密的组件,方便开发。提供了常用的散列、加/解密算法;在pom文件中导入依赖
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.3.2</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>commons-httpclient</groupId> <artifactId>commons-httpclient</artifactId> <version>3.1</version> </dependency> </dependencies>在resources文件下配置初始化文件——shiro-first.ini
#配置用户名,带用户数据表 [users] kaka=1234 taotao=4567在test文件下编写测试类
import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.crypto.hash.Md5Hash; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.junit.Test; /** * Created by Kak on 2020/9/2. */ public class TestShiroBase { @Test //认证处理 public void testShiroFirst(){ //根据ini初始化文件创建SecurityManager工厂 IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-first.ini"); //生成安全管理器,创建securityManager实例 SecurityManager securityManager = factory.createInstance(); //使用shiro提供的工具装配安全管理器 SecurityUtils.setSecurityManager(securityManager); //获取shiro访问的主体对象 Subject subject = SecurityUtils.getSubject(); //模拟用户登录操作创建访问主体的令牌 AuthenticationToken token = new UsernamePasswordToken("kaka", "1234"); try{ subject.login(token); }catch (IncorrectCredentialsException icex){ System.out.println("用户口令错误!!!"); }catch (UnknownAccountException uaEx){ System.out.println("用户名错误!!!"); }catch (AuthenticationException aex){ System.out.println("用户认证失败!!!"); } if (subject.isAuthenticated()){ System.out.println("用户登录成功!!!"); } } }在resources文件下配置初始化文件——shiro-perms.ini
[users] kaka=1234,role1,role2 taotao=12345,role2,role3 [roles] role1=stu:select,stu:insert role2=stu:update,stu:delete role3=emp:select在test文件下编写测试类
@Test //加入权限 public void testShiroPerms(){ IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-perms.ini"); SecurityManager securityManager = factory.createInstance(); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("kaka","1234"); try{ subject.login(token); }catch (AuthenticationException aex){ System.out.println("用户认证错误,登录失败!!!"); } if(subject.isAuthenticated()){ System.out.println("用户登录成功!!!"); if(subject.isPermitted("stu:select")){ System.out.println("用户有查询权限stu:select"); }else{ System.out.println("无权访问stu:select!!!"); } String[] perms = {"stu:select","stu:delete"}; boolean[] permitted = subject.isPermitted(perms);//我们需要判断的权限 if (permitted[1]){ System.out.println("用户有权访问stu:delete"); } if(!subject.isPermittedAll(perms)){ System.out.println("用户无权访问!!!"); } if (subject.hasRole("role1")){ System.out.println("kaka有role1角色"); } } }在resources文件下配置初始化文件——shiro-realm.ini
[main] #自定义realm myRealm=com.sx.kak.shiro.MyRealm #在securityManager中设置自定义的realm securityManager.realms=$myRealm创建shiro包,在包下创建MyRealm.java
package com.sx.kak.shiro; 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 java.util.HashSet; import java.util.Set; /** * Created by Kak on 2020/9/2. */ public class MyRealm extends AuthorizingRealm{ //shiro中的授权实现 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { Object principal = principalCollection.getPrimaryPrincipal(); //根据用户信息查询数据库中的权限 String userName = (String) principal; //创建用户授权信息对象 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); //创建权限集合 Set<String> perms = new HashSet<String>(); perms.add("stu:select"); perms.add("stu:update"); authorizationInfo.setStringPermissions(perms); return authorizationInfo; } //shiro中的认证实现 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //获取用户信息 String username = (String)authenticationToken.getPrincipal(); //获取密码 Object credentials = authenticationToken.getCredentials(); System.out.println("userName:" + username+"password:"+credentials); //根据用户名查询用户对象信息获取用密码 String userName="kaka"; String password="1234"; //将送来用户账号及根据账号查出的密码(凭证)封装成一个AuthenticationInfo对象,返回 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userName, password, getName()); //将外部获取的用户信息及凭证信息 封装送给Authenticator对象进行认证 return simpleAuthenticationInfo; } }在test文件下编写测试类
@Test //自定义安全策略的shiro public void testShiroRealm(){ IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini"); SecurityManager securityManager = factory.createInstance(); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("kaka", "1234"); try{ subject.login(token); }catch (UnknownAccountException uaex){ System.out.println("账户名不存在!!!"); }catch (IncorrectCredentialsException ice){ System.out.println("凭证错误!!!"); }catch (AuthenticationException ae){ System.out.println("认证失败!!!"); } if(subject.isAuthenticated()){ System.out.println("用户登录成功!!!"); }else{ System.out.println("用户登录失败!!!"); } if(subject.isPermittedAll("stu:select")){ System.out.println("kaka用户有权访问stu:select"); }else{ System.out.println("kaka用户无权访问stu:select"); } }一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致;
//生成MD5密钥 public void testMd5(){ String pwd = "1234"; String salt = "kaka"; Md5Hash md5Hash1 = new Md5Hash(pwd); String md5Str1 = md5Hash1.toString(); System.out.println("md5加密:" + md5Str1); Md5Hash md5Hash2 = new Md5Hash(pwd, salt, 1); String md5Str2 = md5Hash2.toString(); System.out.println("加盐:"+md5Str2); Md5Hash md5Hash3 = new Md5Hash(pwd, salt, 1024); String md5Str3 = md5Hash3.toString(); System.out.println("多次加盐:"+md5Str3); } md5加密:81dc9bdb52d04dc20036dbd8313ed055 加盐:4fa51ff55b001ea9b7c55338b76834f7 多次加盐:eaee658c75dc83917d7be1bd689ff15e创建shiro包,在包下创建MyRealmMd5.java
package com.sx.kak.shiro; 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.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; /** * Created by Kak on 2020/9/2. */ public class MyRealmMd5 extends AuthorizingRealm{ //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //用户名 String principal = (String)authenticationToken.getPrincipal(); //根据用户名查询用户信息(密码) String hashedCredentials ="4fa51ff55b001ea9b7c55338b76834f7"; ByteSource credentialsSalt = ByteSource.Util.bytes("kaka"); /** * principal:用户信息 * hashedCredentials:加密之后的密文 * credentialsSalt:加密时加的盐 * AuthorizingRealm 的派生类名称 */ SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal, hashedCredentials, credentialsSalt, getName()); return authenticationInfo; } }在test文件下编写测试类
@Test //自定义安全策略的shiro加密钥 public void testShiroMd5(){ IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-realm-md5.ini"); SecurityManager securityManager = factory.createInstance(); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("kaka", "1234"); try{ subject.login(token); }catch (UnknownAccountException uaex){ System.out.println("账户名不存在!!!"); }catch (IncorrectCredentialsException ice){ System.out.println("凭证错误!!!"); }catch (AuthenticationException ae){ System.out.println("认证失败!!!"); } if(subject.isAuthenticated()){ System.out.println("用户登录成功!!!"); }else{ System.out.println("用户登录失败!!!"); } }