【安全】Shiro权限框架基于SE的理解入门

Shiro权限框架基于SE的理解入门

1. Shiro简介

Shiro官网:http://shiro.apache.org/

以下来自官网简介:
Apache Shiro是一个功能强大、易于使用的Java安全框架,它执行身份验证、授权、加密和会话管理。使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序——从最小的移动应用程序到最大的Web和企业应用程序。

  • Apache Shiro 是 Java 的一个安全(权限)框架
  • Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 Java SE 环境,也可以用在 Java EE 环境
  • Shiro 可以完成:认证、授权、加密、会话管理、与 Web 集成、缓存等

功能简介

基本功能点如下图所示:
【安全】Shiro权限框架基于SE的理解入门_第1张图片
各部分功能简介:

  • Authentication:身份认证 / 登录,验证用户是否拥有相应身份
  • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度地验证某个用户对某个资源是否具有某个权限
  • Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 Java SE 环境,也可以是 Web 环境
  • Cryptography:加密,保护数据的安全性,如密码加密储存到数据库,而不是明文储存
  • Web Support:Web 支持,可以非常容易地集成到 Web 环境
  • Caching:缓存,如用户登录后,其用户信息、拥有的角色 / 权限不必每次都去检查,可以提高效率
  • Concurrency:Shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能够把权限自动转播出去
  • Testing:提供测试支持
  • Run As:允许一个用户假装成另一个用户的身份进行访问
  • Remember Me:记住我,这是一个非常常见的功能,即一次登录以后,下次再来的话则不用再次登录

2. Shiro架构

从应用程序的角度观察 Shiro 的工作流程:
【安全】Shiro权限框架基于SE的理解入门_第2张图片
Shiro 架构:

  • Subject:任何可以与应用交互的“用户”
  • SecurityManager:相当于 SpringMVC 中的 DispatcherServlet;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有的 Subject、且负责进行认证、授权、会话及缓存的管理
  • Authenticator:负责 Subject 认证,是一个扩展点,可以自定义实现;可以使用认证策略(Authentication Strategy),即什么情况下算用户认证通过了
  • Authorizer:授权器、即访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
  • Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是内存实现等等;由用户提供;所以一般在应用中都需要实现自己的 Realm
  • Session Manager:管理 Session 生命周期的组件;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在普通 Java SE 环境
  • CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能
  • Cryptography:密码模块,Shiro 提供了一些常见的加密组件用于如密码加密 / 解密

3. 基于SE的认证用户

创建 Maven 工程,导入依赖 jar 包


    
    
        org.apache.shiro
        shiro-core
        1.3.2
    
    
    
        org.slf4j
        slf4j-nop
        1.7.2
    
    
        org.slf4j
        slf4j-api
        1.7.21
    

    
    
        junit
        junit
        4.12
        test
    

配置 shiro.ini 文件如下

[users]
##用户名=密码,角色
root = secret, admin
guest = guest, guest
presidentskroob = 12345, president
darkhelmet = ludicrousspeed, darklord, schwartz
lonestarr = vespa, goodguy, schwartz

[roles]
##角色=权限(*代表全部权限,一个角色可以有多个权限,一个用户可以有多个角色,都是多对多关系)
admin = *
schwartz = lightsaber:*
goodguy = winnebago:drive:eagle5

测试类

