Shiro学习day-71:Shiro安全框架

一、Shiro框架介绍

1.什么是Shiro?

Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和 会话管理等功能。

  • Shiro应用场景:

对于任何一个应用程序,Shiro都可以提供全面的安全管理服务。其不仅可 以用在JavaSE环境,也可以用在JavaEE环境。

2.Shiro的架构图:

  • 从外部看shiro:


    shiro.png
  • 从Shiro内部看Shiro的框架:


    Shiro.png
  • Shiro中常见的英文名词:

Subject:主体;
Security:安全;
Realm:领域,范围;
Authenticator:认证器;
Authentication:认证;
Authorizer:授权器;
Authorization:授权;
Cryptography:密码;
Credential:证书、凭证;
Matcher:匹配器;
Principal:身份。

二、Shiro环境搭建

1.Shiro中的配置文件:

shiro.ini文件放在classpath下,shiro会自动查找。其中格式是key/value 键值对配置。INI配置文件一般适用于用户少且不需要在运行时动态创建的 情景下使用。 ini文件中主要配置有四大类:main,users,roles,urls。

shiro.ini

(1)[mian]:

main主要配置shiro的一些对象,例如securityManager ,Realm, authenticator,authcStrategy 等等。

image.png

(2)[users]:

[users]允许你配置一组静态的用户,包含用户名,密码,角色,一个用户 可以有多个角色,可以配置多个角色。

image.png

(3)[roles]:

[roles]将角色和权限关联起来,格式为:角色名=权限字符串1,权限字符 串2…..。

image.png

(4)、[roles]

[roles]将角色和权限关联起来,格式为:角色名=权限字符串1,权限字符 串2…..。

image.png

2.Shiro环境搭建实现简单的认证:

认证:验证用户是否合法 在 shiro 中,用户需要提供principals (身份)和credentials(凭证) 给shiro,从而实现对用户身份的验证。

  • principals:

身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。 例如:用户名/邮箱/手机号等。

  • .credentials

凭证,即只有主体知道的安全值,如密码/数字证书等。 最常见的principals和credentials组合就是用户名/密码。

  • 实现简单的认证:
    (1)导入jar包:


    jar

(2)shiro.ini:

[users]
root = 1234

(3)测试:

package com.zlw.test;

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.junit.Test;

//实现简单认证
public class AuthenticationTest {

    @Test
    public void testAuthentication() {
        //1.构建SecurityManager工厂
        IniSecurityManagerFactory securityManagerFactory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //2.通过securityManagerFactory工厂获取SecurityManager实例
        SecurityManager securityManager = securityManagerFactory.getInstance();
        //3.将securityManager设置到运行环境中
        SecurityUtils.setSecurityManager(securityManager);
        //4.获取subject实例
        Subject subject = SecurityUtils.getSubject();
        //5.创建用户名和密码验证令牌Token
        UsernamePasswordToken token = new UsernamePasswordToken("root","1234");
        //6.进行身份验证
        subject.login(token);
        //7.判断是否验证通过
        System.out.println("验证是否通过:"+subject.isAuthenticated());
    }
}
结果

3.Shiro内置的JDBCRealm:

Shiro默认使用自带的IniRealm,IniRealm从ini配置文件中读取用户的信息。 大部分情况下需要从系统的数据库中读取用户信息,所以需要使用JDBCRealm或自定义Realm;:使用JDBCRealm提供数据源,从而实现认证 。
在JDBCRealm中限定了表中的表名和字段。

(1)创建users表,(字段为username,password):


users

(2)导入jar包:


jar

(3)配置shiro.ini:
[main]
#配置Realm
jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm

#配置数据源
dataSource = com.mchange.v2.c3p0.ComboPooledDataSource
dataSource.driverClass = com.mysql.jdbc.Driver
dataSource.jdbcUrl = jdbc:mysql:///shiro
dataSource.user = root
dataSource.password = root

jdbcRealm.dataSource = $dataSource

#将Realm注入给SecurityManager
securityManager.realm = $jdbcRealm

(4)测试:

package com.zlw.test;

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.junit.Test;

public class AuthenticationTest {
    
    @Test
    public void testAuthentication() {
        //1.构建SecurityManager工厂
        IniSecurityManagerFactory securityManagerFactory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //2.通过securityManagerFactory工厂获取SecurityManager实例
        SecurityManager securityManager = securityManagerFactory.getInstance();
        //3.将SecurityManager设置到运行环境中
        SecurityUtils.setSecurityManager(securityManager);
        //4.获取欧subject实例
        Subject subject = SecurityUtils.getSubject();
        //5.创建用户名密码验证令牌Token
        UsernamePasswordToken token = new UsernamePasswordToken("victor", "123456");
        //6.进行身份验证
        subject.login(token);
        //7.判断是否认证通过
        System.out.println(subject.isAuthenticated());
    }
}
结果

4.自定义Realm提供数据源:

自定义Realm,可以注入给securityManager更加灵活的安全数据源通过实现Realm接口,或根据需求继承他的相应子类即可。使用自定义Realm提供数据源,从而实现认证 。

