Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

Apache Shiro学习笔记

11
Jan
2018

Apache Shiro学习笔记

By Alex
/ in Java
0 Comments
简介

Shiro是一个支持身份验证、授权、加密、会话管理的Java安全框架。Shiro的API很简单,比Sprng Security简单的多。它的特性如下:

  1. 在身份验证方面,Shiro支持LDAP、JDBC、AD等数据源
  2. 在授权(访问控制)方面,Shiro支持基于角色的权限控制,或者细粒度的Permissions控制。授权信息也存放在可拔插的数据源中
  3. 内置的企业级会话管理功能,支持Web/非Web应用,支持需要SSO或者分布式会话的应用场景
  4. 支持SSO,登陆到共享会话的多个应用中的任意一个,其它应用自动获得会话信息
  5. 内置简单好用的密码算法,包括哈希
  6. 支持跨越线程传递Subject
  7. 支持RunAs,也就是以另外一个身份进行访问
核心概念
概念 说明
Subject 主体,和应用程序交互的任何实体,可以是人、网络爬虫,等等
Principal 主体的一个属性,具有识别主体的能力,可能是用户账号、身份证号等
SecurityManager 安全管理器,所有和安全相关的操作都要通过该对象,它管理所有Subject
Realm

领域,Shiro从Realm获取用户、角色、权限等数据,它是LDAP、RDBMS、AD等任意形式的数据源

可以配置多个Realm,Shiro的默认行为是依次调用Realm进行身份验证

Authenticator

认证器,一个扩展点,可以自定义身份验证逻辑

Authrizer 授权器,一个扩展点,可以自定义访问控制逻辑
Session 和Subject关联的有状态的数据上下文,你可以对其进行CRUD操作。当Subject登出,或者因为长期不活动而超时,会话终结
SessionManager 会话管理器,可以用它实现分布式会话支持
Resource 应用程序中任何可以供用户访问的东西,例如URL、页面、数据、方法、摄像头,等等
Permission 权限,原子的访问控制单元,代表对某种资源的某种操作许可
Role 权限的集合,通常为用户赋予角色而非权限
 起步
Web环境 

本节贴出一个简单的、基于Shiro的Web应用。

Maven配置
XML
1
2
3
4
5
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
    <version>1.2.2</version>
</dependency>
web.xml
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
    <!-- 在Web容器启动时初始化Shiro需要的WebEnvironment对象 -->
    <context-param>
        <param-name>shiroEnvironmentClass</param-name>
        <param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value>
    </context-param>
    <context-param>
        <param-name>shiroConfigLocations</param-name>
        <!-- 默认从/WEB-INF/shiro.ini加载配置文件 -->
        <param-value>classpath:shiro.ini</param-value>
    </context-param>
 
    <listener>
        <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
    </listener>
    <filter>
        <filter-name>ShiroFilter</filter-name>
        <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>ShiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>
shiro.ini

位于Classpath下的配置文件,可以包含硬编码的用户信息,配合隐含声明的Realm使用:

shiro.ini
INI
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[main]
; 登陆URL
authc.loginUrl = /login
; 基于角色授权失败时重定向到的URL
roles.unauthorizedUrl = /unauthorized
; 基于权限授权失败时重定向到的URL
perms.unauthorizedUrl = /unauthorized
[users]
; 硬编码的用户信息,格式:username = password, roleName1, roleName2, …, roleNameN
alex = passw0rd,admin
meng = passw0rd
[roles]
; 硬编码的角色信息,格式:rolename = permissionDefinition1, permissionDefinition2, …, permissionDefinitionN
admin = user:*,menu:*
[urls]
; 硬编码的,基于URL的ACL
; 允许匿名访问
/login = anon
/unauthorized = anon
; 支持多种通配符:
; ? 匹配一个字符
; * 匹配0-N个字符,但是不能跨越 /
; ** 匹配0-N个路径(路径即/分隔的片断)
/static/** = anon
; 需要身份验证
/authenticated = authc
; 需要获得授权:要求admin角色
/role = authc,roles[admin]
; 需要获得授权:要求user:create权限
/permission = authc,perms["user:create"]
身份验证代码
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package cc.gmem.study.shiro.servlet;
 
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
 
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
 
@WebServlet( "/login" )
public class LoginServlet extends HttpServlet {
 
    private static final Logger LOGGER = LogManager.getLogger( LoginServlet.class );
 
    @Override
    protected void doGet( HttpServletRequest req, HttpServletResponse resp ) throws ServletException, IOException {
        /**
         * 获得当前主体,通常就是类似于用户的概念:
         * 1、对于Web应用,从当前线程或者请求中获取用户信息
         * 2、对于独立运行的应用,从自定义位置获取用户信息
         */
        Subject user = SecurityUtils.getSubject();
 
        if ( !user.isAuthenticated() ) {
            // 从外部获取用户凭证,例如密码、X509证书、OpenID,产生一个令牌对象
            UsernamePasswordToken token = new UsernamePasswordToken( req.getParameter( "name" ), req.getParameter( "pswd" ) );
            // 是否跨越会话记住用户
            token.setRememberMe( true );
            try {
                // 登陆
                user.login( token );
 
                // 可以获得一个会话对象,类似HttpSession,在Web应用中,该对象就是基于HttpSession的
                Session session = user.getSession();
                session.setAttribute( "loginTime", System.currentTimeMillis() );
                LOGGER.debug( "Logged in as {}", user.getPrincipal().toString() );
                // 判断是否具有特定的角色、权限
                assert user.hasRole( "admin" );
                assert user.isPermitted( "user:create" );
                resp.sendRedirect( "/authenticated" );
 
            } catch ( UnknownAccountException uae ) {
                LOGGER.error( "账号未知" );
            } catch ( IncorrectCredentialsException ice ) {
                LOGGER.error( "密码错误" );
            } catch ( LockedAccountException lae ) {
                LOGGER.error( "账户锁定" );
            }
        }
    }
}

启动Web容器后,访问 http://host:port/login?name=alex&pswd=passw0rd即可登陆并通过身份验证。

独立应用

在非Web环境下使用Shiro类似,但是需要显式初始化安全管理器:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
// 示例一
Realm realm = new DatabaseRealm();
SecurityManager securityManager = new DefaultSecurityManager(realm);
SecurityUtils.setSecurityManager(securityManager);
 
// 示例二
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
 
 
Subject user = SecurityUtils.getSubject();
// ...
配置文件

你可以使用INI格式为Shiro提供必要的配置信息,INI格式类似于属性文件,使用 #或者 ;作为注释前导符号。INI配置文件包含四个段:

段 说明
[main]

在此声明安全管理器的各属性及其依赖的对象,支持简单的DI

示例配置:

INI
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
; 进行密码匹配时使用的散列算法
sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
 
; 配置一个Realm
dbRealm = cc.gmem.security.shiro.DatabaseRealm
; 注入Realm的属性,字面值
dbRealm.connectionTimeout = 30000
dbRealm.username = gmem
dbRealm.password = gmem
; 注入Realm的属性,引用(前缀$)
dbRealm.credentialsMatcher = $sha256Matcher
 
; 配置嵌套属性,等价于:securityManager.getSessionManager().setGlobalSessionTimeout(1800000);
; 全局的会话超时设置
securityManager.sessionManager.globalSessionTimeout = 1800000
 
; 字节数组,可以Base64或者HEX方式配置
securityManager.rememberMeManager.cipherKey = kPH+bIxk5D2deZiIxcaaaA==
securityManager.rememberMeManager.cipherKey = 0x3707344A4093822299F31D008
 
; 集合属性,用逗号(,)分隔元素
securityManager.sessionManager.sessionListeners = $sessionListener1, $sessionListener2
 
