概述
前2天写了web权限管理,那么实际开发中是如何实现权利管理的呢?下面一起来学习一下。
传统方案:通过设置拦截器,基于url的方式进行管理,创建一个user类,用于存储menus,把user存储到session中到前端进行菜单动态显示,而user类的permissions集合用于url拦截,有对应权限才放行。这种方式实现简单,但是不易于维护。
新方案:使用shiro权限管理框架
什么是shiro?
Shiro是apache麾下的
开源java安全框架
,提供了认证、授权、加密、会话管理、与Web集成、缓存等功能,与此类似的框架还有spring security。
shiro功能图
subject:主体,可以是用户也可以是程序,主体要访问系统,系统需要对主体进行认证、授权。
securityManager:安全管理器,主体进行认证和授权都是通过securityManager进行,是shiro的核心。
authenticator:认证器,主体进行认证最终通过authenticator进行的。
authorizer:授权器,主体进行授权最终通过authorizer进行的。
sessionManager:web应用中一般是用web容器对session进行管理,shiro也提供一套session管理的方式。
SessionDao:通过SessionDao管理session数据,针对个性化的session数据存储需要使用sessionDao。
cache Manager:缓存管理器,主要对session和授权数据进行缓存,比如将授权数据通过cacheManager进行缓存管理,和ehcache整合对缓存数据进行管理。
realm:域,领域,相当于数据源,通过realm存取认证、授权相关数据。
shiro架构图
subject:主体,可以是用户也可以是程序,主体要访问系统,系统需要对主体进行认证、授权。
securityManager:安全管理器,主体进行认证和授权都是通过securityManager进行,是shiro的核心。
authenticator:认证器,主体进行认证最终通过authenticator进行的。
authorizer:授权器,主体进行授权最终通过authorizer进行的。
sessionManager:web应用中一般是用web容器对session进行管理,shiro也提供一套session管理的方式。
SessionDao:通过SessionDao管理session数据,针对个性化的session数据存储需要使用sessionDao。
cache Manager:缓存管理器,主要对session和授权数据进行缓存,比如将授权数据通过cacheManager进行缓存管理,和ehcache整合对缓存数据进行管理。
realm:域,领域,相当于数据源,通过realm存取认证、授权相关数据。
示例1
下面是一个认证和授权的入门例子,只需要一个shiro-core的jar包和它所依赖的slf4j-api.jar、commons-logging、
commons-beanutils.jar、hamcrest-core.jar以及Junit,使用maven依赖的话,只要添加shiro-core、commons-logging和Junit就行了,
我使用的是shiro1.3.2。
下面这个例子所用到的数据都在shiro-data-from-ini.ini文件里,模拟数据库的数据,一般是测试用的,真正的项目肯定是自定义realm,然后到数据库中去获取用户信息和权限信息的,例子中对主要步骤都有注释。
shiro-data-from-ini.ini
#模拟用户数据源,帐号=密码,角色,角色...(有realm时不再起作用)
[users]
pens=123,role1,role2
holien=123,role3
#设置角色、权限和资源(有realm时不再起作用)
#格式:角色=资源:操作:实例,资源:操作 相当于 资源:操作:*
[roles]
#角色role1对资源user拥有create、update权限
role1=user:create,user:update
#角色role2对资源user拥有create、delete权限
role2=user:delete
#角色role3对资源user拥有create权限
role3=user:create
#以上的数据在没有设置realm时才起作用
测试类:AuthcAndAuthzByIniData.class
package shiro_authenc;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
/**
* writer: holien
* Time: 2017-08-19 20:30
* Intent: 使用ini文件中测试数据作为数据源进行身份认证和授权
*/
public class AuthcAndAuthzByIniData {
@Test
public void testAuthcAndAuthozByRealm() {
/*
身份认证
*/
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-data-from-ini.ini");
SecurityManager securityManager = factory.getInstance();
// 把安全管理器与安全工具关联起来
SecurityUtils.setSecurityManager(securityManager);
// 模拟用户表单发送过来的帐号密码,默认名称必须是username和password,可以在配置文件更改
String username = "pens";
String password = "123";
// 创建一个口令(用户输入的帐号密码),帐号信息与ini文件中的帐号信息匹配即可认证成功
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 获取用户主体
Subject subject = SecurityUtils.getSubject();
try {
// 登录,即身份认证(另起一个线程执行此方法),到ini文件中去对比用户信息
subject.login(token);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("是否认证:" + subject.isAuthenticated());
// 退出后再判断,无论认证还是授权都是false
// subject.logout();
// System.out.println("是否认证:" + subject.isAuthenticated());
/*
基于角色的授权
*/
// 验证是否具有指定角色
System.out.println("是否具有角色role1:" + subject.hasRole("role1"));
// 验证是否具有其中某个角色(这里我觉得使用多个hasRole方法进行逻辑判断比较直观)
List<String> list = Arrays.asList("role1", "role2");
boolean[] b = subject.hasRoles(list);
System.out.println(b[0] + " " + b[1]);
// 验证是否具有某组角色,需要多个角色一起拥有
System.out.println("是否同时具有角色role1和role2:" + subject.hasAllRoles(Arrays.asList("role1", "role2")));
/*
基于资源的授权
*/
System.out.println("是否具有对用户的创建权限:" + subject.isPermitted("user:create"));
System.out.println("是否同时具有对用户的创建、更新、删除权限:" +
subject.isPermittedAll("user:create", "user:update", "user:delete"));
// 与isPermission不同,checkPermisson如果检测不到相应的权限会抛异常
subject.checkPermission("user:create");
// subject.checkPermission("user:batch"); // 抛出异常
}
}
这里先看看shiro自带的md5加密的基本使用例子,下面会使用到
package shiro_authenc;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.junit.Test;
/**
* writer: holien
* Time: 2017-08-20 20:38
* Intent: shiro自带的MD5加密
*/
public class MD5Test {
@Test
public void testMD5() {
String password = "123";
// MD5加密加盐
String salt = "110";
// 参数1:待加密密码 参数2:盐 参数3:hash迭代次数
Md5Hash md5Hash = new Md5Hash(password, salt, 1);
String md5Password = md5Hash.toString();
System.out.println(md5Password); // 5319bf4ef8f5029ec32a4ad62a3f8eff
}
}
示例2
shiro-realm.ini
[main]
#定义凭证(密码)匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#指定散列算法
credentialsMatcher.hashAlgorithmName=md5
#散列次数
credentialsMatcher.hashIterations=1
#设置自定义的realm
customRealm=shiro_authenc.CustomRealm
#将凭证匹配器设置到realm中
customRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$customRealm
此ini文件定义了凭证匹配器,使用了md5算法,迭代1次;自定义realm类的引用配置到名为customRealm的realm,并把前面定义的凭证匹配器配置给customRealm,最后把customRealm配置到securityManager中。
自定义realm, 继承自AuthorizingRealm的CustomRealm.class
package shiro_authenc;
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.apache.shiro.util.ByteSource;
import java.util.ArrayList;
import java.util.Arrays;
/**
* writer: holien
* Time: 2017-08-20 14:47
* Intent: 自定义realm,其中的用户信息需要到数据库中查询,在这里使用测试数据,
* 配置了realm就不会再读取ini文件的user了
*/
public class CustomRealm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 认证成功后,用户身份会被存进principalCollection,我们根据主用户身份去数据库取相应的角色和权限
String username = (String) principalCollection.getPrimaryPrincipal();
// 模拟从数据库取出的资源权限,格式:资源:操作:实例
ArrayList<String> permissions = new ArrayList<>();
permissions.add("user:create");
permissions.add("user:update");
permissions.add("user:delete");
// 把数据库取出的角色或权限存入info对象中,供授权器校验
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(permissions);
info.addRoles(Arrays.asList("role1", "role2"));
return info;
// 若查询不到对应的角色或权限,则返回null
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 从token取出用户输入的帐号,根据此帐号去数据库取帐号,下面以userCode来模拟
String username = (String) authenticationToken.getPrincipal();
// 根据用户输入的帐号从数据库取出帐号,我们假设取出的帐号叫userCode
String userCode = "pens";
if (userCode == null || userCode.equals("")) {
// 如果帐号不存在,返回null
return null;
} else {
// 模拟根据帐号从数据库取出的散列密码(md5散列一次,盐为110),查不到返回null,查得到返回info
String password = "5319bf4ef8f5029ec32a4ad62a3f8eff";
// 从数据库获取盐,假设为110
String salt = "110";
// realm会根据此对象包含的password(数据库)与token中的password(用户输入的密码进行加盐加密)做对比,加密算法在ini文件中指定
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, ByteSource.Util.bytes(salt), this.getName());
return info;
}
}
}
测试类:
testAuthcAndAuthozByRealm.class
package shiro_authenc;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
/**
* writer: holien
* Time: 2017-08-19 20:30
* Intent: 使用realm作为数据源进行身份认证和授权
*/
public class AuthcAndAuthzByRealm {
@Test
public void testAuthcAndAuthozByRealm() {
/*
身份认证
*/
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
SecurityManager securityManager = factory.getInstance();
// 把安全管理器与安全工具关联起来
SecurityUtils.setSecurityManager(securityManager);
// 模拟用户表单发送过来的帐号密码,默认名称必须是username和password,可以在配置文件更改
String username = "pens";
String password = "123";
// 创建一个口令(用户输入的帐号密码),帐号信息与realm去数据库中获取的帐号信息相同即可认证成功
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 获取用户主体
Subject subject = SecurityUtils.getSubject();
try {
// 登录,即身份认证(另起一个线程执行此方法),到realm中去对比用户信息
subject.login(token);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("是否认证:" + subject.isAuthenticated());
// 退出后再判断,无论认证还是授权都是false
// subject.logout();
// System.out.println("是否认证:" + subject.isAuthenticated());
/*
基于角色的授权
*/
// 验证是否具有指定角色
System.out.println("是否具有角色role1:" + subject.hasRole("role1"));
// 验证是否具有其中某个角色(这里我觉得使用多个hasRole方法进行逻辑判断比较直观)
List<String> list = Arrays.asList("role1", "role2");
boolean[] b = subject.hasRoles(list);
System.out.println(b[0] + " " + b[1]);
// 验证是否具有某组角色,需要多个角色一起拥有
System.out.println("是否同时具有角色role1和role2:" + subject.hasAllRoles(Arrays.asList("role1", "role2")));
/*
基于资源的授权
*/
System.out.println("是否具有对用户的创建权限:" + subject.isPermitted("user:create"));
System.out.println("是否同时具有对用户的创建、更新、删除权限:" +
subject.isPermittedAll("user:create", "user:update", "user:delete"));
// 与isPermission不同,checkPermisson如果检测不到相应的权限会抛异常
subject.checkPermission("user:create");
// subject.checkPermission("user:batch"); // 抛出异常
}
}
其中login方法一经调用就会到
customRealm中的
doGetAuthenticationInfo方法去验证用户信息,而
hasRole方法和isPermitted等方法一经调用都会到customRealm中的
doGetAuthorizationInfo方法
去查询,这里有点耗资源,等spring整合时,使用ehcache做缓存,就不用每次都到customRealm中获取了,直接返回缓存的结果。
总结一下流程
认证流程:
1、subject(主体)请求认证,调用subject.login(token)
2、SecurityManager (安全管理器)执行认证
3、SecurityManager通过ModularRealmAuthenticator进行认证。
4、ModularRealmAuthenticator将token传给realm,realm根据token中用户信息从数据库查询用户信息(包括身份和凭证)
5、realm如果查询不到用户给ModularRealmAuthenticator返回null,ModularRealmAuthenticator抛出异常(用户不存在)
6、realm如果查询到用户给ModularRealmAuthenticator返回AuthenticationInfo(认证信息)
7、ModularRealmAuthenticator拿着AuthenticationInfo(认证信息)去进行凭证(密码 )比对。如果一致则认证通过,如果不一致抛出异常(凭证错误)。
授权流程:
1、对subject进行授权,调用方法isPermitted("permission串")
2、SecurityManager执行授权,通过ModularRealmAuthorizer执行授权
3、ModularRealmAuthorizer执行realm(自定义的CustomRealm)中的doGetAuthorizationInfo方法从数据库查询权限数据
4、realm从数据库查询权限数据,返回给ModularRealmAuthorizer
5、ModularRealmAuthorizer调用PermissionResolver进行权限串比对
6、如果比对后,isPermitted中"permission串"在realm查询到权限数据中,说明用户访问permission串有权限,否则 没有权限,抛出异常。
下一篇就是shiro和spring的整合了,不能再拖拖拉拉了,加油...
最后
以上就是直率唇膏为你收集整理的shiro快速入门的全部内容,希望文章能够帮你解决shiro快速入门所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复