1、Shiro

一、Shiro介绍

        Apache Shiro(发音为“shee-roh”,日语“堡垒(Castle)”的意思)是一个强大灵活的开源安全框架,提供认证、授权、会话管理以及密码加密等功能,由Java语言开发。

     Shiro可以做到:                

  1.  验证用户身份
  2. 对用户进行访问控制(配置文件中配置拦截器,对请求地址进行拦截)
  3. 判断某个用户是否被赋予某个特定角色
  4. 判断某个用户是否被允许执行某些操作
  5. 可以在各种环境下使用Session API,即使不在web或EJB容器中
  6. 对认证、访问控制火灾会话生命周期中的事件进行响应处理 

二、Shiro功能    

2.1Apache Shiro 框架提供了很多功能,下图展示了Shiro的着重点

1、Shiro

Shiro 主要面向 Shiro开发团队所谓的“应用安全的四大基础”——认证,授权,会话管理与密码加密

  • 认证:或“登录”,用以验证用户身份。
  • 授权:访问控制,比如决定谁可以访问某些资源
  • 会话管理:管理用户相关的Session,即使是在非Web或EJB应用中。
  • 加密:可以非常方便地使用(各种算法)加密算法来保证数据的安全。

2.2 Shiro中的几个重要概念

       Shiro 的框架主要有三个*概念:Subject,SecurityManager 和 Realms

1、Shiro

 Subject:实际是安全邻域里的“当前执行用户”。Subject 可以是一个人,或者是第三方服务,守护进程账户,定时任务等等——可以是基本上任何与软件交互的事务。Subject的实例都会(也是必须)绑定一个 SecurityManager,对 Subject的操作会转换为 Subject 与 SecurityManager的交互。

SecurityManager:Shiro架构核心,协调内部组件运作。配置完成后,开发者只需要使用 Subject。

Realm:在 Shiro 和你的安全数据之间扮演“桥梁”或“连接器”的角色。用于用户账号的认证和授权。

        *进行认证或授权时,Shiro会从应用配置的一个或多个Realms中来查找。这个意义上Realm其实就是安全(操作)特有的DAO:它封装了数据源的细节,并在Shiro需要时提供相关数据。配置Shiro时,必须至少得有一个Realm以用于认证(和/或)授权。在SecurityManager中可以配置Realm,至少得有一个。Shiro自带一些开箱即用的Realms用于连接像是LDAP,关系数据库(JDBC),文本配置源(比如INI)以及属性文件等等这样的安全数据来源。如果默认的不能满足你的需要,你也可以自己实现一个Realm。

2.3 Shiro 的核心架构

1、Shiro

Subject:安全视角下与软件交互的实体(用户、第三方等等)

SecurityManager:安全管理器         协调内部各组件的作用;同时管理 Shiro 的所有用户,对用户进行相关的安全操作

Authenticator:认证器       结合Realm完成认证操作

Authorizer:授权器        结合Realm完成授权操作

CacheManager:缓存管理器        缓存权限数据

Realm:域

SessionManager:会话管理器        管理Session

CrypotoGraphy:加密        用于密码加密


三、Shiro 认证

 3.1即身份验证:

         在 Shiro 中,用户需要提供 principals Credentials 给 Shiro 进行验证。

principals :身份(用户名/手机号等)

Credentials :证明/凭证(密码)

认证需要用到两个相关的概念是 Subject 及 Realm,分别是主体及验证主体的数据源。

1、Shiro

【认证流程】
   主体调用Subject.login()该方法后,由Realm查询数据源进行校验,把校验的结果给
   Authenticator,由认证器分析完成认证操作:
   如果身份错误,抛出UnknownAccountException;
   如果凭证错误,抛出IncorrectCredentialException

认证时 Realm 有三种方式:下面的 Realm 都是测试,未查询数据库,用 ini 文件配置代替数据源

  1. 默认 Realm                  无法支持加密功能   
  2. 自定义 Realm
  3. 密码加密,凭证匹配器

3.2 环境搭建(用于JavaEE工程,进行测试;Maven-Web工程则只需要导入Shiro相关的依赖进行了,包含了这些依赖)

