乐趣区

关于apache:Apache-Shiro-Realm实战及认证授权源码解读

之前文章咱们解说了 Apache Shiro 的一些基础知识,明天咱们会进行 Shiro Realm 实战以及对 Shiro 认证受权源码进行解读。

  1. Shiro 平安数据起源之 Realm 实战

从之前章节的解说咱们理解到理论进行权限信息验证的是咱们的 Realm,Shiro 从 Realm 获取平安数据,Shiro 框架外部默认提供了两种实现,一种是查问.ini 文件的 IniRealm,另一种是查询数据库的 JdbcRealm,除此之外,咱们还能够依据本身的需要进行自定义 Realm,这其中有两个概念须要先理解一下:

principal : 主体的标示,能够有多个,然而须要具备唯一性,常见的有用户名,手机号,邮箱等

credential:凭证, 个别就是明码

接下来咱们别离来看如何应用 Shiro 默认实现的 realm 以及如何自定义 realm:

尚硅谷 Java 开发培训全新体系

尚硅谷 2020Java 全新课程体系,我的项目实战,造就 Java 开发实战人才!

尚硅谷 IT 培训

查看

1.1 Shiro 默认实现的 realm 实操和常见应用办法

1.1.1 Shiro 内置 realm 之 IniRealm 实操

1)首先咱们须要创立一个.ini 配置文件,并依照相应的语法格局进行配置:

格局 username=password,role1,role2,…roleN

[users]

atguigu=123456,user

tom=456789,root,admin

格局 role=permission1,permission2…permissionN 也能够用通配符

上面配置 user 角色的权限为所有 video:find,video:buy,如果须要配置 video 全副操作 crud 则 user=video:*

权限都是自行配置,个别格局是:资源名: 操作,比方对视频的更新操作能够定义为:video:update

[roles]

user = video:find,video:buy

admin = video:update,video:delete,comment:*

root 角色有所有的权限,能够用通配符 * 来示意

root=*

2)新建一个【IniRealmTest】测试类:

package com.atguigu.shiro.demo;

import org.apache.shiro.SecurityUtils;

import org.apache.shiro.authc.UsernamePasswordToken;

import org.apache.shiro.config.IniSecurityManagerFactory;

import org.apache.shiro.mgt.SecurityManager;

import org.apache.shiro.subject.Subject;

import org.apache.shiro.util.Factory;

import org.junit.Test;

public class IniRealmTest {
@Test

public void test() {
// 创立 SecurityManager 工厂,并且读取配置文件 shiro.ini

Factory<SecurityManager> factory = new IniSecurityManagerFactory(“classpath:shiro.ini”);

SecurityManager securityManager = factory.getInstance();

// 将 securityManager 设置到以后运行环境中

SecurityUtils.setSecurityManager(securityManager);

// 获取以后主体

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 驱动包 –>

<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<!– 正文掉 –>

<!–<scope>runtime</scope>–>

</dependency>

<!– 这里应用阿里巴巴 druid 数据源 –>

<dependency>

<groupId>com.alibaba</groupId>

<artifactId>druid</artifactId>

<version>1.1.6</version>

</dependency>

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<SecurityManager> 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<String>> permissionMap = new HashMap<>();

{
Set<String> set1 = new HashSet<>();

Set<String> 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<String>> roleMap = new HashMap<>();

{
Set<String> set1 = new HashSet<>();

Set<String> 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<String> permissions = getPermissionsByNameFromDB(name);

Set<String> 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<String> getRolesByNameFromDB(String name) {
return roleMap.get(name);

}

/**

  • 模仿从数据库获取权限汇合
  • @param name
  • @return

*/

private Set<String> 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”));

}

}

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

  1. 深刻 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{
……

protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
this.assertRealmsConfigured();

// 获取以后配置的 realm

Collection<Realm> realms = this.getRealms();

// 如果以后配置 realm 个数为 1,则执行 doSingleRealmAuthentication() 办法,否则执行 doMultiRealmAuthentication()

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;

}

}

}

……

}

7)在此能够看到,AuthenticatingRealm 的 getAuthenticationInfo() 办法会调用咱们自定义配置 realm 的 doGetAuthenticationInfo(token) 获取认证信息,并且调用 AuthenticatingRealm 的 assertCredentialsMatch 办法进行明码匹配认证

public abstract class AuthenticatingRealm extends CachingRealm implements Initializable{
……

public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = this.getCachedAuthenticationInfo(token);

if (info == null) {
// 调用自定义配置 realm 的 doGetAuthenticationInfo(token) 办法

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;

}

……

}

到此,咱们的认证流程就完结了,通过源码能够看到认证流程到最初就是调用的咱们自定义 realm 并重写的 doGetAuthenticationInfo(token) 办法,从以上的源码剖析,能够失去以下办法调用的时序图:

2.2 受权流程源码解读

1)受权流程咱们以 subject.checkRole() 办法为终点进行断点调试

2)DelegatingSubject 的 checkRole() 办法会将受权申请委托给 AuthorizingSecurityManager 的 checkRole() 办法进行解决,接着持续进入 AuthorizingSecurityManager 的 checkRole() 办法

public class DelegatingSubject implements Subject {
……

public void checkRole(String role) throws AuthorizationException {
this.assertAuthzCheckPossible();

// 将申请委托给 AuthorizingSecurityManager 的 checkRole() 办法

this.securityManager.checkRole(this.getPrincipals(), role);

}

……

}

