Apache Shiro Realm实战及认证授权源码解读_field loginservice in com(2)

本文详细介绍了ApacheShiro中的JdbcRealm配置、自定义Realm的实现,包括权限查找、认证流程和自定义realm的doGetAuthorizationInfo和doGetAuthenticationInfo方法。同时强调了知识体系化学习的重要性,推荐了一个综合的学习资源平台。
摘要由CSDN通过智能技术生成

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

//获取当前主体

Subject subject = SecurityUtils.getSubject();

//用户输入的账号密码

UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(“atguigu”, “123456”);

//主体提交登录认证

subject.login(usernamePasswordToken);

System.out.println(“认证结果:”+subject.isAuthenticated());

System.out.println(“是否有对应的user角色:” + subject.hasRole(“user”));

System.out.println(“是否有对应的root角色:” + subject.hasRole(“root”));

System.out.println(“当前用户的用户名为:” + subject.getPrincipal());

System.out.println(“当前用户是否有video:find权限:” + subject.isPermitted(“video:find”));

System.out.println(“当前用户是否有video:delete权限:” + subject.isPermitted(“video:delete”));

}

}

3)运行测试用例,可以看到结果如下:

1.1.2 Shiro内置realm之JdbcRealm实操

1)由于JdbcRealm需要操作数据库,所以在编写测试用例之前需要先导入连接数据库的相关依赖包:

mysql

mysql-connector-java

com.alibaba

druid

1.1.6

2)创建相应的数据库表进行用户权限信息的存储,由于JdbcRealm提供了默认编写的SQL进行数据库查询,所以创建数据库表时表名和字段名都需要对应上,下面提供创建数据库表所需的SQL脚本并插入相应的测试数据:

角色-权限对应关系表

DROP TABLE IF EXISTS roles\_permissions;

