Shiro的使用
1. 什么是权限管理
权限管理属于安全管理的范畴,简单的说就是对用户的访问进行控制,你是什么身份,你就能访问到什么级别的数据
2. 什么是身份认证
判断一个用户,是否是合法用户的处理的这个过程叫做身份认证,最常用的就是通过简单的用户名和密码,进行身份验证,
如果你的用户名和密码在数据库中是存在的,那么说明你的身份就是合法的,否则,不合法
3.关键对象 (名词解释)
//Subject: 主体: 访问该系统的用户,程序,在咱们的上面进行认证的都称为主体,简单的说,谁去认证那么这个主体就是谁:
//Principal:身份信息:就是Subject进行身份认证的标识,标识必须具有唯一性 比如说:手机号,用户名,邮箱,简单的说,就是你带什么信息去进行认证,那么这个Principal就是什么
//credential:凭证信息,密码,安全信息
4. 基本的权限模型
什么是权限模型?
模型简单来说,就相当于一个套路,这个套路简单的说就是我们在做项目的时候,如果遇到一个项目中,有很多种不同身份的用户(角色)使用这个系统,而且不同身份的这些用户还拥有不同的访问权限,我们在设置数据库的时候的这个套路
eg: 商城项目:角色----平台 卖家 买家
不同身份的用户还具有不同的访问权限
角色: 实际上就是权限的一个集合
资源:官方的解释就是 一切能够被计算机识别的图片文字和文件等等都被称为资源
项目开发种的资源: 指的是页面上的所有的按钮,图片 文字,超链接等等
5. 通用的权限模型
六张表
用户表---(用户角色表)---角色表---(角色权限表)---权限表-------资源表
用户角色表是多对对关系 角色权限表 多对多 权限 一对一 资源表
六张表可以简化成五张表
用户表---(用户角色表)---角色表---(角色权限表)---权限资源表
6.表中的字段
//用户表
用户id 用户名 ....
//角色表
角色id 角色名 ....
//用户角色表
用户id 角色id (这里设置复合主键)
// 权限资源表
权限资源id 权限名 权限描述 type(per|res) resName resPath 显示区域的编码
//角色权限表
角色id 权限id
7. 目前市场上通用的权限管理的框架
shiro Spring Security OAuth2
// Spring Security 这个框架是有依赖性的 Spring
// OAuth2:第三方登录,公众平台
//shiro :这个框架有个优点,没有框架的依赖,任何平台都可以用
8.shiro简介
shiro就是一个授权和认证的这样一个框架
简单的说,就是以前咱们认证和授权的所有代码,shiro都给咱们写好了,而且封装好了,我们只需要按照这个框架提供的Api来简单的集成到我们的项目种就可以了
9.shiro的功能
认证 授权 Cache管理 Session管理 rememberMe功能的实现 登录 退出
10.shiro中常见的名词解释
Subject:登录的这个用户,谁认证谁就是这个主体
Principal:用户名
credential:密码
Token:令牌(用户名+密码的封装)---进行认证的封装对象
SecurityManager:安全管理器(只要使用了shiro框架那么这个对象都是必不可少的)
Authenticator:认证器:主要做用户身份认证,就是登录的时候用来做身份校验的
Authrizer:授权器:用来给用户授权
Realm:用户认证和授权的时候,和数据库进行交互的对象(这里面做的事情就是从数据库查询数据 封装成token然后进行认证和授权)
11. shiro的第一个程序
11.1 导包
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.4.0</version>
</dependency>
11.2 编写配置文件,模拟数据库
[users]
chaochao=123
xiaowang=456
11.3 代码
public class ShiroTest {
public static void main ( String[] args ) {
// 获取安全管理的工厂
IniSecurityManagerFactory iniSecurityManagerFactory =
new IniSecurityManagerFactory("classpath:shiro.ini");
// 获取安全管理器
SecurityManager securityManager = iniSecurityManagerFactory.createInstance();
// 将安全管理器设置到运行环境中
SecurityUtils.setSecurityManager(securityManager);
// 获取当前操作的主体
Subject subject = SecurityUtils.getSubject();
// 将用户名和密码丢进去进行测试,用户名和密码都是前端传过来的
// 创建token
UsernamePasswordToken token = new
UsernamePasswordToken("chaochao", "123");
// 传递token进行检验
subject.login(token);
// 查看返回结果
boolean b = subject.isAuthenticated();
System.out.println(b);
}
}
12. shiro源码的解读
public class DelegatingSubject implements Subject {
public void login(AuthenticationToken token) throws AuthenticationException {
this.clearRunAsIdentitiesInternal();
//完成认证
Subject subject = this.securityManager.login(this, token);
public class DefaultSecurityManager extends SessionsSecurityManager {
// token 是前端传过来的值(usernameAndpasswordtoken)
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
// 进行认证的地方
info = this.authenticate(token);
} catch (AuthenticationException var7) {
// 如果有异常意味着认证失败
AuthenticationException ae = var7;
try {
// 调用认证失败的方法
this.onFailedLogin(token, ae, subject);
} catch (Exception var6) {
if(log.isInfoEnabled()) {
log.info("onFailedLogin method threw an exception. Logging and propagating original AuthenticationException.", var6);
}
}
throw var7;
}
//用户认证成功,就封装成subject
Subject loggedIn = this.createSubject(token, info, subject);
// 调用认证成功的方法
this.onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}
public abstract class AuthenticatingSecurityManager extends RealmSecurityManager
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
// 调用了校验器中的authenticate方法
return this.authenticator.authenticate(token);
}
public abstract class AbstractAuthenticator implements Authenticator, LogoutAware {
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
if(token == null) {
throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");
} else {
log.trace("Authentication attempt received for token [{}]", token);
AuthenticationInfo info;
try {
// 实现认证的方法
info = this.doAuthenticate(token);
public class ModularRealmAuthenticator extends AbstractAuthenticator {
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
this.assertRealmsConfigured();
Collection<Realm> realms = this.getRealms();
return realms.size() == 1?
// 进行认证
this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken):this.doMultiRealmAuthentication(realms, authenticationToken);
}
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
if(!realm.supports(token)) {
String msg = "Realm [" + realm + "] does not support authentication token [" + token + "]. Please ensure that the appropriate Realm implementation is " + "configured correctly or that the realm accepts AuthenticationTokens of this type.";
throw new UnsupportedTokenException(msg);
} else {
// 认证
AuthenticationInfo info = realm.getAuthenticationInfo(token);
if(info == null) {
String msg = "Realm [" + realm + "] was unable to find account data for the " + "submitted AuthenticationToken [" + token + "].";
throw new UnknownAccountException(msg);
} else {
return info;
}
}
}
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = this.getCachedAuthenticationInfo(token);
if(info == null) {
// 认证的地方
info = this.doGetAuthenticationInfo(token);
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if(token != null && info != null) {
this.cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
}
if(info != null) {
// 校验密码
this.assertCredentialsMatch(token, info);
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
}
return info;
}
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken)token;
// 通过用户名获取用户
SimpleAccount account = this.getUser(upToken.getUsername());
if(account != null) {
// 如果用户对象获取到了数据,判断数据是否被锁定
if(account.isLocked()) {
throw new LockedAccountException("Account [" + account + "] is locked.");
}
if(account.isCredentialsExpired()) {
String msg = "The credentials for account [" + account + "] are expired";
throw new ExpiredCredentialsException(msg);
}
}
return account;
}
protected boolean equals(Object tokenCredentials, Object accountCredentials) {
if(log.isDebugEnabled()) {
log.debug("Performing credentials equality check for tokenCredentials of type [" + tokenCredentials.getClass().getName() + " and accountCredentials of type [" + accountCredentials.getClass().getName() + "]");
}
if(this.isByteSource(tokenCredentials) && this.isByteSource(accountCredentials)) {
if(log.isDebugEnabled()) {
log.debug("Both credentials arguments can be easily converted to byte arrays. Performing array equals comparison");
}
//比较密码的byte类型是否一致,如果一致,就返回true
byte[] tokenBytes = this.toBytes(tokenCredentials);
byte[] accountBytes = this.toBytes(accountCredentials);
return MessageDigest.isEqual(tokenBytes, accountBytes);
} else {
return accountCredentials.equals(tokenCredentials);
}
}
13. shiro与realm
13.1 自定义一个realm
public class MyShiro extends AuthorizingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo ( AuthenticationToken authenticationToken ) throws AuthenticationException {
// 通过token获取到用户数据
Object userName = authenticationToken.getPrincipal();
// 在数据库中通过用户名进行查询,是否存在这个用户
// ....
// 模拟查出的用户数据
User user = new User(1, "chaochao", "1234");
AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getName(), user.getPassword(), getName());
return authenticationInfo;
}
13.2 编写配置文件
[main]
customRealm=com.wcc.springboot.shiro.MyShiro
securityManager.realms=$customRealm
13.3 编写测试类
public class ShiroTest {
public static void main ( String[] args ) {
// 获取安全管理的工厂
IniSecurityManagerFactory iniSecurityManagerFactory =
new IniSecurityManagerFactory("classpath:shiro-realm.ini");
// 获取安全管理器
SecurityManager securityManager = iniSecurityManagerFactory.createInstance();
// 将安全管理器设置到运行环境中
SecurityUtils.setSecurityManager(securityManager);
// 获取当前操作的主体
Subject subject = SecurityUtils.getSubject();
// 将用户名和密码丢进去进行测试,用户名和密码都是前端传过来的
// 创建token
UsernamePasswordToken token = new
UsernamePasswordToken("chaochao", "1234");
// 传递token进行检验
subject.login(token);
// 查看返回结果
boolean b = subject.isAuthenticated();
System.out.println(b);
}
}
14. shiro认证中常见的两个异常
14.1 用户名不存在异常
Exception in thread "main" org.apache.shiro.authc.UnknownAccountException:
14.2 密码错误异常
Exception in thread "main" org.apache.shiro.authc.IncorrectCredentialsException
15. MD5Hash的使用
在数据库存储的时候,一些敏感信息不能以明文的方式存储,shiro中也提供了密码散列
15.1 Md5简单使用
public class Md5Test {
public static void main ( String[] args ) {
Md5Hash md5Hash = new Md5Hash("ABC");
System.out.println("散列后的值是"+md5Hash);
System.out.println("----------------------------------");
// 加盐,为什么加盐,给这个密码再添加一层保障
// 为了让密码更加安全,更加不容易被破解
Md5Hash md5Hash1 = new Md5Hash("abc", "123");
System.out.println("加盐散列后的值"+md5Hash1);
Md5Hash md5Hash2 = new Md5Hash("123abc");
System.out.println("加盐散列后的值"+md5Hash2);
// md5Hash1=md5Hash2 两个值是相同的,相当于将盐放在密码前进行散列
System.out.println(md5Hash1.equals(md5Hash2));
System.out.println("-----------------------------------");
Md5Hash md5Hash3 = new Md5Hash("abc", "123", 2);
System.out.println("加盐+两次散列后的值"+md5Hash3);
Md5Hash md5Hash4 = new Md5Hash(md5Hash1);
System.out.println("加盐+散列后的值再次散列"+md5Hash4);
// md5Hash3=md5Hash4
System.out.println(md5Hash3.equals(md5Hash4));
System.out.println("-----------------------------------");
/*
上面代码的运行结果
散列后的值是902fbdd2b1df0c4f70b4a5d23525e932
----------------------------------
加盐散列后的值a906449d5769fa7361d7ecc6aa3f6d28
加盐散列后的值a906449d5769fa7361d7ecc6aa3f6d28
true
-----------------------------------
加盐+两次散列后的值4c25782028c1edf8f4e122fd71d7c37c
加盐+散列后的值再次散列4c25782028c1edf8f4e122fd71d7c37c
true
-----------------------------------
* */
15.2 realm和Md5Hash的使用
15.2.1 自定义一个realm
public class Md5Realm extends AuthorizingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo
( AuthenticationToken authenticationToken )
throws AuthenticationException {
Object userName = authenticationToken.getPrincipal();
if (!userName.equals("chaochao")){
return null;
}
SimpleAuthenticationInfo simpleAuthenticationInfo =
new SimpleAuthenticationInfo("chaochao",
// 存储在数据库中的密码
"a906449d5769fa7361d7ecc6aa3f6d28",
// 从数据库取的盐值
ByteSource.Util.bytes("123"),
"Md5Realm");
return simpleAuthenticationInfo;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo ( PrincipalCollection principalCollection ) {
return null;
}
15.2.2 编写配置文件
[main]
#定义凭证匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#散列算法
credentialsMatcher.hashAlgorithmName=md5
#散列次数
credentialsMatcher.hashIterations=1
#将凭证匹配器设置到realm
customRealm=com.wcc.springboot.shiro.Md5Realm
customRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$customRealm
15.2.3 测试
public class Md5RealmTest {
public static void main ( String[] args ) {
// 获取安全管理器的工厂
IniSecurityManagerFactory factory =
new IniSecurityManagerFactory(
"classpath:shiro-hash.ini");
// 获取安全管理器
SecurityManager securityManager = factory.getInstance();
// 将安全管理器设置进运行环境
SecurityUtils.setSecurityManager(securityManager);
// 获取主体
Subject subject = SecurityUtils.getSubject();
// 获取token
UsernamePasswordToken token = new
UsernamePasswordToken("chaochao", "abc");
subject.login(token);
// 判断是否认证成功
boolean b = subject.isAuthenticated();
System.out.println(b);
}
}
16. 代码授权
用户要授权,必须是在认证的基础上进行授权
16.1 自定义realm
public class Md5Realm extends AuthorizingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo
( AuthenticationToken authenticationToken )
throws AuthenticationException {
Object userName = authenticationToken.getPrincipal();
if (!userName.equals("chaochao")){
return null;
}
SimpleAuthenticationInfo simpleAuthenticationInfo =
new SimpleAuthenticationInfo("chaochao",
// 存储在数据库中的密码
"a906449d5769fa7361d7ecc6aa3f6d28",
// 从数据库取的盐值
ByteSource.Util.bytes("123"),
"Md5Realm");
return simpleAuthenticationInfo;
}
// 授权的代码
@Override
protected AuthorizationInfo doGetAuthorizationInfo (
PrincipalCollection principalCollection ) {
// 1. 通过前端传过来的数据获取用户名
Object userName = principalCollection.getPrimaryPrincipal();
// 2. 通过用户名在数据库中查询出角色,和相应的权限
// 。。。。。
// 假设这是从数据库查出来的权限
Set<String> perms = new HashSet<>();
perms.add("user:add");
perms.add("user:delete");
// 假设这是从数据库中查出来的角色
Set<String> roles = new HashSet<>();
roles.add("buyer");
roles.add("soler");
SimpleAuthorizationInfo authorizationInfo =
new SimpleAuthorizationInfo(roles);
authorizationInfo.setStringPermissions(perms);
return authorizationInfo;
}
}
16.2测试
public class Md5RealmTest {
public static void main ( String[] args ) {
// 获取安全管理器的工厂
IniSecurityManagerFactory factory =
new IniSecurityManagerFactory(
"classpath:shiro-hash.ini");
// 获取安全管理器
SecurityManager securityManager = factory.getInstance();
// 将安全管理器设置进运行环境
SecurityUtils.setSecurityManager(securityManager);
// 获取主体
Subject subject = SecurityUtils.getSubject();
// 获取token
UsernamePasswordToken token = new
UsernamePasswordToken("chaochao", "abc");
subject.login(token);
// 判断是否认证成功
boolean b = subject.isAuthenticated();
System.out.println(b);
// 测试 ,当用户具有这个权限时打印。。。
if (subject.isPermitted("user:add")){
System.out.println("yes ,you are clear");
}
//表示的是一个一个判断 用户是否具有 某一个权限 (返回的是数组)
boolean[] permitted = subject.isPermitted("", "", "");
//表示的是一个一个判断 用户是否具有 某一个权限 (返回的是数组)
ArrayList<Permission> list = new ArrayList<>();
boolean[] booleans = subject.isPermitted(list);
//玩下角色 (一个一个判断是否具有某一个角色)
boolean[] booleans1 = subject.hasRoles(new ArrayList<String>());
boolean b1 = subject.hasRole("");//判断是否具有 某一个角色
boolean b2 = subject.hasAllRoles(new ArrayList<String>());//判断是否具有所有权限
}
}