(1)创建数据库表(自定义表名,字段名):

CREATE TABLE `user` (
  `userid` int(5) NOT NULL AUTO_INCREMENT,
  `username` varchar(30) DEFAULT NULL,
  `pwd` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`userid`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

(2)导入jar包:


jar

(3)自定义Realm继承AuthenticatingRealm类:

package com.zlw.realm;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.realm.AuthenticatingRealm;

public class CustomRealm extends AuthenticatingRealm{

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        PreparedStatement pstm = null;
        Connection conn = null;
        ResultSet rs = null;
        String username = token.getPrincipal().toString();

        String principal = null;
        String credentials = null;
         SimpleAuthenticationInfo simpleAuthenticationInfo = null;
        
        try {
            //加载驱动
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/shiro", "root", "root");
           //创建对象
            pstm = conn.prepareStatement("select *from user where username=?");
            pstm.setString(1,username);
            //执行sql
            rs = pstm.executeQuery();
            while(rs.next()) {
                principal = rs.getString("username");
                credentials = rs.getString("pwd");
            }
            
            simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, credentials,"customRealm");
            
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            try {
                rs.close();
                pstm.close();
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return simpleAuthenticationInfo;
    }
}

(4)测试:

package com.zlw.test;

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.junit.Test;

public class Test01 {

    @Test
    public void testAuthentication() {
        // 1.构建SecurityManager工厂
        IniSecurityManagerFactory securityManagerFactory = new IniSecurityManagerFactory();
        // 2.通过securityManagerFactory工厂获取SecurityManager实例
        SecurityManager securityManager = securityManagerFactory.getInstance();
        // 3.将SecurityManager设置到运行环境中
        SecurityUtils.setSecurityManager(securityManager);
        // 4.获取欧subject实例
        Subject subject = SecurityUtils.getSubject();
        // 5.创建用户名密码验证令牌Token
        UsernamePasswordToken token = new UsernamePasswordToken("admin", "1234");
        // 6.进行身份验证
        subject.login(token);
        // 7.判断是否认证通过
        System.out.println(subject.isAuthenticated());
    }
}
结果

三、MD5加密

1.常见 的加密算法:

  • 对称加密算法(加密与解密密钥相同):


    示例
  • 非对称加密算法(加密密钥和解密密钥不同):


    示例
  • 对称与非对称算法比较:


    示例
  • 散列算法比较:


    示例

2.使用DM5加密:

  • 加盐:

使用MD5存在一个问题,相同的password生产的Hash值是相同的,如 果两个用户设置了相同的密码,那么数据库当就会存储相同的值,这样是极 不安全的。
加Salt可以一定程度上解决这一问题。所谓加Salt方法,就是加点 “佐料”。其基本想法是这样的:当用户首次提供密码时(通常是注册时), 由系统自动往这个密码里撒一些“佐料”,然后再散列。而当用户登录时, 系统为用户提供的代码撒上同样的“佐料”,然后散列,再比较散列值,来 确定密码是否正确。
原理 :给原文加入随机数生成新的MD5值。

  • 迭代:

对加密的动作进行重复反馈过程的活动;加密的次数。

  • 代码示例:
package com.zlw.test;

import org.apache.shiro.crypto.hash.Md5Hash;
import org.junit.Test;

//MD5加密、加盐以及迭代
public class MD5Test {

    @Test
    public void testMD5() {
        //md5加密
        Md5Hash md5 = new Md5Hash("1234556");
        System.out.println(md5);
    }
    
    @Test
    public void testMD5Hash() {
        //加盐
        Md5Hash md5 = new Md5Hash("123456","aaa");
        System.out.println(md5);
    }
    
    @Test
    public void testMD5Hash2() {
        //迭代
        Md5Hash md5 = new Md5Hash("123456","sxt",3);
        System.out.println(md5);
    }
}
MD5加密

加盐

迭代

3.凭证匹配器和Shiro的授权:

  • 凭证匹配器:

在Realm接口的实现类AuthenticatingRealm中有credentialsMatcher属性。 意为凭证匹配器。常用来设置加密算法及迭代次数等。

  • Shiro授权:

授权:又称作为访问控制,是对资源的访问管理的过程。即对于 认证通过的用户,授予他可以访问某些资源的权限。
Shiro 支持三种方式的授权:代码触发、注解触发、标签触发 。

授权示例

4.使用MD5加密和Shiro授权实现菜单授权:

(1)创建数据库:

  • users表:


    示例
  • roles角色表(角色和user是一对多关系):


    示例
  • Menus菜单表(菜单和角色是多对多关系):


    示例
  • 角色和菜单的中间表:


    示例

    (2)导入jar包:


    jar

    (3)自定义Realm继承AuthorizingRealm类;重写doGetAuthenticationInfo(AuthenticationToken token):认证的方法;doGetAuthorizationInfo(PrincipalCollection collection):授权方法;
package com.zlw.realm;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Set;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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 org.apache.shiro.util.ByteSource;

/**
 * AuthenticatingRealm:认证使用的Realm,只包含认证的方法,认证时调用doGetAuthenticationInfo方法
 * AuthorizingRealm:授权使用的Realm,继承了AuthenticatingRealm, 包含认证和授权的方法,
 *       认证时调用doGetAuthenticationInfo方法,授权时调用doGetAuthorizationInfo方法
 * 
 * 自定义Realm的实现步骤: 我们通过自定义Realm从指定的数据源中获取数据 
 * 1.自定义Realm,继承AuthorizingRealm类
 * 2.重写认证和授权的方法 doGetAuthenticationInfo(AuthenticationToken token):认证的方法
 * doGetAuthorizationInfo(PrincipalCollection arg0):授权方法
 * 3.在配置文件(shiro.ini)中配置自定义的realm customRealm = com.sxt.realm.CustomRealm
 * securityManager.realm = $customRealm
 * 
 * @author zhang
 *
 */
public class CustomRealm extends AuthorizingRealm {

    // 授权方法
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
        Connection conn = null;
        PreparedStatement pstm = null;
        ResultSet rs = null;
        String username = token.getPrincipal().toString();//从令牌中获取身份信息
        String principal = null;
        String credentials = null;
        String password_salt = null;
        SimpleAuthenticationInfo simpleAuthenticationInfo = null;

        try {
            // 加载驱动
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/rbac", "root", "root");
            pstm = conn.prepareStatement("select *from users where username=?");
            pstm.setString(1, username);

            rs = pstm.executeQuery();
            while (rs.next()) {
                principal = rs.getString("username");
                credentials = rs.getString("userpwd");
                password_salt = rs.getString("salt");//获取盐值
            }
            simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, credentials,ByteSource.Util.bytes(password_salt), "customRealm");

        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                rs.close();
                pstm.close();
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return simpleAuthenticationInfo;
    }

    // 响应方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection collection) throws AuthenticationException {
        Connection conn = null;
        PreparedStatement pstm = null;
        ResultSet rs = null;
        String userName = collection.getPrimaryPrincipal().toString();//获取主体身份信息
        Set<String> roleNameSet = new HashSet<String>();//创建Set集合拥有封装所有的菜单名称
        Set<String> menuNameSet = new HashSet<String>();//创建Set集合拥有封装所有的菜单名称
        
        try {
            //加载驱动
            Class.forName("com.mysql.jdbc.Driver");
            //获取链接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/rbac", "root", "root");
            String sql = "select r.roleName,m.menuName "
                    + "from users u, roles r, roles_menus rm,menus m "
                    + "where u.role_id=r.roleid "
                    + "and r.roleid=rm.roles_id "
                    + "and rm.menus_id=m.menuid "
                    + "and u.username=?";
            pstm = conn.prepareStatement(sql);
            pstm.setString(1, userName);
            rs = pstm.executeQuery();
            while (rs.next()) {
                String roleName = rs.getString("roleName");
                String menuName = rs.getString("menuName");
                roleNameSet.add(roleName);
                menuNameSet.add(menuName);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            try {
                rs.close();
                pstm.close();
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            
        }
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addRoles(roleNameSet);
        simpleAuthorizationInfo.addStringPermissions(menuNameSet);
        return simpleAuthorizationInfo;
    }
}

(4)配置Shiro.ini文件:

[main]
#配置凭证配置器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#设置凭证配置器的算法名称
credentialsMatcher.hashAlgorithmName=MD5 
#设置凭证匹配器的的迭代次数
credentialsMatcher.hashIterations=3

#配置Realm
customRealm =com.zlw.realm.CustomRealm
#设置Realm的凭证匹配器
customRealm.credentialsMatcher=$credentialsMatcher
#将Realm注入给SecurityManager
securityManager.realm = $customRealm

(5)测试:

package com.zlw.test;

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.junit.Test;

public class MD5Test {
    
    @Test
    public void TestMD5() {
        //1.构建SecurityManager工厂
        IniSecurityManagerFactory securityManagerFactory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //2.通过securityManagerFactory工厂获取SecurityManager实例
        SecurityManager securityManager = securityManagerFactory.getInstance();
        //3.将securityManager设置到运行环境中
        SecurityUtils.setSecurityManager(securityManager);
        //4.获取subject实例
        Subject subject = SecurityUtils.getSubject();
        //5.创建用户名和密码验证令牌Token
        UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","123456");
        //6.进行身份验证
        subject.login(token);
        //7.判断是否验证通过
        System.out.println("验证是否通过"+subject.isAuthenticated());
        //判断该用户是否拥有指定的角色
        System.out.println("判断是否拥有指定角色:"+subject.hasRole("管理员"));
        //8.判断该用户是否拥有指定的权限
        System.out.println("是否拥有指定权限:"+subject.isPermitted("客户管理"));
            
    }   
}
zhangsan

admin

5.可能会遇到的异常信息:

(1)UnknownAccountException:No account found for user
用户主体找不到,用户名错误。
(2)IncorrectCredentialsException:did not match the expected credentials
用户认证没有找到相匹配的凭证;用户密码错误。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351

推荐阅读更多精彩内容