Shiro 快速入门

Scroll Down

shiro 快速入门

参考官方文章:https://www.infoq.cn/article/apache-shiro/?itm_source=infoq_en&itm_medium=link_on_en_item&itm_campaign=item_in_other_langs

shiro 简单介绍

shiro 是一个 apache 的安全框架, 提供了认证,授权,加密和会话等管理功能,可以为任何应用提供保障

他可以解决以下问题:

  1. 认证 - 用户身份是被
  2. 授权 - 访问控制
  3. 密码加密 - 保护或隐藏数据防止被偷窥
  4. 会话管理 - 每用户相关的事件敏感的状态

Shiro 最早被叫做 JSecurity 项目,最早始于2003年,是一个历史悠久的项目,

核心概念

Subject

Subject 是一个安全术语,指当前操作的用户(进程等),至于为什么不用User,因为很多框架,很多程序自己就要定义用户,Shiro 不想引起歧义

SecurityManager

Subject 代表了当前用户的安全操作,SecurityManager 则管理 所有 用户的安全操作。是 Shiro 框架的核心,充当 保护伞, 引用了多个内部嵌套的安全组件。

SecurityManager 是一个单例模式实现的,可以使用多种方式去配置,官方建议的是ini文件配置

Realms

Realm 充当了 Shiro 与应用安全数据间的桥梁或者连接器

Realm 实质上是一个安全相关的DAO,它封装了数据源的连接细节,并在需要时将相关数据提供给 Shiro。

核心流程

认证

认证是合适用户身份的过程。也就是说,当用户使用应用进行认证时,他们就在证明自己所说的那个人,分为三步

  1. 收集用户的身份信息
  2. 将当时人和证书提交给系统
  3. 如果提交的证书与系统期望的用户身份匹配,该用户被认为是通过认证的

授权

授权的实质是访问控制,控制用户能访问哪些资源。

通过Subject 的api可以轻松地做到获取角色。

会话管理

Shiro的会话是是独立于容器的。一旦获得 Shiro 的会话,你几乎可以像使用 HttpSession 一样使用它。

加密

加密是隐藏或混淆数据以避免被偷窥的过程。在加密方面,Shiro 的目标是简化并让 JDK 的加密支持可用。

Shiro 对加密和解密都进行包装使使用非常方便

Web支持

Shiro 附带了一个帮助保护 Web 应用的强建的 Web 支持模块。

唯一需要做的就是在 web.xml 中定义一个 Shiro Servlet 过滤器。

JSP标签

Shiro 还提供了JSP标签库,用来根据权限控制JSP的输出

实战部分

导入依赖

<parent>
    <groupId>org.apache</groupId>
    <artifactId>apache</artifactId>
    <version>21</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.5.2</version>
    </dependency>
    
    <!-- configure logging -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <scope>runtime</scope>
        <version>1.7.26</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.26</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
        <scope>runtime</scope>
    </dependency>
</dependencies>

日志配置

创建log4j.properties文件

log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

# General Apache libraries
log4j.logger.org.apache=WARN

# Spring
log4j.logger.org.springframework=WARN

# Default Shiro logging
log4j.logger.org.apache.shiro=INFO

# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN

配置 ini 文件

创建shiro.ini作为启动的配置文件

[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz

# -----------------------------------------------------------------------------
# Roles with assigned permissions
# 
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5

QuickStart.java

/**
 * Simple Quickstart application showing how to use Shiro's API.
 *
 * @since 0.9 RC2
 */
public class Quickstart {

    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);


    public static void main(String[] args) {

        // The easiest way to create a Shiro SecurityManager with configured
        // realms, users, roles and permissions is to use the simple INI config.
        // We'll do that by using a factory that can ingest a .ini file and
        // return a SecurityManager instance:

        // Use the shiro.ini file at the root of the classpath
        // (file: and url: prefixes load from files and urls respectively):
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();

        // for this simple example quickstart, make the SecurityManager
        // accessible as a JVM singleton.  Most applications wouldn't do this
        // and instead rely on their container configuration or web.xml for
        // webapps.  That is outside the scope of this simple quickstart, so
        // we'll just do the bare minimum so you can continue to get a feel
        // for things.
        SecurityUtils.setSecurityManager(securityManager);

        // Now that a simple Shiro environment is set up, let's see what you can do:

        // get the currently executing user:
        Subject currentUser = SecurityUtils.getSubject();

        // Do some stuff with a Session (no need for a web or EJB container!!!)
        Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Retrieved the correct value! [" + value + "]");
        }

        // let's login the current user so we can check against roles and permissions:
        if (!currentUser.isAuthenticated()) {
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true);
            try {
                currentUser.login(token);
            } catch (UnknownAccountException uae) {
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {
                //unexpected condition?  error?
            }
        }

        //say who they are:
        //print their identifying principal (in this case, a username):
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //test a role:
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        //test a typed permission (not instance-level)
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring.  Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }

        //a (very powerful) Instance Level permission:
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

        //all done - log out!
        currentUser.logout();

        System.exit(0);
    }
}

这样,最基本的一个快速入门项目就搭建完成了

代码分析

读取配置文件

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

创建SecurityManager

SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);

这样后台的所有配置都配置好了(仅限此快速入门)

获取Subject

Subject currentUser = SecurityUtils.getSubject();

用**SecurityUtils.getSubject()**方法来进行Subject对象的获取,我们一般根据它的意义命名为user。

获取Session

Session session = currentUser.getSession();
session.setAttribute( "someKey", "aValue" );

Session的对象获取不需要web环境,这里Session的使用方式和HttpSession很像

登录验证

// let's login the current user so we can check against roles and permissions:
if (!currentUser.isAuthenticated()) {
    UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
    token.setRememberMe(true);
    try {
        currentUser.login(token);
    } catch (UnknownAccountException uae) {
        log.info("There is no user with username of " + token.getPrincipal());
    } catch (IncorrectCredentialsException ice) {
        log.info("Password for account " + token.getPrincipal() + " was incorrect!");
    } catch (LockedAccountException lae) {
        log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                "Please contact your administrator to unlock it.");
    }
    // ... catch more exceptions here (maybe custom ones specific to your application?
    catch (AuthenticationException ae) {
        //unexpected condition?  error?
    }
}

如果subject没有被验证,就去获得token(这里新建了一个),这里使用了ini配置文件中配置好的用户名和密码

//this is all you have to do to support 'remember me' (no config - built in!):
token.setRememberMe(true);

如果需要支持 记住我 功能,只需要加一行代码即可

如果登录验证失败,就需要处理捕捉的各种异常。

  1. UnknownAccountException未知用户异常
  2. IncorrectCredentialsException密码错误异常
  3. LockedAccountException用户被锁异常

角色判断

if ( currentUser.hasRole( "schwartz" ) ) {
    log.info("May the Schwartz be with you!" );
} else {
    log.info( "Hello, mere mortal." );
}

测试这个subject有没有这个角色

权限判断

//test a typed permission (not instance-level)
if (currentUser.isPermitted("lightsaber:wield")) {
    log.info("You may use a lightsaber ring.  Use it wisely.");
} else {
    log.info("Sorry, lightsaber rings are for schwartz masters only.");
}

测试一个权限

因为ini文件配置了这个,所有lightsaber的任何权限都可以执行

# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*

登出

currentUser.logout(); //removes all identifying information and invalidates their session too.

登出的同时也去掉了所有身份验证信息和session会话信息