网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
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 {
…
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
//调用AbstractAuthenticator的authenticate()方法进行验证
return this.authenticator.authenticate(token);
}
…
}
5)AbstractAuthenticator的authenticate()方法会调用ModularRealmAuthenticator的doAuthenticate()方法进行验证,接着继续进入ModularRealmAuthenticator的doAuthenticate()方法
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 {
//调用ModularRealmAuthenticator的doAuthenticate()进行验证
info = this.doAuthenticate(token);
if (info == null) {
String msg = “No account information found for authentication token [” + token + "] by this " + “Authenticator instance. Please check that it is configured correctly.”;
throw new AuthenticationException(msg);
}
} catch (Throwable var8) {
…
}
log.debug(“Authentication successful for token [{}]. Returned account [{}]”, token, info);
this.notifySuccess(token, info);
return info;
}
}
…
}
6)ModularRealmAuthenticator的doAuthenticate()方法会获取相应的realm,由于我们目前只配置了一个realm,所以会执行doSingleRealmAuthentication()方法,接着会AuthenticatingRealm的getAuthenticationInfo()方法,接着继续进入AuthenticatingRealm的getAuthenticationInfo()方法
public class ModularRealmAuthenticator extends AbstractAuthenticator{
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
en + "] by this " + “Authenticator instance. Please check that it is configured correctly.”;
throw new AuthenticationException(msg);
}
} catch (Throwable var8) {
…
}
log.debug(“Authentication successful for token [{}]. Returned account [{}]”, token, info);
this.notifySuccess(token, info);
return info;
}
}
…
}
6)ModularRealmAuthenticator的doAuthenticate()方法会获取相应的realm,由于我们目前只配置了一个realm,所以会执行doSingleRealmAuthentication()方法,接着会AuthenticatingRealm的getAuthenticationInfo()方法,接着继续进入AuthenticatingRealm的getAuthenticationInfo()方法
public class ModularRealmAuthenticator extends AbstractAuthenticator{
[外链图片转存中…(img-obW15QZt-1715318629666)]
[外链图片转存中…(img-46nw2DR1-1715318629666)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!