3)AuthorizingSecurityManager 的 checkRole() 办法会将申请委托给 ModularRealmAuthorizer 的 checkRole() 办法进行解决,接着持续进入 ModularRealmAuthorizer 的 checkRole() 办法

public abstract class AuthorizingSecurityManager extends AuthenticatingSecurityManager {
……

public void checkRole(PrincipalCollection principals, String role) throws AuthorizationException {
// 将申请委托给 ModularRealmAuthorizer 的 checkRole() 办法

this.authorizer.checkRole(principals, role);

}

……

}

4)ModularRealmAuthorizer 的 checkRole() 办法会调用 AuthorizingRealm 的 hasRole() 办法验证以后用户是否有相应的权限,若没有相应的权限,则会抛出异样,接着持续进入 AuthorizingRealm 的 hasRole() 办法

public class ModularRealmAuthorizer implements Authorizer, PermissionResolverAware, RolePermissionResolverAware {
……

public void checkRole(PrincipalCollection principals, String role) throws AuthorizationException {
this.assertRealmsConfigured();

// 验证以后用户是否有相应的权限,如果没有,则会抛出异样

if (!this.hasRole(principals, role)) {
throw new UnauthorizedException(“Subject does not have role [” + role + “]”);

}

}

public boolean hasRole(PrincipalCollection principals, String roleIdentifier) {
this.assertRealmsConfigured();

Iterator var3 = this.getRealms().iterator();

Realm realm;

do {
if (!var3.hasNext()) {
return false;

}

realm = (Realm)var3.next();

// 调用 AuthorizingRealm 的 hasRole() 办法进行验证,如果有相应权限则返回 true,否则返回 false

} while(!(realm instanceof Authorizer) || !((Authorizer)realm).hasRole(principals, roleIdentifier));

return true;

}

……

}

5)AuthorizingRealm 的 hasRole() 办法会通过 getAuthorizationInfo() 办法进行调用自定义 realm 并重写的 doGetAuthorizationInfo() 办法获取以后用户领有的受权信息

public abstract class AuthorizingRealm extends AuthenticatingRealm implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware {
……

public boolean hasRole(PrincipalCollection principal, String roleIdentifier) {
AuthorizationInfo info = this.getAuthorizationInfo(principal);

return this.hasRole(roleIdentifier, info);

}

protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) {
return null;

} else {
AuthorizationInfo info = null;

……

if (info == null) {
// 调用自定义 realm 并重写的 doGetAuthorizationInfo() 办法获取以后用户的受权信息

info = this.doGetAuthorizationInfo(principals);

// 如果受权信息存在并且存在缓存,则将以后受权信息进行缓存

if (info != null && cache != null) {
if (log.isTraceEnabled()) {
log.trace(“Caching authorization info for principals: [” + principals + “].”);

}

key = this.getAuthorizationCacheKey(principals);

cache.put(key, info);

}

}

return info;

}

}

……

}

到此,咱们的受权流程就完结了,通过源码能够看到受权流程到最初就是调用的咱们自定义 realm 并重写的 doGetAuthorizationInfo() 办法,从以上的源码剖析,能够失去以下办法调用的时序图:

至此,咱们就通过源码把 Shiro 的认证以及受权流程解读完了,java 培训由上边剖析咱们能够晓得 Shiro 认证受权的时候,会调用咱们自定义 Realm 时重写的 doGetAuthenticationInfo() 办法以及 doGetAuthorizationInfo() 办法进行获取以后主体的认证以及受权信息,所以咱们须要依据我的项目的具体情况在自定义 Realm 中定义获取认证以及受权信息的逻辑。

  1. Shiro 数据安全之数据加解密

为什么须要加解密?

在之前的学习中,咱们在数据库中保留的明码都是明文的,一旦数据库数据泄露,那就会造成不可估算的损失。

什么是散列算法?

个别叫 hash,简略的说就是一种将任意长度的消息压缩到某一固定长度的音讯摘要的函数,适宜存储明码,比方 MD5

什么是 salt(盐)?

如果间接通过散列函数失去加密数据,容易被对应解密网站暴力破解,个别会在应用程序外面加非凡的字段进行解决,比方用户 id,例子:加密数据 = MD5(明文明码 + 用户 id), 破解难度会更大,也能够应用多重散列,比方屡次 md5

Shiro 如何依据用户传过来的明码进行加密与数据库明码进行匹配?

Shiro 会应用 AuthenticatingRealm 的 assertCredentialsMatch() 办法进行验证明码是否正确

因而,在 Shiro 中咱们个别会自定义明码验证规定,示例如下:

@Bean

public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();

// 散列算法,应用 MD5 算法;

hashedCredentialsMatcher.setHashAlgorithmName(“md5”);

// 散列的次数,比方散列两次,相当于 md5(md5(“xxx”));

hashedCredentialsMatcher.setHashIterations(2);

return hashedCredentialsMatcher;

}

自定义验证规定之后,Shiro 进行明码验证时就会调用相应的逻辑对用户输出的明码进行加密之后与数据库存储的明码进行匹配。

总结一下:明天这篇文章咱们进行 Shiro Realm 实战以及对 Shiro 认证受权源码进行解读,之后咱们将会对 Shiro 的缓存、Session 模块进行解说,以及会整合 SpringBoot2.x 进行综合实战,敬请期待。

关键词:java 培训

退出移动版