CREATE TABLE roles\_permissions (

id bigint(20) NOT NULL AUTO_INCREMENT,

role\_name varchar(100) DEFAULT NULL,

permission varchar(100) DEFAULT NULL,

PRIMARY KEY (id),

UNIQUE KEY idx\_roles\_permissions (role\_name,permission)

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

LOCK TABLES roles\_permissions WRITE;

插入相应的测试数据

INSERT INTO roles\_permissions (id, role\_name, permission)

VALUES

(4,‘admin’,‘video:*’),

(3,‘role1’,‘video:buy’),

(2,‘role1’,‘video:find’),

(5,‘role2’,‘video:list’),

(1,‘root’,‘*’);

UNLOCK TABLES;

用户-角色对应关系表

DROP TABLE IF EXISTS user\_roles;

CREATE TABLE user\_roles (

id bigint(20) NOT NULL AUTO_INCREMENT,

username varchar(100) DEFAULT NULL,

role\_name varchar(100) DEFAULT NULL,

PRIMARY KEY (id),

UNIQUE KEY idx\_user\_roles (username,role\_name)

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

LOCK TABLES user\_roles WRITE;

插入测试数据

INSERT INTO user\_roles (id, username, role\_name)

VALUES

(1,‘jack’,‘role1’),

(2,‘jack’,‘role2’),

(4,‘atguigu’,‘admin’),

(3,‘atguigu’,‘root’);

UNLOCK TABLES;

用户信息表

DROP TABLE IF EXISTS users;

CREATE TABLE users (

id bigint(20) NOT NULL AUTO_INCREMENT,

username varchar(100) DEFAULT NULL,

password varchar(100) DEFAULT NULL,

password\_salt varchar(100) DEFAULT NULL,

PRIMARY KEY (id),

UNIQUE KEY idx\_users\_username (username)

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

LOCK TABLES users WRITE;

插入测试数据

INSERT INTO users (id, username, password, password\_salt)

VALUES

(1,‘jack’,‘123’,NULL),

(2,‘atguigu’,‘123456’,NULL);

UNLOCK TABLES;

3)运行SQL脚本创建完相应的数据表之后,需要进行工程代码的编写,JdbcRealm提供了2种方式进行实现,下面分别对两种方式进行介绍及代码实操:

方式一:使用.ini配置文件进行配置

编写配置文件jdbcRealm.ini

#注意 文件格式必须为ini,编码为ANSI

#声明Realm,指定realm类型

jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm

#配置数据源 由于我们引入了druid数据源依赖,所以这里使用druid数据源

dataSource=com.alibaba.druid.pool.DruidDataSource

mysql-connector-java 5 用的驱动url是com.mysql.jdbc.Driver,mysql-connector-java6以后用的是com.mysql.cj.jdbc.Driver

dataSource.driverClassName=com.mysql.cj.jdbc.Driver

#数据源链接

dataSource.url=jdbc:mysql://192.168.200.128:3306/atguigu_shiro?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false

dataSource.username=root

dataSource.password=123456

#指定数据源

jdbcRealm.dataSource=$dataSource

#开启查找权限, 默认是false,如果不开启不会去查找角色对应的权限,这是一个坑!!!!!

jdbcRealm.permissionsLookupEnabled=true

#指定SecurityManager的Realms实现,设置realms,可以有多个,用逗号隔开

securityManager.realms=$jdbcRealm

新建一个【JdbcRealmTest】测试类:

@Test

public void test() {

//创建SecurityManager工厂,并加载jdbcrealm.ini配置文件

Factory factory = new IniSecurityManagerFactory(“classpath:jdbcrealm.ini”);

SecurityManager securityManager = factory.getInstance();

//将securityManager 设置到当前运行环境中

SecurityUtils.setSecurityManager(securityManager);

Subject subject = SecurityUtils.getSubject();

//用户输入的账号密码

UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(“jack”, “123”);

subject.login(usernamePasswordToken);

System.out.println(" 认证结果:"+subject.isAuthenticated());

System.out.println(" 是否有对应的role1角色:"+subject.hasRole(“role1”));

System.out.println(“是否有video:find权限:”+ subject.isPermitted(“video:find”));

}

运行测试用例,结果如下:

方式二:自行配置数据源及JdbcRealm

依赖引入与方式一一致

在【JdbcRealmTest】测试类中编写一个测试用例:

@Test

public void test2(){

DefaultSecurityManager securityManager = new DefaultSecurityManager();

DruidDataSource ds = new DruidDataSource();

ds.setDriverClassName(“com.mysql.cj.jdbc.Driver”);

ds.setUrl(“jdbc:mysql://192.168.200.128:3306/atguigu_shiro?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false”);

ds.setUsername(“root”);

ds.setPassword(“123456”);

JdbcRealm jdbcRealm = new JdbcRealm();

jdbcRealm.setPermissionsLookupEnabled(true);

jdbcRealm.setDataSource(ds);

securityManager.setRealm(jdbcRealm);

//将securityManager 设置到当前运行环境中

SecurityUtils.setSecurityManager(securityManager);

Subject subject = SecurityUtils.getSubject();

//用户输入的账号密码

UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(“jack”, “123”);

subject.login(usernamePasswordToken);

System.out.println(" 认证结果:"+subject.isAuthenticated());

System.out.println(" 是否有对应的role1角色:"+subject.hasRole(“role1”));

System.out.println(" 是否有video:find权限:"+ subject.isPermitted(“video:find”));

System.out.println(" 是否有任意权限:"+ subject.isPermitted(“aaaa:xxxxxxxxx”));

}

运行测试用例,结果如下:

1.2 Shiro自定义realm实战

1.2.1 自定义realm实战基础

1)步骤:

创建一个类 ,继承AuthorizingRealm,AuthorizingRealm继承关系如下图:

由上图可见继承关系为:AuthorizingRealm->AuthenticatingRealm->CachingRealm->Realm

重写授权方法 doGetAuthorizationInfo

重写认证方法 doGetAuthenticationInfo

方法:

当用户登陆的时候会调用 doGetAuthenticationInfo,获取认证信息进行用户身份认证

进行权限校验的时候会调用: doGetAuthorizationInfo,获取授权信息进行用户授权

对象介绍

UsernamePasswordToken : 对应的是用户输入的账号密码信息组成的,token中有Principal和Credential,UsernamePasswordToken继承关系图如下:

由上图可见继承关系为:UsernamePasswordToken->HostAuthenticationToken->AuthenticationToken

  • SimpleAuthorizationInfo:代表用户角色权限信息
  • SimpleAuthenticationInfo :代表该用户的认证信息

1.2.2 自定义realm代码实操

有了上面的对基础知识以及认证授权的理解,我们先在合适的包下创建一个【CustomRealm】类,继承 Shiro 框架的 AuthorizingRealm 类,并实现默认的两个方法:

package com.atguigu.shiro.demo;

import org.apache.shiro.authc.*;

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 java.util.HashMap;

import java.util.HashSet;

import java.util.Map;

import java.util.Set;

public class CustomRealm extends AuthorizingRealm {

//此处用Map集合模拟用户-角色-权限之间的关联关系,实际开发中会从数据库进行查询

private final Map<String,String> userInfoMap = new HashMap<>();

{

userInfoMap.put(“jack”,“123”);

userInfoMap.put(“atguigu”,“123456”);

}

//role -> permission

private final Map<String, Set> permissionMap = new HashMap<>();

{

Set set1 = new HashSet<>();

Set set2 = new HashSet<>();

set1.add(“video:find”);

set1.add(“video:buy”);

set2.add(“video:add”);

set2.add(“video:delete”);

permissionMap.put(“jack”,set1);

permissionMap.put(“atguigu”,set2);

}

//user -> role

private final Map<String,Set> roleMap = new HashMap<>();

{

Set set1 = new HashSet<>();

Set set2 = new HashSet<>();

set1.add(“role1”);

set1.add(“role2”);

set2.add(“root”);

roleMap.put(“jack”,set1);

roleMap.put(“atguigu”,set2);

}

//进行权限校验的时候会调用

@Override

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

System.out.println(“权限 doGetAuthorizationInfo”);

String name = (String)principals.getPrimaryPrincipal();

Set permissions = getPermissionsByNameFromDB(name);

Set roles = getRolesByNameFromDB(name);

SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

simpleAuthorizationInfo.setRoles(roles);

simpleAuthorizationInfo.setStringPermissions(permissions);

return simpleAuthorizationInfo;

}

//当用户登陆的时候会调用

@Override

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

System.out.println(“认证 doGetAuthenticationInfo”);

//从token获取身份信息,token代表用户输入的信息

String name = (String)token.getPrincipal();

//模拟从数据库中取密码

String pwd = getPwdByUserNameFromDB(name);

if( pwd == null || “”.equals(pwd)){

return null;

}

SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, pwd, this.getName());

return simpleAuthenticationInfo;

}