<%--在pomp文件中导入依赖--%>
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.1.3</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.4.1</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.9</version>
    <scope>provided</scope>
</dependency>

3.3 默认 Realm  无需创建Realm类

此处使用 ini 配置文件,通过 [users] 指定了两个主体

配置文件 shiro-authenticate.ini    需要在 idea 中下载该插件(类似油桶,下载后要重启软件)

1、Shiro

[users]
zhangsan=123
lisi=lisi

测试代码

//测试默认的Realm完成认证
    @Test
    public void test01(){
        //读取配置文件,生成工厂对象
        Factory<SecurityManager> factory =
                new IniSecurityManagerFactory("classpath:autenticate/shiro-authenticate.ini");
        //从工厂中取出SecurityManager:单例
        SecurityManager securityManager = factory.getInstance();

        //把securityManager设置环境变量中 等同于把securityManager放入到了一个工具类中,方便获取securityManager中
        //各个组件对象的
        SecurityUtils.setSecurityManager(securityManager);

        //获取Sebject主体
        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","1111");
        try {
            subject.login(token);
        } catch (UnknownAccountException e) {
            System.out.println("身份错误");
        }catch (IncorrectCredentialsException e1){
            System.out.println("凭证错误");
        }
    }

3.4自定义 Realm 

自定义 Realm 需要实现或者继承框架中的 realm 类

1、Shiro

配置文件 shiro-realm.ini

#声明一个realm,获取Realm路径
myRealm= com.bjpowernode.shiro.MyRealm
#指定securityManager的realms实现,即将Realm注入到安全管理器里
securityManager.realms=$myRealm

自定义Realm:

自定义Realm认证流程:先校验身份, 身份没有问题后,再校验凭证

/**
 * AuthenticatingRealm:认证的Realm
 * AuthorizingRealm:授权的Realm
 */
public class MyRealm extends AuthorizingRealm {

    //认证的功能
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //先校验身份
        //获取用户输入的身份
        String principal = (String) authenticationToken.getPrincipal();

        //拿着用户输入的身份,去数据库查询,过程.... where username=principal
        String correctPrincipal = "abc";

        if(correctPrincipal == null){
            throw new UnknownAccountException();
        }

        //再校验凭证
        //从查询出来的身份中取出凭证
        String credential = "admin";
        SimpleAuthenticationInfo simpleAuthenticationInfo =
                new SimpleAuthenticationInfo(principal, credential, "myRealm");
        return simpleAuthenticationInfo;
    }

    //授权
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

}

测试代码

 //自定义的Realm来完成认证
    @Test
    public void test02(){
        //读取配置文件,生成工厂对象
        Factory<SecurityManager> factory =
                new IniSecurityManagerFactory("classpath:autenticate/shiro-realm.ini");
        //从工厂中取出SecurityManager:单例
        SecurityManager securityManager = factory.getInstance();

        //把securityManager设置环境变量中 等同于把securityManager放入到了一个工具类中,方便获取securityManager中
        //各个组件对象的
        SecurityUtils.setSecurityManager(securityManager);

        //获取Sebject主体
        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","admin");
        subject.login(token);

        //判断用户是否认证成功
        boolean authenticated = subject.isAuthenticated();

        System.out.println(authenticated);
    }

3.5自定义 realm,加密完成验证

散列算法

一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如 MD5、SHA 等。一般进行散列时最好提供一个 salt(盐),比如加密密码 “admin”,产生的散列值是 21232f297a57a5a743894a0e4a801fc3”,可以到一些 md5 解密网站很容易的通过散列值得到密码 “admin”,即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,如用户名和 ID(即盐);这样散列的对象是 “密码 + 用户名 +ID”,这样生成的散列值相对来说更难破解。

配置文件 shiro-md5.ini

#凭证匹配器  整合Spring框架,实际就是个bean对象
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#散列算法  加密的方法MD5
credentialsMatcher.hashAlgorithmName=md5
#散列次数  即加密次数
credentialsMatcher.hashIterations=1
#声明一个realm
md5Realm= com.bjpowernode.shiro.Md5Reaml
#将散列设置到当前reaml中  把凭证匹配器注入到Realm中
md5Realm.credentialsMatcher=$credentialsMatcher
#指定securityManager的realms实现
securityManager.realms=$md5Realm

 自定义Realm