; 映射属性,用冒号(:)分隔键值
anObject.mapProperty = key1:$object1, key2:$object2
[users] 在简单的应用中,静态化的配置用户、角色、权限信息
[roles]
[urls] 在Web应用中,实现基于URL的访问控制
安全域

Realm表示一个安全域,它持有用户、角色、权限等安全数据,有能力对Subject进行身份验证。如果不进行配置,Shiro默认使用org.apache.shiro.realm.text.IniRealm这个Realm,安全数据静态的配置在ini文件中。

Realm接口包括以下方法:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 大部分情况下不需要直接实现此接口:
* 1、如果仅需要身份验证功能,可以继承AuthenticatingRealm
* 2、如果还需要访问控制功能,可以继承AuthorizingRealm(AuthenticatingRealm的子类)
*/
public interface Realm {
 
    /**
     * 返回Realm的名称,此名称必须唯一
     */
    String getName();
 
    /**
     * 判断此Realm是否支持对token所代表的Subject进行身份验证
     */
    boolean supports( AuthenticationToken token );
 
    /**
     * 执行身份验证,或者说基于token进行登陆
     *
     * @param token 包含了Subject的标识(Principal)和凭证(Credential)信息的令牌
     * @return 和令牌相关的账户的身份验证信息,如果相关账户不存在则返回null
     * @throws org.apache.shiro.authc.AuthenticationException 如果身份验证失败
     */
    AuthenticationInfo getAuthenticationInfo( AuthenticationToken token ) throws AuthenticationException;
 
}
内置实现

Shiro内置的Realm实现如下图所示:

shiro-class-diagram-realm

其中常用实现说明如下:

实现 说明
IniRealm 使用INI文件中的配置信息,Web环境下默认使用shiro.ini,格式在上文已有说明
PropertiesRealm 默认使用类路径下的属性文件shiro-users.properties中的配置信息,格式如下:
Shell
1
2
3
4
# user.用户名=密码,角色列表
user.username=password,role1,role2
# role.角色名=权限列表
role.role1=permission1,permission2
JdbcRealm

从JDBC数据源中查找配置信息,默认使用的SQL如下:

SQL
1
2
3
4
5
6
7
8
-- 查询身份认证信息
select password from users where username = ?
-- 如果密码使用加盐哈希值存储,且使用SaltStyle.COLUMN,则查询身份信息的SQL为:
select password, password_salt from users where username = ?
-- 查询用户的角色信息
select role_name from user_roles where username = ?
-- 查询角色的权限信息
select permission from roles_permissions where role_name = ?

 

配置Realm

Realm的配置信息放在INI的main段,你可以配置多个Realm:

shiro.ini
INI
1
2
3
4
[main]
dbRealm = cc.gmem.security.shiro.DatabaseRealm
ldapRealm = cc.gmem.security.shiro.LDAPRealm
securityManager.realms=$dbRealm, $ldapRealm

如果不配置securityManager.realms属性,则Shiro根据声明顺序,依次调用各Realm进行身份验证。

身份验证
基本流程
  1. 调用Subject.login(token)进行身份验证
  2. Subject委托给SecurityManager进行处理,SecurityManager需要事先通过SecurityUtils. setSecurityManager()设置
  3. SecurityManager委托Authenticator进行实际的身份验证。Shiro缺省的安全管理器本身实现了Authenticator接口,但是它自己不负责身份验证,而是委托ModularRealmAuthenticator。后者也是Authenticator的实现
  4. ModularRealmAuthenticator持有一个Realm的集合,也就是说它可以从多个安全数据源获取信息,对Subject进行身份验证
  5. ModularRealmAuthenticator持有AuthenticationStrategy即身份验证策略,后者能够决定是每个Realm都需要对Subject进行成功的身份验证,还是仅仅需要一个就可以了

牵涉到的类如下:

shiro-class-diagram-secmgr

AuthenticationStrategy

此接口和ModularRealmAuthenticator配合,指定存在多个Realm时的身份验证策略:

Java
1
2
3
4
5
6
7
8
9
10
11
12
public interface AuthenticationStrategy {
 
    // 尝试基于任何Realm进行身份验证之前调用
    AuthenticationInfo beforeAllAttempts(Collection<? extends Realm> realms, AuthenticationToken token) throws AuthenticationException;
    // 尝试基于某个Realm进行身份验证之前调用
    AuthenticationInfo beforeAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException;
    // 基于某个Realm进行身份验证之后调用
    AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t)
        throws AuthenticationException;
    // 基于全部Realm进行身份验证之后调用
    AuthenticationInfo afterAllAttempts(AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException;
}
内置实现
实现 说明
FirstSuccessfulStrategy 只需要有一个Realm验证成功即可,只返回第一个Realm身份验证成功的AuthenticationInfo
AtLeastOneSuccessfulStrategy 只需要有一个Realm验证成功即可,返回所有Realm的AuthenticationInfo
AllSuccessfulStrategy 必须所有Realm验证成功,返回所有Realm的AuthenticationInfo
AuthenticationToken

此接口用于收集主体的身份验证信息,例如主体标识、密码:

Java
1
2
3
4
5
6
public interface AuthenticationToken extends Serializable {
    // 返回主体唯一标识,可能是用户名、X.509证书,等等
    Object getPrincipal();
    // 返回主体的身份凭证,可能是密码、X.509证书,等等
    Object getCredentials();
}
AuthenticationInfo

此接口表示主体已经存储到安全域中的、和身份验证(登陆)相关的账户信息。

需要注意AuthenticationInfo和AuthenticationToken的不同,前者是经过验证的、存储在安全域(Realm)中的账户信息。后者则是登陆请求所提交的数据,不一定能和AuthenticationInfo匹配。

PrincipalCollection

每个应用中可以有多个Realm,因此同一Subject就可以对应多个Principal,此接口用于聚合多个Realm提供的、关联目标Subject的Principal: 

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public interface PrincipalCollection extends Iterable, Serializable {
 
    /**
     * 返回主要的Principal,如果只有一个Realm则只能有一个Principal
     * 如果有多个Realm,Shiro默认返回迭代得到的第一个Principal
     */
    Object getPrimaryPrincipal();
 
    /**
     * 得到第一个指定类型的Principal,如果找不到返回null
     */
    <T> T oneByType(Class<T> type);
 
    /**
     * 得到所有指定类型的Principal,如果找不到返回空集合
     */
    <T> Collection<T> byType(Class<T> type);
 
    /**
     * 返回所有Principal构成的列表
     */
    List asList();
 
    /**
     * 返回所有Principal构成的集合
     */
    Set asSet();
 
    /**
     * 返回从指定Realm取得的Subject的Principal的集合
     */
    Collection fromRealm(String realmName);
 
    /**
     * 返回此集合中Principal的来源Realm
     */
    Set<String> getRealmNames();
 
    /**
     * 判断集合中是否存在至少一个Principal
     */
    boolean isEmpty();
}
访问控制
控制方式

Shiro原生支持三种方式,来指定访问某种资源所需的角色或权限。

编程方式
Java
1
2
3
4
Subject user = SecurityUtils.getSubject();  
if( user.hasRole( "admin" ) ) {  
    //有权限  
}

相关方法都定义在Subject上,例如:

方法 说明
isPermitted 判断Subject是否有权访问参数所指定的资源
isPermittedAll 判断Subject是否有权访问参数所指定的所有资源
checkPermission 检查Subject是否有权访问资源,如果无权则抛出异常
checkPermissions 检查Subject是否有权访问参数指定的所有资源,如果无权则抛出异常
hasRole 判断Subject是否具有指定的角色
hasAllRoles 判断Subject是否具有所有指定的角色
checkRole 检查Subject是否具有指定的角色,如果没有则抛出异常
checkRoles 检查Subject是否具有所有指定的角色,如果没有则抛出异常

这些方法会委托安全管理器来检查Subject(准确的说是其Principal)具有资源的访问资格。

