索引
权限的管理
权限管理包括用户的身份认证和授权两部分,简称认证授权。
身份认证:打游戏登录
授权:1级的鬼剑士不能去18级的地图打怪
Shiro简介
Shiro是阿帕奇下的一个开源,它把系统安全相关功能抽取出来,实现用户身份认证、权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。
Shiro的核心架构
核心架构图
subject:主体,外部应用通过subject接口进行认证授权
Security Manager:安全管理部分
Realm:域,数据来源
Authenticator:认证器
Authorizer:授权器
Session Manager:会话管理,web服务的时候session会交给它管理
Session Dao:回话的DAO层
Cache Manager:缓存管理,用户权限数据会在缓存,提高性能
Realm:域,数据来源
Cryptography:算法部分
Shiro的认证
关键对象
Subject:主体,访问系统的用户。
Principal:身份信息,主体进行认证的标识,必须具有唯一性,一个主体可以有多个身份,但必须有一个主身份。相当于账号
Credential:凭证信息,只有主体自己才知道的安全信息,相当于密码
认证流程
先跑起来(认证)
快速建一个shiro demo
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.7.0</version>
</dependency>
在resources新建ini文件
[users]
zhangsan=123
lisi=123456
模拟登陆
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.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;
public class TestAuthenticator {
public static void main(String[] args) {
//1·创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//2·给安全管理器设置realm
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
//3·SecurityManager 全局安全工具类
SecurityUtils.setSecurityManager(securityManager);
//4·关键对象 subject主体
Subject subject = SecurityUtils.getSubject();
//5·创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","1232");
try {
subject.login(token);
System.out.println("认证成功" + subject.isAuthenticated() + token);
}catch (UnknownAccountException e){
System.out.println("用户名不存在");
}catch (IncorrectCredentialsException e){
System.out.println("密码错误");
}
}
}
简单的实现就完成了,为了替换掉ini作为数据来源,就得自定义Realm。
自定义Realm
/**
* 自定义Realm,将认证/授权数据的来源转为数据库的实现
*/
public class CustomerRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//在token中获取用户名字
String principal = (String) token.getPrincipal();
//根据身份信息使用mybatis查询相关数据库
//假如数据库里有这个用户
if ("zhaoshanhe".equals(principal)){
//参数1:返回数据库中正确的用户名 //参数2:返回数据库中的密码 //参数3:提供当前Realm的名字 this.getName
return new SimpleAuthenticationInfo(principal,"123456",getName());
}
return null;
}
}
/**
* 使用自定义Realm
*/
public class TestCustomerRealmAuthenticator {
public static void main(String[] args) {
//创建securityManager
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//设置自定义realm
defaultSecurityManager.setRealm(new CustomerRealm());
//设置安全工具类
SecurityUtils.setSecurityManager(defaultSecurityManager);
//通过安全工具类获取subject
Subject subject = SecurityUtils.getSubject();
//创建token
UsernamePasswordToken token = new UsernamePasswordToken("zhaoshanhe", "123456");
try {
subject.login(token);
System.out.println("登录成功");
} catch (UnknownAccountException e){
System.out.println("用户名不存在");
}catch (IncorrectCredentialsException e){
System.out.println("密码错误");
}
}
}
上边代码通过脑补jdbc实现了用户登录,但密码是明文
明文密码?MD5?加盐?
加密效果
public static void main(String[] args) {
//使用MD5
Md5Hash hash = new Md5Hash("123456");
System.out.println(hash.toHex());
//使用MD5 + salt(盐)处理,盐完后要替换成随机字符串
Md5Hash hash1 = new Md5Hash("123456","x0*366go");
System.out.println(hash1.toHex());
//使用MD5 + 盐 + hash散列
Md5Hash hash2 = new Md5Hash("123456","x0*366go",1024);
System.out.println(hash2.toHex());
}
e10adc3949ba59abbe56e057f20f883e
ecb5c4a53e65c8db187106601468d0ca
cac6f8e93a1d711cb13b6415e5b835f1
下面开始测试三种加密程度,自定义realm里脑补数据库查密码
MD5不加盐登录
public class TestCustomerMd5RealmAuthenticator {
public static void main(String[] args) {
//创建安全管理器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//注入realm
CustomerMd5Realm realm = new CustomerMd5Realm();
//设置realm使用hash凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("md5");
realm.setCredentialsMatcher(credentialsMatcher);
defaultSecurityManager.setRealm(realm);
//将安全管理器注入安全工具
SecurityUtils.setSecurityManager(defaultSecurityManager);
//安全工具获取subject
Subject subject = SecurityUtils.getSubject();
//认证
UsernamePasswordToken token = new UsernamePasswordToken("zhaoshanhe","123456");
try {
subject.login(token);
System.out.println("登录成功");
}catch (UnknownAccountException e){
System.out.println("用户名不存在");
}catch (IncorrectCredentialsException e){
System.out.println("密码错误");
}
}
}
md5不加盐
import java.lang.reflect.ParameterizedType;
public class CustomerMd5Realm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取身份信息
String principal = (String) token.getPrincipal();
//根据用户名查数据库
if ("zhaoshanhe".equals(principal)){
return new SimpleAuthenticationInfo(principal,"e10adc3949ba59abbe56e057f20f883e",getName());
}
return null;
}
}
登录成功
md5加盐操作
登录代码不变,改掉自定义realm里假密码ecb5c4a53e65c8db187106601468d0ca和盐
//ByteSource.Util.bytes里边放的是盐
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取身份信息
String principal = (String) token.getPrincipal();
//根据用户名查数据库
if ("zhaoshanhe".equals(principal)){
return new SimpleAuthenticationInfo(principal,
"ecb5c4a53e65c8db187106601468d0ca",
ByteSource.Util.bytes("x0*366go"),
getName());
}
return null;
}
登录成功
MD5加盐+ 散列
改掉自定义realm里数据库假密码cac6f8e93a1d711cb13b6415e5b835f1
public static void main(String[] args) {
//创建安全管理器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
CustomerMd5Realm realm = new CustomerMd5Realm();
//设置realm使用hash凭证匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//使用的算法
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//告诉匹配器散列多少次
hashedCredentialsMatcher.setHashIterations(1024);
realm.setCredentialsMatcher(hashedCredentialsMatcher);
//注入realm
defaultSecurityManager.setRealm(realm);
//将安全管理器注入安全工具
SecurityUtils.setSecurityManager(defaultSecurityManager);
//安全工具获取subject
Subject subject = SecurityUtils.getSubject();
//认证
UsernamePasswordToken token = new UsernamePasswordToken("zhaoshanhe","123456");
try {
subject.login(token);
System.out.println("登录成功");
} catch (UnknownAccountException e){
System.out.println("用户名不存在");
}catch (IncorrectCredentialsException e){
System.out.println("密码错误");
}
}
登录成功
Shiro的授权
授权:就是访问控制,控制谁能访问那些资源,认证通过后需要分配权限才能访问系统资源,某些资源没有权限是不能访问的。就是之前说的登上账号后,1级不能去10级的地图。
关键对象
谁对什么做什么。who,what,how
who:主体(Subject)
what:资源(Resource)
hwo:权限/许可(Premission)
授权流程
通过认证进入系统后检查操作权限
授权方式
授权方式有两种:
基于角色的访问控制和基于资源的访问控制
权限字符串
权限字符串的规则是:资源标识符:操作:资源实例标识符
比如:
用户创建权限:user:create或者user:create:*
用户修改实例shop的权限:user:update:shop
用户实例shop的所有权限:user:*:shop
基于角色的授权
授权是基于认证的,所以使用上边的MD5加随机盐加散列作为认证
//认证用户进行授权
//isAuthenticated是判断有没有认证通过
if (subject.isAuthenticated()){
//基于角色权限控制,hasRole,返回是boolean,是说subject有没有这个角色
System.out.println(subject.hasRole("admin"));
}
//基于多角色权限控制,这个用户必须同时拥有这两个角色才true
System.out.println(subject.hasAllRoles(Arrays.asList("admin", "user")));
//是否有其中一个角色
boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "user", "super"));
for (boolean aBoolean : booleans){
System.out.println(aBoolean);
}
它走了自定义realm的doGetAuthorizationInfo方法,所以要覆盖
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
System.out.println("身份信息:" + primaryPrincipal);
//上边拿到了身份信息,下来就根据身份信息去数据库拿角色信息,以及权限信息
//脑补数据局查到了这个用户有admin和user角色
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//把数据库查到的角色信息赋值刚给权限对象
simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addRole("user");
return simpleAuthorizationInfo;
}
基于权限字符串的授权
if (subject.isAuthenticated()){
//基于权限字符串的访问控制 资源标识符:操作:资源类型,
System.out.println(subject.isPermitted("user:update:shop"));
//同时具有哪些权限
System.out.println(subject.isPermittedAll("user:create:shop", "product:*"));
//分别有哪些权限
boolean[] permitted = subject.isPermitted("user:*:shop", "order:*:*");
for (boolean b : permitted) {
System.out.println(b);
}
}
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
System.out.println("身份信息:" + primaryPrincipal);
//上边拿到了身份信息,下来就根据身份信息去数据库拿角色信息,以及权限信息
//脑补数据局查到了这个用户有admin和user角色
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//把数据库查到的角色信息赋值刚给权限对象
simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addRole("user");
//将数据库中查到的权限信息赋值给权限对象
simpleAuthorizationInfo.addStringPermission("user:*:shop");
return simpleAuthorizationInfo;
}