package com.jiker.shirose;

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.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class ShiroTest {

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

    @Test
    public void test(){
        IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();

        SecurityUtils.setSecurityManager(securityManager);

        Subject currentUser = SecurityUtils.getSubject();

        Session session = currentUser.getSession();
        session.setAttribute("someKey","aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")){
            log.info("Retrieved the corrent value! {" + value + "}");
        }

        if (!currentUser.isAuthenticated()){
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr","vespa");
            token.setRememberMe(true);
            try {
                currentUser.login(token);
            } catch (UnknownAccountException uae) {
                log.info("用户名不存在:" + token.getPrincipal ());
            } catch (IncorrectCredentialsException ice) {
                log.info ("账户密码 " + token.getPrincipal () + " 不正确!");
            } catch (LockedAccountException lae) {
                log.info("用户名 " + token.getPrincipal () + " 被锁定 !");
            } catch (AuthenticationException ae){ 
            }
        }

        log.info("-------> User {" + currentUser.getPrincipal() + "} logged in successfully.");

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

        if (currentUser.isPermitted("lightsaber:weild")){
            log.info("You may use a lightsaber ring. Use it wisely");
        } else {
            log.info("Sorry,lightsaber rings are for schwartz masters only.");
        }

        if (currentUser.isPermitted("winnebago:drive:eagle5")){
            log.info("You are permitted to 'driver' the winnebago with license plate (id) 'eagle5'.");
        } else {
            log.info("Sorry,you aren't allowed to drive the 'eagle5' winnebago!");
        }

        currentUser.logout();

        System.exit(0);
    }

}

流程解析:
1、使用工厂的方式,读取类路径下的 .ini 文件,进而返回一个 SecurityManager 的实例

   IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
   SecurityManager securityManager = factory.getInstance();

2、让 SecurityManager 实例在 JVM 虚拟机中变为单例且可以访问的
注:大部分应用都不再使用此方式

SecurityUtils.setSecurityManager(securityManager);

3、获取当前的 Subject,调用 SecurityUtils.getSubject()

Subject currentUser = SecurityUtils.getSubject();

4、测试使用 Session
获取 Session :调用 Subject#getSession()

Session session = currentUser.getSession();
session.setAttribute("someKey","aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")){
	log.info("Retrieved the corrent value! {" + value + "}");
}

5、测试当前的用户是否已经被认证,即是否已经登录
调用 SubjectisAuthenticated()
由于当前还未认证,则将用户名、密码封装为 UsernamePasswordToken 对象

 if (!currentUser.isAuthenticated()){
    UsernamePasswordToken token = new UsernamePasswordToken("lonestarr","vespa");
    token.setRememberMe(true);

6、执行登录
能否登录成功取决于配置文件中是否配置了对应的用户名:lonestarr、密码:vespa
同时可能抛出登录时的异常

try {
    currentUser.login(token);
}

配置文件 shiro.ini 中存在
lonestarr = vespa, goodguy, schwartz
 //若没有指定的用户,则抛出 UnknownAccountException  异常
 catch (UnknownAccountException uae) {
	log.info("用户名不存在:" + token.getPrincipal ());
}
//若账户存在但密码不匹配,则抛出 IncorrectCredentialsException 异常
catch (IncorrectCredentialsException ice) {
	log.info ("账户密码 " + token.getPrincipal () + " 不正确!");
}
//用户被锁定的异常: LockedAccountException 
catch (LockedAccountException lae) {
	log.info("用户名 " + token.getPrincipal () + " 被锁定 !");
}
//所有认证时异常的父类 
catch (AuthenticationException ae){ 
}

7、登录验证后,测试是否有某一个角色

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

配置文件中 lonestarr 用户含有 schwartz 角色
lonestarr = vespa, goodguy, schwartz

8、测试用户是否有某一个行为,调用 SubjectisPermitted() 方法

if (currentUser.isPermitted("lightsaber:weild")){
	log.info("You may use a lightsaber ring. Use it wisely");
} else {
    log.info("Sorry,lightsaber rings are for schwartz masters only.");
}

9、测试用户是否具备某一更具体的行为

if (currentUser.isPermitted("winnebago:drive:eagle5")){

配置文件中
goodguy = winnebago:drive:eagle5
意为:允许对 winnebago 类型的 eagle5 做 drive 动作

10、执行登出,调用 Subjectlogout() 方法

currentUser.logout();

时间:2019.6.29 12:05

你可能感兴趣的:(java)