注解方式
Java
1
2
3
@RequiresRoles("admin")  
public void saveUser( User u ) {
}  

相关注解如下表:

注解 说明
RequiresAuthentication Subject的当前会话,必须已经通过身份验证,才能访问注解了此类型的类、实例、方法
RequiresGuest Subject必须没有经过身份验证,才能访问
RequiresUser

Subject必须是应用程序的一个用户,才能访问。所谓用户,可以是:

  1. 已验证用户,当前会话已经经过身份验证的Subject
  2. 已记住用户,被记住的Subject,当前会话可以没有经过身份验证
RequiresPermissions Subject必须(隐含的)具有指定的权限,才能访问
RequiresRoles Subject必须具有指定的角色,才能访问
标签库 

使用JSP时,可以通过特殊标签进行访问控制:

JSP
1
2
3
<shiro:hasRole name="admin">  
<button>Create User</button>
</shiro:hasRole>
基本流程
  1. 当Subject.isPermitted/hasRole等方法被调用后,Subject委托SecurityManager进行访问控制
  2. SecurityManager委托Authorizer进行处理,尽管默认的安全管理全就是Authorizer的实现,它还是创建了一个ModularRealmAuthorizer,委托后者进行处理
  3. ModularRealmAuthorizer持有所有实现了Authorizer的Realm。它会循环调用Realm的isPermitted/hasRole方法,如果返回true则表示授权成功
  4. 只要有一个Realm授权成功即可

如果你的Realm支持授权功能,请让它继承AuthorizingRealm,内置的Realm都继承自该类。

牵涉到的类如下:

shiro-class-diagram-autho

权限语法

权限使用字符串来表示,格式为 资源类型:操作类型:资源实例标识符。 多个资源类型、操作类型使用逗号分隔,字符*可以用于通配。示例:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 对资源system:user的update操作权
system:user:update
 
# 对system:user的update权,以及对system:user的delete权
system:user:update,system:user:delete
# 等价写法
system:user:update,delete
 
# 对system:user的任何操作权限
system:user:*
# 等价写法
system:user
 
# 针对任何资源的view权限
*:*:view
 
 
# 对用户1的update和delete权
user:update,delete:1
# 对用户1的任何权限
user:*:1
# 对任何用户的任何权限
user:*:*
 
 
# 前缀匹配规则
user  # 隐含了user:*或者user:*:*
权限解析器

如何从上面的字符串形式的权限语法中解析出Permission对象,依赖于以下接口:

接口 说明
PermissionResolver 从权限字符串中解析出Permission对象
RolePermissionResolver 从角色解析出其对应的一系列Permission对象

你可以自定义权限解析器,并按照如下方式配置、启用:

INI
1
2
3
4
5
6
7
8
9
authorizer=org.apache.shiro.authz.ModularRealmAuthorizer  
 
permissionResolver=cc.gmem.study.shiro.CustomPermissionResolver  
authorizer.permissionResolver=$permissionResolver  
rolePermissionResolver=cc.gmem.study.shiro.CustomRolePermissionResolver
authorizer.rolePermissionResolver=$rolePermissionResolver
 
securityManager.authorizer=$authorizer
AuthorizationInfo

此接口代表一个Subject已经存储到安全域中的授权数据(角色、权限),仅仅在进行授权(访问控制)时使用:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface AuthorizationInfo extends Serializable {
 
    /**
     * 此授权信息关联的主体所拥有的角色
     */
    Collection<String> getRoles();
 
    /**
     * 此授权信关联的主体所拥有的权限,以字符串形式表示
     */
    Collection<String> getStringPermissions();
 
    /**
     * 此授权信关联的主体所拥有的权限,以类型安全方式表示
     */
    Collection<Permission> getObjectPermissions();
}

AuthorizingRealm会持有Principal到AuthorizationInfo的映射。在进行访问控制时,先得到Subject的PrincipalCollection,然后获取和这些Principal对应的AuthorizationInfo,与入参提供的角色、权限信息进行比对。

