Shiro权限验证详细教程

Shiro权限验证详细教程

image.png

目录

正文

一、Shiro介绍

1、可从本教程中明白以下几个知识点:

  • 认识Shiro的整体架构和各组件的概念;
  • Shiro认证、授权过程;
  • Shiro自定义Realm.

2、Shiro简介

Shiro是Apache强大灵活的开源的安全框架,有认证、授权、企业会话管理、安全加密、缓存管理等功能。

Shiro和Spring Security相比较;Shiro更加简单方便、并且可脱离Spring,Spring Security比较笨重复杂,不可脱离Spring。

3、Shiro架构图

image

我主要解释下几个对象:

Subject :主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;
SecurityManager :安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;
Realm: 域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。

也就是说对于我们而言,最简单的一个 Shiro 应用:
1、 应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;
2、 我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法
的用户及其权限进行判断。

建议看不懂的可以先直接看完下面的4个案例,再回头看看就很明白了。_

详细图如上面所示:在这里就不介绍具体每个组件了,我会在以下4个实例代码中详细说明;

4、项目中用到的依赖:

image

添加完依赖,以下所有实例都可以直接运行。

二、第一个实例(初识Shiro认证/授权)

1、Shiro认证/授权过程:代码有详解

认证原理:创建SecurityManager---->主体提交认证请求----->提交到SecurityManager认证---->SecurityManager认证是由Authenticator进行认证---->Authenticator认证需要通过Realm验证用户数据。

授权原理:创建SecurityManager---->主体授权---->提交到SecurityManager授权---->SecurityManager授权是有Authorizer进行授权---->Realm获取角色权限数据

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">public class AuthenticationTest {
SimpleAccountRealm simpleAccountRealm=new SimpleAccountRealm();
@Before public void getAccount()
{ //方便测试 新增用户和角色权限
simpleAccountRealm.addAccount("quentin","123456","admin");
}
@Test public void AuthenticationTest()
{ //1、创建SecurtyManager
DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
defaultSecurityManager.setRealm(simpleAccountRealm); //2、主体认证/授权
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject=SecurityUtils.getSubject();//获得当前正在执行程序的用户
UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken("quentin","123456"); //3、login()主体认证请求,里面封装好的不用管,它是由SecurityManager认证->Authenticate认证->Realm验证组成,这样就可以实现认证。
subject.login(usernamePasswordToken);
System.out.println("isAuthen:"+subject.isAuthenticated()); //3、checkRole()主体授权请求,里面封装好的不用管,它是由SecurityManager授权->Authenticate授权->Realm获取角色权限组成,这样就可以实现授权功能。
subject.checkRole("admin");
}
}</pre>

[
复制代码

](javascript:void(0); "复制代码")

运行结果:

image

当验证不通过会抛出异常,没有权限也会抛出异常!!

** 所以,总结一下整个流程,以认证为例:就是在我们调用Subject的login()方法之后,可以看到我传入的是用户的token(里面的实际原理不用管,其实就是第一第二步它经历过一系列的步骤,它调用了Realm中的doGetAuthenticationInfo(token)方法)。**

三、第二个实例(IniRealm实例讲解)

** 介绍:通过加载.ini文件生成realm对象来验证**

1、在resourses中建立user.ini文件,内容如下:

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">[users]
quentin=123456,admin
[roles]
admin=user:delete,user:update</pre>

设置用户名"quentin",密码是"123456",“admin”角色,"admin"拥有“user:delete、user:update”权限。

2、实例文件代码如下:

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">public class IniRealmTest {

@Test public void IniRealmTest()
{
    IniRealm iniRealm=new IniRealm("classpath:user.ini");  //配置user.ini文件(账号和权限等信息) //1、创建SecurtyManager
    DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
    defaultSecurityManager.setRealm(iniRealm);//设置Realm //2、主体认证/授权请求

SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject=SecurityUtils.getSubject();

    UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken("quentin","123456");
    subject.login(usernamePasswordToken);
    System.out.println("isAuth:"+subject.isAuthenticated());//是否认证 //判断是否授权等
    subject.checkRole("admin");//是否有角色权限
    subject.checkPermission("user:delete");//是否有删除权限

}
}</pre>

[
复制代码

](javascript:void(0); "复制代码")

运行结果:

image

说明认证通过(用户名密码正确),但是跑出没有权限异常(因为admin没有user:deleteall全选)。

四、第三个实例(JdbcRealm实例讲解)

** 介绍:通过获取数据库用户数据来验证**

1、实例代码如下:

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">public class JdbcRealmTest {

DruidDataSource dataSource=new DruidDataSource();
{
    dataSource.setUrl("jdbc:mysql://10.0.92.23:3306/test");
    dataSource.setUsername("root");
    dataSource.setPassword("123");
}

@Test public void JdbcRealmTest()
{
    JdbcRealm jdbcRealm=new JdbcRealm();
    jdbcRealm.setDataSource(dataSource);

    String sql="select password from user where name=?";
    jdbcRealm.setAuthenticationQuery(sql); //查询认证语句
    String sqlrole="select rolename from role where name=?";
    jdbcRealm.setUserRolesQuery(sqlrole); //查询角色权限语句 //1、创建SecurtyManager
    DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
    defaultSecurityManager.setRealm(jdbcRealm); //2、主体认证/授权请求

SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject=SecurityUtils.getSubject();

    UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken("aaa","dfdf");
    subject.login(usernamePasswordToken);

    System.out.println("isAuth:"+subject.isAuthenticated());//判断是有认证

subject.checkRole("admin");//是否有权限
}
}</pre>

[
复制代码

](javascript:void(0); "复制代码")

运行结果也一样就不演示了。其实JdbcRealm里面也提供了默认的sql语句,但是考虑到查询不一样,所以需要单独编写。

