目录
主流权限管理方案
- -Shiro框架(Apache组织)
优势:简单易用,开发人员只需花费很短的学习时间,就可以完成项目中复杂的权限管理的开发。
- -Spring Security框架(Spring技术栈)
1 权限的管理
1.1 什么是权限管理
基本上有用户参与的系统都要进行权限管理。权限管理实现对用户访问系统的控制,并按照安全策略控制用户可以访问且只能访问被授权的资源。
权限管理包括用户身份认证
和授权
两部分。认证,即看当前访问系统的用户是否具有访问该系统的权限;授权,即看认证通过的用户是否具有系统资源的访问权限。
1.2 什么是身份认证
身份认证就是判断一个用户是否为合法用户的处理过程。
最常用的认证方式是系统通过验证 用户输入的用户名和密码 与 系统中存储的该用户的用户名和密码 是否一致,判断该用户是否通过认证访问系统。
1.3 什么是授权
授权即访问控制,控制不同身份的用户能访问哪些资源。进行身份认证后,需进行权限控制,用户有权限才能授权访问资源;对没有权限的一些资源则无法访问。
2 Shiro
Shiro是Apache旗下一个功能强大且易于使用的开源安全框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。使用shiro就可以非常快速的完成认证、授权等功能的开发,降低系统成本。
2.1 Shiro详细架构
1.Subject(主体):
外部应用与subject进行交互,它记录了当前操作用户,可以将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序或第三方服务等。
2.SecurityManager(安全管理器) :
Shiro 的核心
,用来协调管理组件工作,对全部的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等。
SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager三个接口。源码如下:
public interface SecurityManager extends Authenticator, Authorizer, SessionManager {
Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException;
void logout(Subject subject);
Subject createSubject(SubjectContext context);
}
3.Authenticator(认证管理器):
负责对用户身份进行认证。
4.Authorizer(授权管理器):
用户认证通过后,在访问资源时需要通过授权器判断用户是否有操作权限。
5.SessionManager(会话管理):
负责创建并管理用户 Session 生命周期,提供一个强有力的 Session 体验。
6.SessionDAO:
代表 SessionManager 执行 Session 持久(CRUD)动作,它允许任何存储的数据挂接到 session 管理基础上。
7.CacheManager(缓存管理器):
提供创建缓存实例和管理缓存生命周期的功能。
8.Cryptography(加密管理器):
提供了加密方式的设计及管理。
9.Realms(领域对象):
Realm相当于datasource,SecurityManager进行安全认证需要通过Realm获取用户权限数据,如:若用户身份数据在数据库,那么realm就需要从数据库获取用户身份信息。
注意:Realm不光是从数据源取数据,他其中还有认证授权校验相关的代码。
3 Shiro中的认证
3.1 关键对象
-
Subject:主体
主体可以是访问系统的用户、程序等,进行认证的都可称为主体 -
Principal:身份信息
主体进行身份认证的标识,必须具有唯一性,如用户名、手机号等。一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。 -
credential:凭证信息
只有主体自己知道的安全信息,如密码、证书等。
3.2 认证流程
3.3 Shiro入门案例
注意:本案例中的shiro.ini配置文件是把数据库中的用户名和密码已写死。
给 subject.login(token);
这行加断点,看底层源码的调用过程如下:
总结:
-
1、 最终在
SimpleAccountRealm
的doGetAuthenticationInfo()
方法中完成了用户名校验;
在AuthenticatingRealm
的assertCredentialsMatch()
方法中完成了密码校验(自动完成密码校验)。 -
2、Realm类关系图:
认证realm:
AuthenticatingRealm
-->doGetAuthenticationInfo()
授权realm:
AuthorizingRealm
-->doGetAuthorizationInfo()
-
3、当我们自定义Realm时,只需继承
AuthorizingRealm
就可以了,他的源码中有两个方法,分别是认证和授权。
3.4 自定义Realm实现认证
3.4.1 自定义CustomerRealm
package src.main.java.cn.edu.realm;
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;
/**
* 自定义realm实现
* 将认证、授权数据的来源转为数据库的实现
*/
public class CustomerRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//在token中获取用户名
String principal = (String) token.getPrincipal();
System.out.println(principal);
//根据身份信息使用jdbc mybatis查询相关数据库
if ("xiaochen".equals(principal)){
//参数1:返回数据库中正确的用户名
//参数2:返回数据库中正确的密码
//参数3:提供当前realm的名字 this.getName()
return new SimpleAuthenticationInfo(principal,"123",this.getName());
}
return null;
}
}
3.4.2 测试使用自定义realm
package cn.edu.shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
import src.main.java.cn.edu.realm.CustomerRealm;
/**
* 使用自定义realm
*/
public class TestCusRealmAuthenticator {
public static void main(String[] args) {
//1.创建SecurityManager
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//2.设置自定义realm
defaultSecurityManager.setRealm(new CustomerRealm());
//3.将安全工具类设置安全管理器
SecurityUtils.setSecurityManager(defaultSecurityManager);
//4.通过安全工具类获取subject
Subject subject = SecurityUtils.getSubject();
//5.创建token
UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
try {
subject.login(token);
subject.isAuthenticated();
System.out.println("登录成功");
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("用户名错误");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误");
}
}
}
3.5 MD5加密和Salt
3.5.1 MD5算法
-
特点:
MD5算法不可逆
,只能根据明文生成密文,不能由密文反推出明文;
如果内容相同,无论执行多少次MD5,生成结果是一致的;
MD5生成结果始终是一个16进制的32位长度的字符串。 -
作用:
加密
或签名(校验和)
。
如对用户输入的密码加密存储或对文件内容做校验,判断两个文件内容是否一致等。
3.5.2 MD5&Salt业务流程
3.5.3 MD5加密入门案例
package cn.edu.shiro;
import org.apache.shiro.crypto.hash.Md5Hash;
public class TestShiroMD5 {
public static void main(String[] args) {
//创建一个md5算法:并未加密
// Md5Hash md5Hash = new Md5Hash();
// md5Hash.setBytes("123".getBytes());
// String s = md5Hash.toHex();
// System.out.println(s);//313233
//使用MD5
Md5Hash md5Hash = new Md5Hash("123");
System.out.println(md5Hash.toHex());//202cb962ac59075b964b07152d234b70
//使用MD5+Salt
Md5Hash md5Hash1 = new Md5Hash("123", "X0*7ps");
System.out.println(md5Hash1.toHex());//8a83592a02263bfe6752b2b5b03a4799
//使用MD5+Salt+hash散列
Md5Hash md5Hash2 = new Md5Hash("123", "X0*7ps", 1024);
System.out.println(md5Hash2.toHex());//e4f9bf3e0c58f045e62c23c533fcf633
}
}
3.5.4 使用MD5和Salt认证入门案例
实际是将盐和散列后的值存在数据库中,realm从数据库取出盐和加密后的值由shiro自动完成密码校验。
3.5.4.1 自定义Md5Realm
package cn.edu.realm;
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;
/**'
* 使用自定义realm:加入md5+salt+hash
*/
public class MD5Realm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取身份信息
String principal = (String) token.getPrincipal();
//根据用户名查询数据库
if("xiaochen".equals(principal)){
//参数1:数据库用户名
//参数2:数据库md5+salt之后的密码
//参数3:注册时的随机盐
//参数4:realm的名字
return new SimpleAuthenticationInfo(principal,
"e4f9bf3e0c58f045e62c23c533fcf633",
ByteSource.Util.bytes("X0*7ps"),
this.getName());
}
return null;
}
}
3.5.4.2 测试Md5Realm
package cn.edu.shiro;
import cn.edu.realm.MD5Realm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
public class TestMd5RealmAuthenticator {
public static void main(String[] args) {
//创建安全管理器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//注入realm
MD5Realm realm = new MD5Realm();
//设置realm使用hash凭证匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//使用算法
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//散列次数
hashedCredentialsMatcher.setHashIterations(1024);
realm.setCredentialsMatcher(hashedCredentialsMatcher);
defaultSecurityManager.setRealm(realm);
//将安全管理器注入安全工具类
SecurityUtils.setSecurityManager(defaultSecurityManager);
//通过安全工具类获取subject
Subject subject = SecurityUtils.getSubject();
//认证
UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
try {
subject.login(token);
System.out.println("登录成功");
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("用户名错误");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误");
}
}
}
4 Shiro中的授权
4.1 授权
授权,即访问控制,控制不同身份的用户能访问哪些资源。主体进行身份认证后,需进行权限控制,用户有权限才能授权访问资源;对没有权限的一些资源则无法访问。
4.2 关键对象
授权,简单理解就是:谁 对 什么资源 进行 什么操作。
主体(Subject)
:
访问系统资源的用户或程序等。资源(Resource)
:
如:系统菜单、页面、按钮等。权限/许可(Permission)
:
规定了主体对资源的操作许可,如用户的查询权限、添加权限、某个用户的修改权限等,通过权限可以知道主体都对哪些资源有哪些操作许可。
4.3 授权流程
4.4 权限管理的授权方式
基于角色的访问控制
RBAC(Role-Based Access Control)以角色为中心进行访问控制
if(subject.hasRole("admin")){
//操作什么资源
}
基于资源的访问控制
RBAC(Resource-Based Access Control)以资源为中心进行访问控制
if(subject.isPermission("user:find:*")){//资源标识符(who):操作:资源(资源实例/资源类型)标识符
//对user的所有资源都能进行查询操作
}
4.5 权限字符串
- 规则:
资源标识符 : 操作 : 资源实例标识符
对哪个资源的哪个实例有哪些操作。
例如:
用户创建权限:user:create
用户实例001的所有权限:user:*:001
4.6 shiro授权编程实现方式
- 编程式
Subject subject=SecurityUtils.getSubject();
if(subject.hasRole("admin")){
//有权限
}else{
//无权限
}
- 注解式
@RequiresRoles("admin")
public void hello(){
//有权限
}
- 标签式
<!-- JSP 标签:在JSP页面通过相应标签完成 -->
<shiro:hasRole name="admin">
<!-- 有权限 -->
</shiro:hasRole>
注意:Thymeleaf中使用shiro需要额外集成。
4.7 shiro授权编程实现
4.7.1 编辑Md5Realm
package cn.edu.realm;
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;
/**'
* 使用自定义realm:加入md5+salt+hash
*/
public class MD5Realm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("=============");
String primaryPrincipal = (String) principals.getPrimaryPrincipal();
System.out.println("身份信息:"+primaryPrincipal);
//根据身份信息 用户名 获取当前用户的角色信息以及权限信息 xiaocehn admin user
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//将数据库中查询角色信息赋值给权限对象
simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addRole("user");
//将数据库中查询权限信息赋值给权限对象
simpleAuthorizationInfo.addStringPermission("user:*:01");
simpleAuthorizationInfo.addStringPermission("product:create");
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取身份信息
String principal = (String) token.getPrincipal();
//根据用户名查询数据库
if("xiaochen".equals(principal)){
//参数1:数据库用户名
//参数2:数据库md5+salt之后的密码
//参数3:注册时的随机盐
//参数4:realm的名字
return new SimpleAuthenticationInfo(principal,
"e4f9bf3e0c58f045e62c23c533fcf633",
ByteSource.Util.bytes("X0*7ps"),
this.getName());
}
return null;
}
}
4.7.2 测试shiro授权
package cn.edu.shiro;
import cn.edu.realm.MD5Realm;
import java.util.Arrays;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
public class TestMd5RealmAuthenticator {
public static void main(String[] args) {
//创建安全管理器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//注入realm
MD5Realm realm = new MD5Realm();
//设置realm使用hash凭证匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//使用算法
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//散列次数
hashedCredentialsMatcher.setHashIterations(1024);
realm.setCredentialsMatcher(hashedCredentialsMatcher);
defaultSecurityManager.setRealm(realm);
//将安全管理器注入安全工具类
SecurityUtils.setSecurityManager(defaultSecurityManager);
//通过安全工具类获取subject
Subject subject = SecurityUtils.getSubject();
//认证
UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
try {
subject.login(token);
System.out.println("登录成功");
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("用户名错误");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误");
}
//认证用户进行授权
if(subject.isAuthenticated()){
//基于角色权限控制
System.out.println(subject.hasRole("admin"));//true
//基于多角色权限控制
System.out.println(subject.hasAllRoles(Arrays.asList("admin","user")));//true
//是否有其中一个角色
boolean[] roles = subject.hasRoles(Arrays.asList("admin","user","super"));
for (boolean b : roles) {
System.out.println(b);//true true false
}
System.out.println("******************");
//基于权限字符串的访问控制 资源标识符:操作:资源类型
System.out.println("权限:"+subject.isPermitted("user:*:01"));//true
//分别具有哪些权限
boolean[] permitted = subject.isPermitted("user:*:01","order:*:10");
for (boolean b : permitted) {
System.out.println(b);//true false
}
//同时具有哪些权限
boolean permittedAll = subject.isPermittedAll("user:*:01","product:*");
System.out.println(permittedAll);//false
}
}
}