编解码 
Base64
Java
1
2
3
4
// 编码
Base64.encodeToString(str.getBytes());
// 解码
Base64.decodeToString(base64Encoded);  
Hex 
Java
1
2
3
4
// 编码
Hex.encodeToString(str.getBytes());
// 解码
new String(Hex.decode(base64Encoded.getBytes()));
散列
Java
1
2
3
4
5
6
7
8
9
10
String pswd = "passw0rd";  
String salt = "userid";
// 加盐散列,增强安全性
// MD5算法
String md5 = new Md5Hash(pswd, salt).toString();
// SHA256算法
String sha1 = new Sha256Hash(pswd, salt).toString();
 
// 其它散列算法
new SimpleHash("SHA-1", str, salt).toString();
HashService

此接口提供更加复杂的哈希计算逻辑:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
DefaultHashService hashService = new DefaultHashService();
// 默认哈希算法
hashService.setHashAlgorithmName( "SHA-512" );
// 私盐,服务内部使用,和公盐配对生成加盐哈希
hashService.setPrivateSalt( new SimpleByteSource( "privatesalt" ) );
// 是否生成公盐,如果HashRequest没有提供盐,则可以使用随机生成的公盐
hashService.setGeneratePublicSalt( true );
// 公盐生成器
hashService.setRandomNumberGenerator( new SecureRandomNumberGenerator() );
// 生成哈希时,默认的迭代计算次数
hashService.setHashIterations( 1 );
 
// 哈希生成请求
HashRequest request = new HashRequest.Builder()
    .setAlgorithmName( "MD5" )
    // 原文
    .setSource( ByteSource.Util.bytes( "secret" ) )
    // 请求可以指定盐
    .setSalt( ByteSource.Util.bytes( "userid" ) )
    // 迭代计算次数
    .setIterations( 2 ).build();
// 计算哈希
String hex = hashService.computeHash( request ).toHex(); 
加解密 

Shiro内置了对多种对称密钥算法的支持,例如AES:

Java
1
2
3
4
5
6
7
8
9
AesCipherService aesCipherService = new AesCipherService();
// 密钥长度
aesCipherService.setKeySize( 128 );
// 生成密钥
Key key = aesCipherService.generateNewKey();
// 加密
String encrptText = aesCipherService.encrypt( "secret".getBytes(), key.getEncoded() ).toHex();
// 解密
new String( aesCipherService.decrypt( Hex.decode( encrptText ), key.getEncoded() ).getBytes() );
密码服务 
PasswordService

该接口提供密码加密服务,Shiro提供默认实现DefaultPasswordService。

CredentialsMatcher

该接口提供密码验证服务,Shiro提供实现PasswordMatcher、HashedCredentialsMatcher。

Web集成
拦截器

在Servlet环境下,Shiro支持基于Servlet Filter的拦截器机制:

shiro-filters-class-diagram

Shiro的过滤器类层次从javax.servlet.Filter扩展。NameableFilter允许设置过滤器的名字,可以基于名字来引用过滤器。OncePerRequestFilter可以确保过滤器针对单个请求只能执行一次(不会因为forward导致重复执行),还支持禁用过滤器。ShiroFilter则是Shiro应用逻辑的入口点,需要在web.xml中注册。

AdviceFilter