public class Md5Realm extends AuthorizingRealm {

    //认证
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        //先校验身份
        //获取用户输入的身份
        String principal = (String) authenticationToken.getPrincipal();

        //拿着用户输入的身份,去数据库查询,过程.... where username=principal
        String correctPrincipal = "";

        if(correctPrincipal == null){
            throw new UnknownAccountException();
        }

        //再校验凭证
        //从查询出来的身份中取出凭证,加密之后的密码
        String credential = "f6fdffe48c908deb0f4c3bd36c032e72";
        String salt = "admin";
        SimpleAuthenticationInfo simpleAuthenticationInfo =
                new SimpleAuthenticationInfo
                        (principal, credential, ByteSource.Util.bytes(salt),"myRealm");
        return simpleAuthenticationInfo;
    }

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
}

 测试代码

  //自定义的Realm来完成加密认证
    @Test
    public void test04(){
        //读取配置文件,生成工厂对象
        Factory<SecurityManager> factory =
                new IniSecurityManagerFactory("classpath:autenticate/shiro-md5.ini");
        //从工厂中取出SecurityManager:单例
        SecurityManager securityManager = factory.getInstance();

        //把securityManager设置环境变量中 等同于把securityManager放入到了一个工具类中,方便获取securityManager中
        //各个组件对象的
        SecurityUtils.setSecurityManager(securityManager);

        //获取Sebject主体
        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","123");
        subject.login(token);

        //判断用户是否认证成功
        boolean authenticated = subject.isAuthenticated();

        System.out.println(authenticated);
    }

四、Shiro 授权

4.1授权概念

授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)

  • 主体 主体,即访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权后才允许访问相应的资源。
  • 资源 在应用中用户可以访问的URL,比如访问 JSP 页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
  • 权限 安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如: 访问用户列表页面 查看/新增/修改/删除用户数据(即很多时候都是 CRUD(增查改删)式权限控制)打印文档等。如上可以看出,权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允许,不反映谁去执行这个操作。所以后续还需要把权限赋予给用户,即定义哪个用户允许在某个资源上做什么操作(权限),Shiro 不会去做这件事情,而是由实现人员提供。Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,即实例级别的)
  • 粗粒度:小红以部门经理登陆到系统中,能操作用户模块的所有资源

4.2授权的三种方式

        1、编程式:通过写 If/else 授权代码块完成

Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
    //有权限
} else {
    //无权限
}

        2、注解式:通过在执行的 Java 方法上放置相应的注解完成

@RequiresPermissions("admin")
public void hello() {
    //有权限
}

        3、JSP/GSP 标签:在 JSP/GSP 页面通过相应的标签完成

<shiro:hasRole name="admin">
<!— 有权限 —>
</shiro:hasRole>

编码式:

授权代码:

 //授权
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //先根据用户的身份查询用户的所有权限
        Object primaryPrincipal = principalCollection.getPrimaryPrincipal();

       //查询数据库省略 List<Permission>
     //   List<String> permissions = new ArrayList<String>();
        //添加权限标识符
      //  permissions.add("user:*");

        //定义一个集合,存储用户对应的角色
        List<String> roles = new ArrayList<String>();
        roles.add("role1");
        roles.add("role2");

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addRoles(roles);
        return simpleAuthorizationInfo;
    }

测试类 

 @Test
    public void test01(){
        Factory<SecurityManager> factory =
                new IniSecurityManagerFactory("classpath:authorize/shiro-authorize.ini");

        SecurityManager securityManager = factory.getInstance();

        SecurityUtils.setSecurityManager(securityManager);

        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan","123" );

        subject.login(usernamePasswordToken);

        boolean authenticated = subject.isAuthenticated();

        System.out.println(authenticated);


//        subject.checkPermissions("user:query");
        boolean has = subject.hasRole("role3");

        System.out.println(has);
    }

上一篇:mysql单表多次内联接查询学科名称,一级学科名称二级学科名称三级学科名称


下一篇:【设计模式】汉堡中的设计模式——观察者模式