/**

* 模拟从数据库获取用户角色集合

* @param name

* @return

*/

private Set getRolesByNameFromDB(String name) {

return roleMap.get(name);

}

/**

* 模拟从数据库获取权限集合

* @param name

* @return

*/

private Set getPermissionsByNameFromDB(String name) {

return permissionMap.get(name);

}

private String getPwdByUserNameFromDB(String name) {

return userInfoMap.get(name);

}

}

然后我们编写测试类,来验证是否正确:

package com.atguigu.shiro.demo;

import org.apache.shiro.SecurityUtils;

import org.apache.shiro.authc.UsernamePasswordToken;

import org.apache.shiro.mgt.DefaultSecurityManager;

import org.apache.shiro.subject.Subject;

import org.junit.Test;

public class AuthenticationTest {

private CustomRealm customRealm = new CustomRealm();

private DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();

@Before

public void init(){

//构建环境

defaultSecurityManager.setRealm(customRealm);

SecurityUtils.setSecurityManager(defaultSecurityManager);

}

@Test

public void testAuthentication() {

//获取当前操作的主体

Subject subject = SecurityUtils.getSubject();

//用户输入的账号密码

UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(“jack”, “123”);

subject.login(usernamePasswordToken);

//登录

System.out.println(" 认证结果:"+subject.isAuthenticated());

//拿到主体标示属性

System.out.println(" getPrincipal=" + subject.getPrincipal());

subject.checkRole(“role1”);

System.out.println(“是否有对应的角色:”+subject.hasRole(“role1”));

System.out.println(“是否有对应的权限:”+subject.isPermitted(“video:add”));

}

}

运行测试用例,结果如下:

2. 深入Shiro源码解读认证授权流程

2.1 认证流程源码解读

1)我们以subject.login(token)为起点进行断点调试

2)DelegatingSubject.login(token)会将身份认证请求委托给DefaultSecurityManager的login()方法进行处理,接着继续进入DefaultSecurityManager的login()方法

public class DelegatingSubject implements Subject {

public void login(AuthenticationToken token) throws AuthenticationException {

this.clearRunAsIdentitiesInternal();

//调用DefaultSecurityManager的login方法进行身份信息认证

Subject subject = this.securityManager.login(this, token);

}

}

3)DefaultSecurityManager的login()方法会调用AuthenticatingSecurityManager的authenticate()方法进行验证,接着继续进入AuthenticatingSecurityManager的authenticate()方法

public class DefaultSecurityManager extends SessionsSecurityManager {

public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {

AuthenticationInfo info;

try {

//调用AuthenticatingSecurityManager的authenticate()方法进行验证

info = this.authenticate(token);

} catch (AuthenticationException var7) {

}

Subject loggedIn = this.createSubject(token, info, subject);

this.onSuccessfulLogin(token, info, loggedIn);

return loggedIn;

}

}

4)AuthenticatingSecurityManager的authenticate()方法会调用AbstractAuthenticator的authenticate()方法进行验证,接着继续进入AbstractAuthenticator的authenticate()方法

public abstract class AuthenticatingSecurityManager extends RealmSecurityManager {

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

ubject subject, AuthenticationToken token) throws AuthenticationException {

AuthenticationInfo info;

try {

//调用AuthenticatingSecurityManager的authenticate()方法进行验证

info = this.authenticate(token);

} catch (AuthenticationException var7) {

}

Subject loggedIn = this.createSubject(token, info, subject);

this.onSuccessfulLogin(token, info, loggedIn);

return loggedIn;

}

}

4)AuthenticatingSecurityManager的authenticate()方法会调用AbstractAuthenticator的authenticate()方法进行验证,接着继续进入AbstractAuthenticator的authenticate()方法

public abstract class AuthenticatingSecurityManager extends RealmSecurityManager {

[外链图片转存中…(img-BHUKCqb6-1714959316456)]
[外链图片转存中…(img-347Iz69K-1714959316456)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 27
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值