五、第四个实例(自定义Realm及Shiro加密)

1、自定义Realm,分为doGetAuthenticationInfo()认证 和 doGetAuthorizationInfo()授权信息两部分,注意代码注释:

代码如下:

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">//自定义Realm,代码如下:
public class CustomerRealm extends AuthorizingRealm { //继承父类AuthorizingRealm将获取Subject相关信息分成两步:获取身份验证信息(doGetAuthenticationInfo)及授权信息(doGetAuthorizationInfo); // 原理!!!!!!!!!
/* doGetAuthenticationInfo获取身份验证相关信息:首先根据传入的用户名获取User信息;
* 然后如果user为空,那么抛出没找到帐号异常UnknownAccountException;如果user找到但锁定了抛出锁定异常LockedAccountException;
* 最后生成AuthenticationInfo信息,交给间接父类AuthenticatingRealm使用CredentialsMatcher进行判断密码是否匹配,如果不匹配将抛出密码错误异常IncorrectCredentialsException;
* 另外如果密码重试此处太多将抛出超出重试次数异常ExcessiveAttemptsException;
* 在组装SimpleAuthenticationInfo信息时,需要传入:身份信息(用户名)、凭据(密文密码)、盐(username+salt),CredentialsMatcher使用盐加密传入的明文密码和此处的密文密码进行匹配。
* */ @Override //用于当前用户验证。认证login()方法执行会自动调用
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //1、从主体传过来的认证信息中,获取用户名
String UserName=(String)token.getPrincipal(); // getPrincipal();身份 | getCredentials();凭据 //2、通过用户名从数据库中获取密码凭证
String Password=getPasswordByUsername(UserName); if(Password==null)
{ return null;
} //组装SimpleAuthenticationInfo信息,(用户名、密文密码、盐)
SimpleAuthenticationInfo simpleAuthenticationInfo=new SimpleAuthenticationInfo("quentin",Password,"customerReal");
simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("salt")); //将盐注册到信息中去

    return simpleAuthenticationInfo;
} //方便测试 设置用户数据
Map<String,String> userMap=new HashMap<String, String>(16);
{ //将带盐"salt"也一起加密
    Md5Hash passMD5=new Md5Hash("123456","salt");
    userMap.put("quentin",passMD5.toString()); super.setName("customerReal");
} private String getPasswordByUsername(String userName) { return userMap.get(userName);
} //----------------------------------------------------------------以上是认证,以下是授权------------------------------------------------------------------------------------------------------ //doGetAuthorizationInfo获取授权信息:PrincipalCollection是一个身份集合,因为我们现在就一个Realm,所以直接调用getPrimaryPrincipal得到之前传入的用户名即可; // 然后根据用户名调用UserService接口获取角色及权限信息。

@Override //用于当前登录用户授权,用户的角色与权限初始化,授权会自动调用
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //获取用户名
String UserName=(String)principals.getPrimaryPrincipal(); //getPrimaryPrincipal()得到主要的身份 Set<String> getRealmNames(); //获取所有身份验证通过的Realm名字 //获取角色和权限信息(一般从数据库或缓存中获取)
Set<String> setRoles=getRolesByUserName();
Set<String> setPression=getPressionByUserName(); //设置SimpleAuthorizationInfo信息,(角色和权限)
SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(setRoles);
simpleAuthorizationInfo.setStringPermissions(setPression); return simpleAuthorizationInfo;
} //方便测试 设置用户权限数据
private Set<String> getPressionByUserName() {
Set<String> sets=new HashSet<String>();
sets.add("user:add");
sets.add("user:delete");
sets.add("user:select");
sets.add("admin:select"); return sets;
} //方便测试 设置用户角色数据
private Set<String> getRolesByUserName() {
Set<String> sets=new HashSet<String>();
sets.add("admin");
sets.add("user"); return sets;
}

}</pre>

[
复制代码

](javascript:void(0); "复制代码")

2、实例运行代码如下:

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">public class CustomerRealmTest {

@Test public void CustomerRealmTest()
{ //引入自定义Realm
    CustomerRealm customerRealm=new CustomerRealm(); //创建DefaultSecurityManager
    DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
    defaultSecurityManager.setRealm(customerRealm); //设置算法名称和加密次数
    HashedCredentialsMatcher matcher=new HashedCredentialsMatcher();
    matcher.setHashAlgorithmName("md5");
    matcher.setHashIterations(1);
    customerRealm.setCredentialsMatcher(matcher); //主体认证,得到SecurityManager实例 并绑定给SecurityUtils

SecurityUtils.setSecurityManager(defaultSecurityManager); //得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)
Subject subject=SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken("quentin","123456"); //登录,即身份验证
subject.login(usernamePasswordToken);
System.out.println("isAuth:"+subject.isAuthenticated());

    subject.checkRole("admin");
    subject.checkPermission("admin:select");

}

}</pre>

[
复制代码

](javascript:void(0); "复制代码")

运行结果:

image

单独解释下Shiro加密:

在自定义Realm文件中

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word;">Md5Hash passMD5=new Md5Hash("123456","salt");
userMap.put("quentin",passMD5.toString()); </pre>

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word;">//由上面代码也能看到解释,其实就是将带盐"salt"也一起加密,作为身份验证的密码信息
+</pre>

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word;">simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("salt")); //将盐注册到信息中去
//将盐"salt"注册到信息中去验证
=
这样就更加安全。

通过上面4个实例,动手敲一敲,相信你们都能快速了解Shiro认证授权原理了。</pre>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,295评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,928评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,682评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,209评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,237评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,965评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,586评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,487评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,016评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,136评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,271评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,948评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,619评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,139评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,252评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,598评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,267评论 2 358