拦截器逻辑主要在AdviceFilter类层次中实现,AdviceFilter引入了AOP风格的请求拦截机制:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public abstract class AdviceFilter extends OncePerRequestFilter {
 
    /**
     * 执行预处理操作,例如身份验证、授权
     * 如果应当继续向前处理拦截器链,则返回true,否则返回false
     */
    protected boolean preHandle( ServletRequest request, ServletResponse response ) throws Exception {
        return true;
    }
 
    /**
     * 执行后处理操作
     * 如果当前拦截器执行过程中没有抛出异常,则执行此方法
     */
    protected void postHandle( ServletRequest request, ServletResponse response ) throws Exception {
    }
 
    /**
     * 执行清理操作,例如解除Subject和线程的绑定关系
     * 即使preHandle方法返回false,也会执行此方法
     */
    public void afterCompletion( ServletRequest request, ServletResponse response, Exception exception ) throws Exception {
    }
 
    /**
     * 此默认实现仅仅是继续执行拦截器链
     */
    protected void executeChain( ServletRequest request, ServletResponse response, FilterChain chain ) throws Exception {
        chain.doFilter( request, response );
    }
 
    /**
     * 此方法和Filter.doFilter类似,但是由父类保证了一次性处理。默认逻辑如下:
     * 1、调用preHandle,如果结果为true转到2,否则转到3
     * 2、通过executeChain调用下一个拦截器。如果没有出现异常转到3,否则转到4
     * 3、调用postHandle,转到4
     * 4、调用afterCompletion
     *
     */
    public void doFilterInternal( ServletRequest request, ServletResponse response, FilterChain chain ) throws ServletException, IOException {
    }
 
}

AdviceFilter的主要子类型包括:

拦截器 说明
PathMatchingFilter

下面一个拦截器的基类

支持Ant风格的URL匹配,识别URL需要的访问权限

AccessControlFilter

下面两个拦截器的基类

AuthenticationFilter 用于确保当前Subject已经通过身份验证
AuthorizationFilter 用于确保当前Subject被授权访问当前URL
拦截器链

Shiro的拦截器链,代理了Servlet的过滤器链。在Servlet过滤器链执行之前,首先执行Shiro的拦截器链。代理工作由ProxiedFilterChain负责。

ProxiedFilterChain利用FilterChainResolver为当前请求装配拦截器链:

Java
1
2
3
4
public interface FilterChainResolver {
    // 为当前请求装配拦截器链,originalChain为原始的Servlet过滤器链
    FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain);
}

FilterChainResolver的实现类PathMatchingFilterChainResolver,根据INI配置文件中的urls段来决定如何装配拦截器链。

PathMatchingFilterChainResolver利用DefaultFilterChainManager来管理拦截器链,后者维护url pattern和拦截器链的映射关系。

DefaultFilterChainManager默认会添加枚举DefaultFilter中定义的拦截器。枚举成员的名称在urls段中引用,表示某个url pattern需要启用某个拦截器。

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public enum DefaultFilter {
 
    anon(AnonymousFilter.class),
    authc(FormAuthenticationFilter.class),
    authcBasic(BasicHttpAuthenticationFilter.class),
    logout(LogoutFilter.class),
    noSessionCreation(NoSessionCreationFilter.class),
    perms(PermissionsAuthorizationFilter.class),
    port(PortFilter.class),
    rest(HttpMethodPermissionFilter.class),
    roles(RolesAuthorizationFilter.class),
    ssl(SslFilter.class),
    user(UserFilter.class);
}
动态配置

IniWebEnvironment的默认行为是从INI文件的urls或filters段读取信息,来配置拦url pattern的拦截器链:

org.apache.shiro.web.env.IniWebEnvironment
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected FilterChainResolver createFilterChainResolver() {
 
    FilterChainResolver resolver = null;
 
    Ini ini = getIni();
 
    if (!CollectionUtils.isEmpty(ini)) {
        Ini.Section urls = ini.getSection( IniFilterChainResolverFactory.URLS);
        Ini.Section filters = ini.getSection(IniFilterChainResolverFactory.FILTERS);
        if (!CollectionUtils.isEmpty(urls) || !CollectionUtils.isEmpty(filters)) {
            IniFilterChainResolverFactory factory = new IniFilterChainResolverFactory(ini, this.objects);
            resolver = factory.getInstance();
        }
    }
 
    return resolver;
}

要编程式的配置url pattern对应的拦截器,而不是静态的编写在INI文件中,可以扩展IniWebEnvironment:

cc.gmem.study.shiro.IniWebEnvironment
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class IniWebEnvironment extends org.apache.shiro.web.env.IniWebEnvironment {
 
    @Override
    protected FilterChainResolver createFilterChainResolver() {
        // 创建拦截器链解析器
        PathMatchingFilterChainResolver fcr = new PathMatchingFilterChainResolver();
        // 添加默认拦截器实现
        FilterChainManager fcm = new DefaultFilterChainManager();
        for ( DefaultFilter filter : DefaultFilter.values() ) {
            fcm.addFilter( filter.name(), (Filter) ClassUtils.newInstance( filter.getFilterClass() ) );
        }
        // 动态配置URL和拦截器的对应关系
        fcm.addToChain( "/authenticated", "authc" );
        fcm.addToChain( "/role", "authc" );
        fcm.addToChain( "/role", "roles", "admin" );
        return fcr;
    }
}

然后让Shiro使用此IniWebEnviroment:

web.xml
XML
1
2
3
4
<context-param>
    <param-name>shiroEnvironmentClass</param-name>
    <param-value>cc.gmem.study.shiro.IniWebEnvironment</param-value>
</context-param>
内置拦截器 
名字 说明
身份验证
authc

org.apache.shiro.web.filter.authc.FormAuthenticationFilter

基于表单验证的登陆拦截器,如果当前Subject没有登陆,则跳转到预设的URL,主要属性:

usernameParam  登陆名请求参数
passwordParam  密码请求参数
rememberMeParam  是否记住我请求参数
loginUrl 登陆URL
successUrl 登陆成功后重定向地址
failureKeyAttribute  登陆失败后错误信息存储到的属性键

authcBasic

org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter

基于HTTP基本验证的登陆拦截器,主要属性:

applicationName 登陆弹框提示的应用程序名称

logout

org.apache.shiro.web.filter.authc.LogoutFilter 

退出登陆拦截器,主要属性:

redirectUrl 退出登陆后的重定向地址

user

org.apache.shiro.web.filter.authc.UserFilter

允许通过身份验证的或者记住我的用户访问

anon 

org.apache.shiro.web.filter.authc.AnonymousFilter 

不需要登陆即可访问

访问控制
roles

org.apache.shiro.web.filter.authz.RolesAuthorizationFilter

基于角色的访问控制,用于验证Subject是否有当前URL的访问权限。主要属性:

loginUrl 登陆URL
unauthorizedUrl 如果当前用户未授权,重定向到的URL

perms

org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter

基于权限的访问控制,用于验证Subject是否有当前URL的访问权限。主要属性:

loginUrl 登陆URL
unauthorizedUrl 如果当前用户未授权,重定向到的URL

port

org.apache.shiro.web.filter.authz.PortFilter

端口拦截器,自动改写URL的端口(默认80)并重定向

rest

org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter

REST风格的拦截器,根据HTTP方法自动构建期望Subject拥有的权限字符串

例如配置/users=rest[user],会自动为不同请求方法构建:

POST  = user:create
PUT  = user:update
DELETE = user:delete
HEAD =user:read
TRACE =user:read
OPTIONS =user:read

ssl

 org.apache.shiro.web.filter.authz.SslFilter

强制HTTPS拦截器,如果请求访问不是基于HTTPS协议,强制重定向

内置拦截器会默认注册,不需要在INI中手工配置。你可以直接修改这些拦截器的属性:

shiro.ini
INI
1
2
3
4
; 设置属性
perms.unauthorizedUrl=/unauthorized  
; 禁用拦截器
perms.enabled=false

 

 

 

← ElasticSearch学习笔记
OpenStack学习笔记 →

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • Jenkins插件开发
  • Maven POM文件配置示例
  • gRPC学习笔记
  • Spring对JMS的支持
  • 在Kubernetes中管理和使用Jenkins

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • Bazel学习笔记 37 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • Ceph学习笔记 27 people like this
  • 基于Calico的CNI 27 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2