shiro 订阅
Apache Shiro(读作“sheeroh”,即日语“城”)是一个开源安全框架,提供身份验证、授权、密码学和会话管理。Shiro框架直观、易用,同时也能提供健壮的安全性。 展开全文
Apache Shiro(读作“sheeroh”,即日语“城”)是一个开源安全框架,提供身份验证、授权、密码学和会话管理。Shiro框架直观、易用,同时也能提供健壮的安全性。
信息
外文名
Apache Shiro
分    类
编程代码
中文名
Apache Shiro
shiro历史
Shiro的前身是JSecurity。 [1]  2004年,Les Hazlewood和Jeremy Haile创办了Jsecurity。当时他们找不到适用于应用程序级别的合适Java安全框架,同时又对JAAS非常失望。2004年到2008年期间,JSecurity托管在SourceForge上,贡献者包括Peter Ledbrook、Alan Ditzel和Tim Veil。2008年,JSecurity项目贡献给了Apache软件基金会(ASF),并被接纳成为Apache Incubator项目,由导师管理,目标是成为一个顶级Apache项目。期间,Jsecurity曾短暂更名为Ki(读作“Key”),随后因商标问题被社群更名为“Shiro”。随后项目持续在Apache Incubator中孵化,并增加了贡献者Kalle Korhonen。2010年7月,Shiro社区发布了1.0版,随后社区创建了其项目管理委员会,并选举Les Hazlewood为主席。2010年9月22日,Shrio成为Apache软件基金会的顶级项目(TLP)。 [2] 
收起全文
精华内容
下载资源
问答
  • Shiro

    万次阅读 多人点赞 2019-04-29 21:12:26
    文章目录一、初识shiro1.shiro简介:2.框架图说明:3.从外部查看shiro框架4.内部结构框架5.常见单词说明6.Shiro中的shiro.ini说明:(1) 、main(2)、users(3)、roles(4)urls6.第一个案例:①项目总体图:添加...

    一、初识shiro

    1.shiro简介:

    官网
    在这里插入图片描述
    Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解API,你可以快速、轻松地获取任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序

    2.框架图说明:

    官网架构说明

    3.从外部查看shiro框架

    在这里插入图片描述
    应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject

    api 说明
    Subject 主体,代表当前‘用户’ 。这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委派给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者
    Shiro SecurityManager 安全管理器;即所有与安全有关的操作都会与SecurityManager交互且它管理者所有Subject;可以看出它是Shiro的核心,它负责与后面介绍的其它组件进行交互,可以把它看成DispathcherServlet前端控制器
    Realm 域,Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

    4.内部结构框架

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

    5.常见单词说明

    在这里插入图片描述

    6.Shiro中的shiro.ini说明:

    在这里插入图片描述

    (1) 、main

    提供了对根对象securityManager及其依赖对象的配置

    #创建对象
    securityManager=org.apache.shiro.mgt.DefaultSecurityManager 
    
    

    其构造器必须是public空参构造器,通过反射创建相应的实例。
    1.对象名=全限定类名 相当于调用public无参构造器创建对象
    2.对象名.属性名=值 相当于调用于setter方法设置常量值
    3.对象名.属性名=$对象引用 相当于调用setter方法设置对象引用

    (2)、users

    提供了对用户/密码及其角色的配置,用户名=密码,角色1,角色2
    username=password,role1,role2
      例如:配置用户名/密码及其角色,格式:“用户名=密码,角色1,角色2”,角色部分可省略。如:

    [users] 
    zhang=123,role1,role2 
    wang=123 
    

    (3)、roles

    提供了角色及权限之间关系的配置,角色=权限1,权限2 role1 = permission1 , permission2
      例如:配置角色及权限之间的关系,格式:“角色=权限1,权限2”;如:

    [roles] 
    role1=user:create,user:update 
    role2=*  
    
    

    (4)、urls

    用于web,提供了对web url拦截相关的配置,url=拦截器[参数],拦截器

    /index.html = anon 
    /admin/** = authc, roles[admin],perms["permission1"]
    
    

    7.第一个案例:

    ①项目总体图:

    在这里插入图片描述

    添加依赖:

    <dependencies>
      <dependency>
    	<groupId>org.apache.shiro</groupId>
    	<artifactId>shiro-core</artifactId>
    	<version>1.1.0</version>
    </dependency>
    <dependency>
    	<groupId>org.slf4j</groupId>
    	<artifactId>slf4j-simple</artifactId>
    	<version>1.6.1</version>
    	<scope>test</scope>
    </dependency>
    
    <dependency>
    	<groupId>junit</groupId>
    	<artifactId>junit</artifactId>
    	<version>4.12</version>
    	<scope>test</scope>
    </dependency>
    <dependency>
    	<groupId>commons-logging</groupId>
    	<artifactId>commons-logging</artifactId>
    	<version>1.2</version>
    </dependency>
    </dependencies>
    

    ②添加shiro.ini文件

    [users]
    root=123456
    # 账号为root 密码是123456
    

    ③认证操作:

    package com.sxt.test;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.IncorrectCredentialsException;
    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.subject.Subject;
    import org.apache.shiro.util.Factory;
    
    /**
     * @author Administrator
     *shiro的第一个入门案例
     */
    public class HelloTest {
    	public static void main(String[] args) {
    		//1.加载配置文件获取Factory对象
    		Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro.ini");
    		//2.获取SecurityManager对象
    		SecurityManager securityManager =factory.getInstance();
    		//3.将SecurityManager添加到系统
    		SecurityUtils.setSecurityManager(securityManager);
    		//4.通过SecurityManager获取Subject对象
    		Subject subject=SecurityUtils.getSubject();
    		//账号密码是客户端提交的数据
    		AuthenticationToken token=new UsernamePasswordToken("root","123456");
    		//5.实现认证操作
    		try{
    			subject.login(token);
    			System.out.println("认证成功");
    		}catch(UnknownAccountException e){
    			System.out.println("账号输入错误。,,,");
    		}catch (IncorrectCredentialsException e) {
    			System.out.println("密码输入错误。。。");
    		}
    		
    	}
    }
    
    

    ④实现:

    在这里插入图片描述

    二、自定义Realm

    通过上面我们发现仅仅将数据信息定义在ini文件中我们实际开发环境有很大不兼容,所以我们希望能够自己定义Realm。

    1.自定义Realm的实现

    (1)、创建自定义Realmjava类

    创建一个java文件继承AuthorizingRealm类,重写两个抽象方法:

    package com.sxt.realm;
    
    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.authc.UsernamePasswordToken;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    
    /**
     * @author Administrator
     * 定义的Realm
     */
    public class SecurityRealm  extends AuthorizingRealm{
    
    	/**
    	 * 认证的方法
    	 * 就是我们在测试代码中 定义的UserPassWoldToken对象
    	 * 有我们保存的需要验证的账号密码信息
    	 */
    
    	@Override
    	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    		UsernamePasswordToken t=(UsernamePasswordToken) token;
    		//获取登录的账号
    		String username=t.getUsername();
    		System.out.println("登录的账号:"+username);
    		//通过jdbc去数据库中查询该账号对应的记录
    		if(!"root".equals(username)){
    			//账号不存在
    			return null;
    		}
    		//数据库中查询的密码是123456
    		String password="123456";
    		//身份信息(可以是账号也可以是对象) 密码 realmName(自定义)
    		return new SimpleAuthenticationInfo(username,password,"tang");
    	}
    	
    	/**
    	 * 授权方法
    	 */
    	@Override
    	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
    		// TODO Auto-generated method stub
    		return null;
    	}
    }
    
    
    方法名 说明
    doGetAuthentictionInfo 完成账号认证的方法
    doGetAuthorizationInfo 完成用户授权的方法

    (2)、配置ini.xml文件:

    [main]
    #自定义 realm
    customRealm=com.sxt.realm.SecurityRealm
    #将realm设置到securityManager
    securityManager.realms=$customRealm
    

    (3)、测试(代码跟上面的一样)

    package com.sxt.test;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.IncorrectCredentialsException;
    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.subject.Subject;
    import org.apache.shiro.util.Factory;
    
    /**
     * @author Administrator
     *shiro的第一个入门案例
     */
    public class HelloTest {
    	public static void main(String[] args) {
    		//1.加载配置文件获取Factory对象
    		Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro.ini");
    		//2.获取SecurityManager对象
    		SecurityManager securityManager =factory.getInstance();
    		//3.将SecurityManager添加到系统
    		SecurityUtils.setSecurityManager(securityManager);
    		//4.通过SecurityManager获取Subject对象
    		Subject subject=SecurityUtils.getSubject();
    		//账号密码是客户端提交的数据
    		AuthenticationToken token=new UsernamePasswordToken("root","123456");
    		//5.实现认证操作
    		try{
    			subject.login(token);
    			System.out.println("认证成功");
    		}catch(UnknownAccountException e){
    			System.out.println("账号输入错误。,,,");
    		}catch (IncorrectCredentialsException e) {
    			System.out.println("密码输入错误。。。");
    		}
    		
    	}
    }
    
    

    在这里插入图片描述

    2.原理分析

    为什么要继承AuthorizingRealm?

    通过分析认证的流程,我们发现在认证的过程中核心代码是:
    在这里插入图片描述
    核心方法是doGetAuthenticationInfo(token)
    在Realm的结构中
    在这里插入图片描述
    AuthorizingRealm和AuthenticatingRealm都提供的有doGetAuthenticationInfo(token)的抽象方法。但是AuthenticatingRealm中要重写的抽象方法太多而AuthorizingRealm只需要重写两个方法,且这两个方法都是我们需要使用的。故选择继承AuthorizingRealm

    自定义的Realm什么时候被调用的?

    在这里插入图片描述

    密码验证什么时候执行的?

    注意:自定义Realm中只完成了账号的认证。密码认证还是在AuthenticatingRealm中完成的,只是我们在自定义Realm中完成了密码的设置。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    三、shiro-加密

    加密,是以某种特殊的算法改变原有的信息数据,使得未授权的用户即使获得了已加密的信息,但因不知解密的方法,任然无法了解信息的内容

    1、概念:

    数据加密的基本过程就是对原来为明文的文件或数据按照某种算法进行处理,使其成为不可读的一段代码,通常称为“密文”,使其只能在输入相应的密匙之后才能显示出本来内容,通过这样的途径来达到保护数据不被非法人窃取、阅读的目的。该过程的逆过程为解密,即将该编码信息转换为其原来数据的过程。

    2、加密分类:

    (1)、对称加密

    双方使用的同一个密匙,既可以加密又可以解密,这种加密方法称为对称加密,也称单密匙加密。

    (2)、非对称加密

    一对密匙由公钥和私钥组成(可以使用很多对密匙)。私钥解密公钥加密数据,公钥解密私钥加密数据(私钥公钥可以互相加密解密)。

    3、加密算法分类

    (1)、单项加密

    单项加密是不可逆的,也就是只能加密,不能解密。通常用来传输类型用户名和密码,直接将加密后的数据提交到后台,因为后台不需要知道用户名和密码,可以直接将接收到的加密后的数据存储到数据库

    (2)、双向加密

    通常分为对称性加密算法和非对称性加密算法,对于对称性加密算法,信息接收双方都需事先知道密匙和加解密算法且其密匙是相同的,之后便是对数据进行 加解密了。非对称算法与之不同,发送双方A,B事先均生成一堆密匙,然后A将自己的公有密匙发送给B,B将自己的公有密匙发送给A,如果A要给B发送消 息,则先需要用B的公有密匙进行消息加密,然后发送给B端,此时B端再用自己的私有密匙进行消息解密,B向A发送消息时为同样的道理。

    4、常见的算法

    在这里插入图片描述

    MD5的使用:

    package com.sxt.test;
    
    import org.apache.shiro.crypto.hash.Md5Hash;
    
    public class Md5HashTest {
    	public static void main(String[] args) {
    		// 对单个信息加密
    		Md5Hash md5 = new Md5Hash("123456");
    		System.out.println(md5.toString());
    		// 加密添加盐值 增大解密难度
    		md5 = new Md5Hash("123456","aaa");
    		System.out.println(md5.toString());
    		// 加密添加盐值 增大解密难度  迭代1024次
    		md5 = new Md5Hash("123456","aaa",1024);
    		System.out.println(md5);
    	
    	}
    }
    
    

    输出的结果:
    在这里插入图片描述

    盐值的作用:

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

    shiro中使用MD5加密

    1.认证方法中修改

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    	// 获取账号信息
    	String principal = (String) token.getPrincipal();
    	// 正常逻辑此处应该根据账号去数据库中查询,此处我们默认账号为 root 密码123456
    	// 验证账号
    	if(!"root".equals(principal)){
    		// 账号错误
    		return null;
    	}
    	//String pwd = "123456";
    	// 12345 根据 盐值 aaa 加密获取的密文
    	//88316675d7882e3fdbe066000273842c  1次迭代的密文
    	//a7cf41c6537065fe724cc9980f8b5635  2次迭代的密文
    	String pwd = "88316675d7882e3fdbe066000273842c";
    	// 验证密码
    	AuthenticationInfo info = new SimpleAuthenticationInfo(
    			principal, pwd,new SimpleByteSource("aaa"),"myrealm");
    	return info;
    }
    
    

    在这里插入图片描述

    2.ini.xml文件的修改:

    [main]
    #定义凭证匹配器
    credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
    #散列算法
    credentialsMatcher.hashAlgorithmName=md5
    #散列次数
    credentialsMatcher.hashIterations=1
    
    #将凭证匹配器设置到realm
    customRealm=com.dpb.realm.MyRealm
    customRealm.credentialsMatcher=$credentialsMatcher
    securityManager.realms=$customRealm
    
    

    3.测试:

    @Test
    public void test() {
    	// 1.获取SecurityManager工厂对象
    	Factory<SecurityManager> factory = 
    			new IniSecurityManagerFactory("classpath:shiro.ini");
    	// 2.通过Factory对象获取SecurityManager对象
    	SecurityManager securityManager = factory.getInstance();
    	// 3.将SecurityManager对象添加到当前运行环境中
    	SecurityUtils.setSecurityManager(securityManager);
    	
    	// 4.获取Subject对象
    	Subject subject = SecurityUtils.getSubject();
    	AuthenticationToken token = new UsernamePasswordToken("root", "123456");
    	// 登录操作
    	try {
    		subject.login(token);
    	} catch (UnknownAccountException e) {
    		System.out.println("账号出错...");
    	} catch(IncorrectCredentialsException e){
    		System.out.println("密码出错...");
    	}
    	// 获取登录的状态
    	System.out.println(subject.isAuthenticated());
    }
    
    

    4.迭代次数:

    在这里插入图片描述
    A
    完成。

    展开全文
  • shiro

    千次阅读 2020-05-21 13:43:02
    shiro学习笔记 摘要 springboot-shiro Springboot + shiro权限管理。这或许是流程最详细、代码最干净、配置最简单的shiro上手项目了。 一、Shiro简介 Apache Shiro 是 Java 的一个安全(权限)框架。 Shiro 可以...

    shiro学习笔记

    摘要

    springboot-shiro
    Springboot + shiro权限管理。这或许是流程最详细、代码最干净、配置最简单的shiro上手项目了。

    一、Shiro简介

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

    二、功能简介

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

    三、Shiro 架构

    • Subject:应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外API 核心就是 Subject。Subject 代表了当前“用户”,
      这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;与 Subject 的所有交互都会委
      托给 SecurityManager;Subject 其实是一个门面,SecurityManager 才是实际的执行者;
    • SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager 交互;且其管理着所有 Subject;可以看出它
      是 Shiro的核心,它负责与 Shiro 的其他组件进行交互,它相当于 SpringMVC 中DispatcherServlet 的角色
    • Realm:Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说SecurityManager 要验证用户身份,那么它需要从 Realm
      获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource

    四、redis安装

    https://redis.io/download

    复制这段内容后打开百度网盘手机App,操作更方便哦
    链接:https://pan.baidu.com/s/1NwylPg9IsR-WCULiUCEy4A 提取码:XR3L 
    

    双击,一路next,选择安装文件夹,并勾选配置path,切记,不然你每次都只能在安装目录下启动命令redis-server

    telnet 127.0.0.1 6379
    开启telnet

    • 在控制面板中,我们选择小图标,这样可以显示全部内容,不然会显示不全找不到需要的功能,看到全部设置后我们选择“程序和功能”。
    • 进入程序和功能模块,我们在左边需要选择“打开或关闭windows功能”,点击windows功能后弹出对话框,需要
    • 打开功能的前面打钩,把telnet客户端前的复选框勾上。

    set name redis
    get name

    redis 密码默认设置为空,没有密码

    redis的DB默认是0
    redis> SET number 0         # 默认使用 0 号数据库
    OK
    
    redis> SELECT 1                # 使用 1 号数据库
    OK
    
    redis[1]> GET number        # 已经切换到 1 号数据库,注意 Redis 现在的命令提示符多了个 [1]
    (nil)
    
    redis[1]> SET number 1
    OK
    
    redis[1]> GET number
    "1"
    
    redis[1]> SELECT 3             # 再切换到 3 号数据库
    OK
    
    redis[3]>                      # 提示符从 [1] 改变成了 [3]
    

    配置文件:

    requirepass foobared

    requirepass qwe!@#123

    1、replica-serve-stale-data   yes   # 当从库同主库失去连接或者复制正在进行,从机库有两种运行方式:
    
    1) 如果 replica serve stale data 设置为 yes( 默认设置 )),从库会继续响应客户端的 读 请求。
    
    2) 如果 replicaserve stale data 设置为 no ,除 去指定的命令之外的任何请求都会返回一个错误 "SYNC with master in progress" 。
    
    2、replica-read-only yes # 是否设置从库只读
    
    3、repl-diskless-sync no 是否使用 socket 方式复制数据, 目前 redis 复制提供两种方式, disk 和 socket 如果新的 slave 连上来或者重连的 slave 无法部分同步,就会执行全量同步, master 会生成 rdb 文件,有2 种方式:
    
    1)disk 方式是 master 创建一个新的进程把 rdb 文件保存到磁盘,再把磁盘上的 rdb 文件传递给 slave socket 是 master 创建一个新的进程,
    直接把 rdb 文件以 socket 的方式发给 slave disk 方式的时候,当一个 rdb 保存的过程中,多个 slave 都能共享这个 rdb 文件。
    
    2)socket 的方式就是 一个个 slave顺序复制, 只有在磁盘速度缓慢但是 网络相对较 快的情况下才使用 socket 方式,否则使用默认的disk方式。
    
    4、repl-diskless-sync-delay 30 #diskless 复制的延迟时间, 设置 0为关闭 一旦复制开始还没有结束之前,master 节点不会再接收新 slave 的复制请求, 直到下一次开始。
    
    5、repl-ping-slave-period 10   #slave 根据 master 指定 的时间进行周期性的 PING 监测
    
    6、repl-timeout 60       复制链接超时时间,需要大于 repl ping slave period 否则会 经常 报超时
    
    7、repl-disable-tcp-nodelay no     在 socket 模式下是否在slave 套接字发送 SYNC之后禁用TCP_NODELAY
    
    如果你选择“yes Redis 将使用更少的 TCP 包和带宽来向 slaves 发送数据。但是这 将使数据传输到 slave上有延迟, Linux 内核的默认配置会达到 40毫秒,
    
    如果你选择了 "no" 数据传输到 salve 的延迟将会减少但要使用更多的带宽。
    
    8、repl-backlog-size  1mb   复制缓冲区大小, 只有在 slave 连接之后 才 分配内存 。
    
    9、repl-backlog-ttl 3600 多次时间 master 没有 slave 连接,就清空 backlog 缓冲区 。
    
    10、replica-priority 100 当 master 不可用,Sentinel 会根据 slave 的优先级选举一个 master 。最低的优先级的 slave ,当选 master 。而配置成 0,永远不会被选举。
    
    11、requirepass foobared    设置 redis 连接密码,配置了redis,必须设置密码,防止被入侵之后被黑客搞破坏。
    
     
    
    12、rename-command 重命名 一些高 危命令
    
    13、maxclients 10000  最大连接客户端
    
    14、maxmemory  最大内存 单位为 bytes 字节 8G 内存 的 计算方式 8 G ))*1024 ( MB)*1024 ( KB)*1024 Kbyte,用bc命令可以计算;
    
    需要注意的是 slave 的输出缓冲区是不计算在 maxmemory 内 ,此最大内存应该最大占计算机内存的一半,留一部分内存用来做快照使用。
    
      
    
      
    
    15、appendonly no #是否开启 AOF 日志 记录 默认 redis使用的是 rdb 方式持久化,这种方式在许多应用中已经足够用了。但是 redis 如果中途宕机,会导致可能有几分钟的数据丢失,
    
    根据 save 来策略进行持久化,Append Only File 是另一种持久化方式,可以提供更好的持久化特性。 Redis 会把每次写入的数据在接收后都写入 appendonly.aof 文件,
    每次启动时 Redis 都会先把这个文件的数据读入内存里,先忽略 RDB 文件。
    
    16、appendfilename  "appendonly.aof" #AOF文件名
    
    17、appendfsync everysec  #aof 持久化策略的配置 no 表示不执行 fsync 由操作系统保证数据同步到磁盘 ,always 表示每次写入都执行 fsync ,以保证数据同步到磁
    盘 ,everysec 表示每秒执行一次 fsync ,
    可能会导致丢失这 1s 数据。
    
    18、no-appendfsync-on-rewrite no(推荐为yes) 在 aof rewrite 期间 是否对 aof 新记录的 append 暂缓使用文件同步策略 主要考虑磁盘 IO 开支和请求阻塞时间。
    默认为 no, 表示不暂缓新的 aof 记录仍然会被立即同步
    Linux 的默认fsync策略是30 秒,如果为 yes 可能丢失 30 秒数据 ,但由于yes性能较好,而且会避免出现阻塞, 因此比较推荐 。
    
    19、auto-aof-rewrite-percentage 100 # 当 Aof log增长超过指定百分比例时,重写 logfile设置为0表示不自动重写 Aof 日志,重写是为了使 aof 体积保持最小,
    而确保保存最完整的数据。
    
    20、auto-aof-rewrite -min size 64mb # 触发 aof rewrite 的最小文件大小
    
    21、aof-load-truncated yes 是否加载 由于 其他原因 导致 的 末尾 异常 的 AOF文件主进程被 kill/ 断电等
    
     当文件存在问题时,可以针对不同的文件进行修复操作:可以修复aof和rdb后缀的文件。
    
     打开此功能,就会在/apps/redis/data/目录下生成appendonly.aof后缀的文件。
    
     
    
     
    
    22、aof-use-rdb-preamble yes #r edis4.0 新增 RDB AOF 混合持久化格式,在开启了这个功能之后, AOF 重写产生的文件将同时包含 RDB 格式的内容和 AOF 格式的内容,
    其中 RDB 格式的内容用于记录已有的数
    据,而 AOF 格式的内存则用于记录最近发生了变化的数据,这样 Redis 就可以同时兼有 RDB 持久化和AOF 持久化的优点(既能够快速地生成重写文件,也能够在出现问题
    时,快速地载入数据)。
    
    23、luatime-limit 5000 #lua 脚本 的 最大 执行时间单位为毫秒
    
    24、cluster-enabled yes 是否开启集群模式,默认是单机模式
    
    25、cluster-config-file-nodes 6379.conf 由 node节点自动生成的集群配置 文件
    
    26、cluster-node-timeout 15000 集群中node节点连接 超时时间
    
    27、cluster-replica-validity-factor 10 在 执行故障转移的时候可能有些节点和 master 断开一段时间数据比较旧 这些 节点就 不适用于选举为 master 超过这个时间的就不
    会被进行故障转移
    
    28、cluster-migration-barrier 1 一个主节点拥有的至少正常工作的从节点 即如果主节点的 slave 节点故障后, 会 将多余的从节点 分配 到当前主节点 成为 其 新的 从节点。
    
    29、cluster-require-full-coverage no 集群 槽位覆盖 如果 一个 主库宕机 且 没有备库就会出现集群槽位不全 那么 yes 情况下 redis 集群 槽位 验证不全就不再对外提供服务,
    而 no 则可以继续使用但是会出现查询数据查不到的情况 (因为有数据丢失) 。
    
    #Slow log 是 Redis 用来记录查询执行时间的日志系统 slow log 保存在内存里面,读写速度非常快,因此你可以放心地使用它,不必担心因为开启slow log而损害 Redis 的速度。
    
    30、slowlog-log-slower than 10000 以微秒 为单位 的 慢日志记录, 为 负数会禁用慢日志,为0会记录 每个命令操作。
    
    31、slowlog-max-len 128 # 记录多少条慢日志 保存在 队列,超出后会删除最早的,以此滚动删除
    
    测试效果:
    
    1
    2
    3
    4
    5
    6
    7
    8
    127.0.0.1:6379> slowlog len
    (integer) 14
    127.0.0.1:6379> slowlog get
    1) 1) (integer) 14
    2) (integer) 1544690617
    3) (integer) 4
    4) 1) "slowlog"
    127.0.0.1:6379> SLOWLOG reset
    

    redis可视化工具:
    RedisDesktopManager
    TTL KEY_NAME
    当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 。 否则,以秒为单位,返回 key 的剩余生存时间。
    在这里插入图片描述

    五、shiro项目学习

    https://gitee.com/liyunfengfengfeng/shiro

    修改MySQL/redis连接配置
    pom依赖

    <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>${shiro.spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.crazycake</groupId>
                <artifactId>shiro-redis</artifactId>
                <version>${shiro.redis.version}</version>
                <exclusions>
                    <exclusion>
                        <artifactId>shiro-core</artifactId>
                        <groupId>org.apache.shiro</groupId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>net.mingsoft</groupId>
                <artifactId>shiro-freemarker-tags</artifactId>
                <version>${shiro.freemarker.tags.version}</version>
                <exclusions>
                    <exclusion>
                        <artifactId>fastjson</artifactId>
                        <groupId>com.alibaba</groupId>
                    </exclusion>
                    <exclusion>
                        <artifactId>log4j</artifactId>
                        <groupId>log4j</groupId>
                    </exclusion>
                    <exclusion>
                        <artifactId>shiro-core</artifactId>
                        <groupId>org.apache.shiro</groupId>
                    </exclusion>
                </exclusions>
            </dependency>
    

    @RequiresPermissions
    例如: @RequiresPermissions({“file:read”, “write:aFile.txt”} )
    void someMethod();
    要求subject中必须同时含有file:read和write:aFile.txt的权限才能执行方法someMethod()。否则抛出异常AuthorizationException。

    shiro.ini配置文件:
    在这里插入图片描述

    展开全文
  • 这篇文章我们来学习如何使用Spring Boot集成Apache Shiro。安全应该是互联网公司的一道生命线,几乎任何的公司都会涉及到这方面的需求。在Java领域一般有Spring Security、Apache Shiro等安全框架,但是由于Spring ...

    这篇文章我们来学习如何使用Spring Boot集成Apache Shiro。安全应该是互联网公司的一道生命线,几乎任何的公司都会涉及到这方面的需求。在Java领域一般有Spring Security、Apache Shiro等安全框架,但是由于Spring Security过于庞大和复杂,大多数公司会选择Apache Shiro来使用,这篇文章会先介绍一下Apache Shiro,在结合Spring Boot给出使用案例。

    Apache Shiro

    What is Apache Shiro?

    Apache Shiro是一个功能强大、灵活的,开源的安全框架。它可以干净利落地处理身份验证、授权、企业会话管理和加密。

    Apache Shiro的首要目标是易于使用和理解。安全通常很复杂,甚至让人感到很痛苦,但是Shiro却不是这样子的。一个好的安全框架应该屏蔽复杂性,向外暴露简单、直观的API,来简化开发人员实现应用程序安全所花费的时间和精力。

    Shiro能做什么呢?

    • 验证用户身份
    • 用户访问权限控制,比如:1、判断用户是否分配了一定的安全角色。2、判断用户是否被授予完成某个操作的权限
    • 在非 web 或 EJB 容器的环境下可以任意使用Session API
    • 可以响应认证、访问控制,或者 Session 生命周期中发生的事件
    • 可将一个或以上用户安全数据源数据组合成一个复合的用户 “view”(视图)
    • 支持单点登录(SSO)功能
    • 支持提供“Remember Me”服务,获取用户关联信息而无需登录

    等等——都集成到一个有凝聚力的易于使用的API。

    Shiro 致力在所有应用环境下实现上述功能,小到命令行应用程序,大到企业应用中,而且不需要借助第三方框架、容器、应用服务器等。当然 Shiro 的目的是尽量的融入到这样的应用环境中去,但也可以在它们之外的任何环境下开箱即用。

    Apache Shiro Features 特性

    Apache Shiro是一个全面的、蕴含丰富功能的安全框架。下图为描述Shiro功能的框架图:

    {:.center}

    Authentication(认证), Authorization(授权), Session Management(会话管理), Cryptography(加密)被 Shiro 框架的开发团队称之为应用安全的四大基石。那么就让我们来看看它们吧:

    • **Authentication(认证):**用户身份识别,通常被称为用户“登录”
    • **Authorization(授权):**访问控制。比如某个用户是否具有某个操作的使用权限。
    • **Session Management(会话管理):**特定于用户的会话管理,甚至在非web 或 EJB 应用程序。
    • **Cryptography(加密):**在对数据源使用加密算法加密的同时,保证易于使用。

    还有其他的功能来支持和加强这些不同应用环境下安全领域的关注点。特别是对以下的功能支持:

    • Web支持:Shiro 提供的 web 支持 api ,可以很轻松的保护 web 应用程序的安全。
    • 缓存:缓存是 Apache Shiro 保证安全操作快速、高效的重要手段。
    • 并发:Apache Shiro 支持多线程应用程序的并发特性。
    • 测试:支持单元测试和集成测试,确保代码和预想的一样安全。
    • “Run As”:这个功能允许用户假设另一个用户的身份(在许可的前提下)。
    • “Remember Me”:跨 session 记录用户的身份,只有在强制需要时才需要登录。

    注意: Shiro不会去维护用户、维护权限,这些需要我们自己去设计/提供,然后通过相应的接口注入给Shiro

    High-Level Overview 高级概述

    在概念层,Shiro 架构包含三个主要的理念:Subject,SecurityManager和 Realm。下面的图展示了这些组件如何相互作用,我们将在下面依次对其进行描述。

    {:.center}

    • Subject:当前用户,Subject 可以是一个人,但也可以是第三方服务、守护进程帐户、时钟守护任务或者其它–当前和软件交互的任何事件。
    • SecurityManager:管理所有Subject,SecurityManager 是 Shiro 架构的核心,配合内部安全组件共同组成安全伞。
    • Realms:用于进行权限信息的验证,我们自己实现。Realm 本质上是一个特定的安全 DAO:它封装与数据源连接的细节,得到Shiro 所需的相关的数据。在配置 Shiro 的时候,你必须指定至少一个Realm 来实现认证(authentication)和/或授权(authorization)。

    我们需要实现Realms的Authentication 和 Authorization。其中 Authentication 是用来验证用户身份,Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。

    快速上手

    基础信息

    pom包依赖

    <dependencies>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-data-jpa</artifactId>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-thymeleaf</artifactId>
    		</dependency>
    		<dependency>
    			<groupId>net.sourceforge.nekohtml</groupId>
    			<artifactId>nekohtml</artifactId>
    			<version>1.9.22</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-web</artifactId>
    		</dependency>
    		<dependency>
    			<groupId>org.apache.shiro</groupId>
    			<artifactId>shiro-spring</artifactId>
    			<version>1.4.0</version>
    		</dependency>
    		<dependency>
    			<groupId>mysql</groupId>
    			<artifactId>mysql-connector-java</artifactId>
    			<scope>runtime</scope>
    		</dependency>
    	</dependencies>
    

    重点是 shiro-spring包

    配置文件

    spring:
        datasource:
          url: jdbc:mysql://localhost:3306/test
          username: root
          password: root
          driver-class-name: com.mysql.jdbc.Driver
    
        jpa:
          database: mysql
          show-sql: true
          hibernate:
            ddl-auto: update
            naming:
              strategy: org.hibernate.cfg.DefaultComponentSafeNamingStrategy
          properties:
             hibernate:
                dialect: org.hibernate.dialect.MySQL5Dialect
    
        thymeleaf:
           cache: false
           mode: LEGACYHTML5
    

    thymeleaf的配置是为了去掉html的校验

    页面

    我们新建了六个页面用来测试:

    • index.html :首页
    • login.html :登录页
    • userInfo.html : 用户信息页面
    • userInfoAdd.html :添加用户页面
    • userInfoDel.html :删除用户页面
    • 403.html : 没有权限的页面

    除过登录页面其它都很简单,大概如下:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>index</h1>
    </body>
    </html>
    

    RBAC

    RBAC 是基于角色的访问控制(Role-Based Access Control )在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。

    采用jpa技术来自动生成基础表格,对应的entity如下:

    用户信息

    @Entity
    public class UserInfo implements Serializable {
        @Id
        @GeneratedValue
        private Integer uid;
        @Column(unique =true)
        private String username;//帐号
        private String name;//名称(昵称或者真实姓名,不同系统不同定义)
        private String password; //密码;
        private String salt;//加密密码的盐
        private byte state;//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定.
        @ManyToMany(fetch= FetchType.EAGER)//立即从数据库中进行加载数据;
        @JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns ={@JoinColumn(name = "roleId") })
        private List<SysRole> roleList;// 一个用户具有多个角色
    
        // 省略 get set 方法
     }
    

    角色信息

    @Entity
    public class SysRole {
        @Id@GeneratedValue
        private Integer id; // 编号
        private String role; // 角色标识程序中判断使用,如"admin",这个是唯一的:
        private String description; // 角色描述,UI界面显示使用
        private Boolean available = Boolean.FALSE; // 是否可用,如果不可用将不会添加给用户
    
        //角色 -- 权限关系:多对多关系;
        @ManyToMany(fetch= FetchType.EAGER)
        @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")})
        private List<SysPermission> permissions;
    
        // 用户 - 角色关系定义;
        @ManyToMany
        @JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="uid")})
        private List<UserInfo> userInfos;// 一个角色对应多个用户
    
        // 省略 get set 方法
     }
    

    权限信息

    @Entity
    public class SysPermission implements Serializable {
        @Id@GeneratedValue
        private Integer id;//主键.
        private String name;//名称.
        @Column(columnDefinition="enum('menu','button')")
        private String resourceType;//资源类型,[menu|button]
        private String url;//资源路径.
        private String permission; //权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view
        private Long parentId; //父编号
        private String parentIds; //父编号列表
        private Boolean available = Boolean.FALSE;
        @ManyToMany
        @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")})
        private List<SysRole> roles;
    
        // 省略 get set 方法
     }
    

    根据以上的代码会自动生成user_info(用户信息表)、sys_role(角色表)、sys_permission(权限表)、sys_user_role(用户角色表)、sys_role_permission(角色权限表)这五张表,为了方便测试我们给这五张表插入一些初始化数据:

    INSERT INTO `user_info` (`uid`,`username`,`name`,`password`,`salt`,`state`) VALUES ('1', 'admin', '管理员', 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', 0);
    INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (1,0,'用户管理',0,'0/','userInfo:view','menu','userInfo/userList');
    INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (2,0,'用户添加',1,'0/1','userInfo:add','button','userInfo/userAdd');
    INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (3,0,'用户删除',1,'0/1','userInfo:del','button','userInfo/userDel');
    INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (1,0,'管理员','admin');
    INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (2,0,'VIP会员','vip');
    INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (3,1,'test','test');
    INSERT INTO `sys_role_permission` VALUES ('1', '1');
    INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (1,1);
    INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (2,1);
    INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (3,2);
    INSERT INTO `sys_user_role` (`role_id`,`uid`) VALUES (1,1);
    

    Shiro 配置

    首先要配置的是ShiroConfig类,Apache Shiro 核心通过 Filter 来实现,就好像SpringMvc 通过DispachServlet 来主控制一样。
    既然是使用 Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。

    ShiroConfig

    @Configuration
    public class ShiroConfig {
    	@Bean
    	public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
    		System.out.println("ShiroConfiguration.shirFilter()");
    		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    		shiroFilterFactoryBean.setSecurityManager(securityManager);
    		//拦截器.
    		Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
    		// 配置不会被拦截的链接 顺序判断
    		filterChainDefinitionMap.put("/static/**", "anon");
    		//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
    		filterChainDefinitionMap.put("/logout", "logout");
    		//<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
    		//<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
    		filterChainDefinitionMap.put("/**", "authc");
    		// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
    		shiroFilterFactoryBean.setLoginUrl("/login");
    		// 登录成功后要跳转的链接
    		shiroFilterFactoryBean.setSuccessUrl("/index");
    
    		//未授权界面;
    		shiroFilterFactoryBean.setUnauthorizedUrl("/403");
    		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    		return shiroFilterFactoryBean;
    	}
    
    	@Bean
    	public MyShiroRealm myShiroRealm(){
    		MyShiroRealm myShiroRealm = new MyShiroRealm();
    		return myShiroRealm;
    	}
    
    
    	@Bean
    	public SecurityManager securityManager(){
    		DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
    		securityManager.setRealm(myShiroRealm());
    		return securityManager;
    	}
    }
    

    Filter Chain定义说明:

    • 1、一个URL可以配置多个Filter,使用逗号分隔
    • 2、当设置多个过滤器时,全部验证通过,才视为通过
    • 3、部分过滤器可指定参数,如perms,roles

    Shiro内置的FilterChain

    Filter Name Class
    anon org.apache.shiro.web.filter.authc.AnonymousFilter
    authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
    authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
    perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
    port org.apache.shiro.web.filter.authz.PortFilter
    rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
    roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
    ssl org.apache.shiro.web.filter.authz.SslFilter
    user org.apache.shiro.web.filter.authc.UserFilter
    • anon:所有url都都可以匿名访问
    • authc: 需要认证才能进行访问
    • user:配置记住我或认证通过可以访问

    登录认证实现

    在认证、授权内部实现机制中都有提到,最终处理都将交给Real进行处理。因为在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的DAO.
    Shiro的认证过程最终会交由Realm执行,这时会调用Realm的getAuthenticationInfo(token)方法。

    该方法主要执行以下操作:

    • 1、检查提交的进行认证的令牌信息
    • 2、根据令牌信息从数据源(通常为数据库)中获取用户信息
    • 3、对用户信息进行匹配验证。
    • 4、验证通过将返回一个封装了用户信息的AuthenticationInfo实例。
    • 5、验证失败则抛出AuthenticationException异常信息。

    而在我们的应用程序中要做的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo(),重写获取用户信息的方法。

    doGetAuthenticationInfo的重写

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
        //获取用户的输入的账号.
        String username = (String)token.getPrincipal();
        System.out.println(token.getCredentials());
        //通过username从数据库中查找 User对象,如果找到,没找到.
        //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
        UserInfo userInfo = userInfoService.findByUsername(username);
        System.out.println("----->>userInfo="+userInfo);
        if(userInfo == null){
            return null;
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                userInfo, //用户名
                userInfo.getPassword(), //密码
                ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt
                getName()  //realm name
        );
        return authenticationInfo;
    }
    

    链接权限的实现

    shiro的权限授权是通过继承AuthorizingRealm抽象类,重载doGetAuthorizationInfo();当访问到页面的时候,链接配置了相应的权限或者shiro标签才会执行此方法否则不会执行,所以如果只是简单的身份认证没有权限的控制的话,那么这个方法可以不进行实现,直接返回null即可。在这个方法中主要是使用类:SimpleAuthorizationInfo进行角色的添加和权限的添加。

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        UserInfo userInfo  = (UserInfo)principals.getPrimaryPrincipal();
        for(SysRole role:userInfo.getRoleList()){
            authorizationInfo.addRole(role.getRole());
            for(SysPermission p:role.getPermissions()){
                authorizationInfo.addStringPermission(p.getPermission());
            }
        }
        return authorizationInfo;
    }
    

    当然也可以添加set集合:roles是从数据库查询的当前用户的角色,stringPermissions是从数据库查询的当前用户对应的权限

    authorizationInfo.setRoles(roles);
    authorizationInfo.setStringPermissions(stringPermissions);
    

    就是说如果在shiro配置文件中添加了filterChainDefinitionMap.put(“/add”, “perms[权限添加]”);就说明访问/add这个链接必须要有“权限添加”这个权限才可以访问,如果在shiro配置文件中添加了filterChainDefinitionMap.put(“/add”, “roles[100002],perms[权限添加]”);就说明访问/add这个链接必须要有“权限添加”这个权限和具有“100002”这个角色才可以访问。

    登录实现

    登录过程其实只是处理异常的相关信息,具体的登录验证交给shiro来处理

    @RequestMapping("/login")
    public String login(HttpServletRequest request, Map<String, Object> map) throws Exception{
        System.out.println("HomeController.login()");
        // 登录失败从request中获取shiro处理的异常信息。
        // shiroLoginFailure:就是shiro异常类的全类名.
        String exception = (String) request.getAttribute("shiroLoginFailure");
        System.out.println("exception=" + exception);
        String msg = "";
        if (exception != null) {
            if (UnknownAccountException.class.getName().equals(exception)) {
                System.out.println("UnknownAccountException -- > 账号不存在:");
                msg = "UnknownAccountException -- > 账号不存在:";
            } else if (IncorrectCredentialsException.class.getName().equals(exception)) {
                System.out.println("IncorrectCredentialsException -- > 密码不正确:");
                msg = "IncorrectCredentialsException -- > 密码不正确:";
            } else if ("kaptchaValidateFailed".equals(exception)) {
                System.out.println("kaptchaValidateFailed -- > 验证码错误");
                msg = "kaptchaValidateFailed -- > 验证码错误";
            } else {
                msg = "else >> "+exception;
                System.out.println("else -- >" + exception);
            }
        }
        map.put("msg", msg);
        // 此方法不处理登录成功,由shiro进行处理
        return "/login";
    }
    

    其它dao层和service的代码就不贴出来了大家直接看代码。

    测试

    1、编写好后就可以启动程序,访问http://localhost:8080/userInfo/userList页面,由于没有登录就会跳转到http://localhost:8080/login页面。登录之后就会跳转到index页面,登录后,直接在浏览器中输入http://localhost:8080/userInfo/userList访问就会看到用户信息。上面这些操作时候触发MyShiroRealm.doGetAuthenticationInfo()这个方法,也就是登录认证的方法。

    2、登录admin账户,访问:http://127.0.0.1:8080/userInfo/userAdd显示用户添加界面,访问http://127.0.0.1:8080/userInfo/userDel显示403没有权限。上面这些操作时候触发MyShiroRealm.doGetAuthorizationInfo()这个方面,也就是权限校验的方法。

    3、修改admin不同的权限进行测试

    shiro很强大,这仅仅是完成了登录认证和权限管理这两个功能,更多内容以后有时间再做探讨。

    示例代码-github

    示例代码-码云

    参考:

    Apache Shiro中文手册
    Spring Boot Shiro权限管理【从零开始学Spring Boot】
    SpringBoot+shiro整合学习之登录认证和权限控制

    –end

    最近又赶上跳槽的高峰期(招聘旺季),好多读者都问我要有没有最新面试题,找华为朋友整理一份内部资料《第6版:互联网大厂面试题》并分类 4 份 PDF,累计 926 页!

    整个资料包,包括 Spring、Spring Boot/Cloud、Dubbo、JVM、集合、多线程、JPA、MyBatis、MySQL、大数据、Nginx、Git、Docker、GitHub、Servlet、JavaWeb、IDEA、Redis、算法、面试题等几乎覆盖了 Java 基础和阿里巴巴等大厂面试题等、等技术栈!


    据说已经有小伙伴通过这套资料,成功的入职了蚂蚁金服、字节跳动等大厂。


    而且,这些资料不是扫描版的,里面的文字都可以直接复制,非常便于我们学习:

    那么如何获取这份资料呢?关注下方公众号回复:555

    1. 关注上方公众号
    2. 在下方公众号后台回复 【555】 即可。
    展开全文
  • Shiro 教程_1

    万次阅读 2020-12-30 08:38:46
    Shiro 介绍1.1 什么是 Shiro1.2 Shiro 好处1.3 Shiro 认证流程2. Shiro 单机版使用2.1 创建项目1. 添加依赖2. 创建配置文件 shiro.ini3. 创建测试类TextAuthenticator.java4. 项目结构如下2.2 源码验证流程2.2.1 ...

    教案 https://gitee.com/fakerlove/Shiro

    Shiro 教程

    1. Shiro 介绍

    1.1 什么是 Shiro

    Shiro是Apache旗下的一个开源项目,它是一个非常易用的安全框架,提供了包括认证、授权、加密、会话管理等功能,与Spring Security一样属基于权限的安全框架,但是与Spring Security 相比,Shiro使用了比较简单易懂易于使用的授权方式。Shiro属于轻量级框架,相对于Spring Security简单很多,并没有security那么复杂。

    1.2 Shiro 好处

    它是一个功能强大、灵活的、优秀的、开源的安全框架。

    它可以胜任身份验证、授权、企业会话管理和加密等工作。

    它易于使用和理解,与Spring Security相比,入门门槛低。

    1.3 Shiro 认证流程

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RUb0NY0H-1609288679506)(picture/6.png)]

    Realm 中的具体实现

    • Subject:代表当前用户,Subject 可以是一个人,也可以是第三方服务、守护进程帐户、时钟守护任务或者其它当前和软件交互的任何事件。
    • SecurityManager:管理所有Subject,SecurityManager 是 Shiro 架构的核心,配合内部安全组件共同组成安全伞。
    • Realms:用于进行权限信息的验证,我们自己实现。Realm 本质上是一个特定的安全 DAO:它封装与数据源连接的细节,得到Shiro 所需的相关的数据。在配置 Shiro 的时候,你必须指定至少一个Realm 来实现认证(authentication)和/或授权(authorization)。

    2. Shiro 单机版使用

    2.1 创建项目

    1. 添加依赖

    <dependencies>
            <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-core</artifactId>
                <version>1.5.3</version>
            </dependency>
    
    </dependencies>
    

    2. 创建配置文件 shiro.ini

    [users]
    xiao=123
    joker=123456
    

    3. 创建测试类TextAuthenticator.java

    package com.joker;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.mgt.DefaultSecurityManager;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.realm.text.IniRealm;
    import org.apache.shiro.subject.Subject;
    
    public class TextAuthenticator {
        public static void main(String[] args) {
            // 创建安全管理器对象
            DefaultSecurityManager securityManager=new DefaultSecurityManager();
            // 2. 给安全管理器设置Realm
             securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
          // 给SecurityUtils 给全局安全工具类 设置安全管理器
            SecurityUtils.setSecurityManager(securityManager);
    
             //  关键对象 Subject 主体
            Subject subject=SecurityUtils.getSubject();
    
            // 创建令牌
            UsernamePasswordToken token=new UsernamePasswordToken("joker","123456");
    
            try {
                System.out.println("认证状态"+subject.isAuthenticated());
                subject.login(token);
                System.out.println("认证状态"+subject.isAuthenticated());
            }catch (Exception e){
                e.printStackTrace();
            }
    
        }
    }
    
    

    4. 项目结构如下

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MInBnch3-1609288679510)(picture/1.png)]

    2.2 源码验证流程

    2.2.1 Realm 关系图

    2.2.2 源码讲解

    SimpleAccountRealm.java 中doGetAuthenticationInfo 方法验证用户名

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            UsernamePasswordToken upToken = (UsernamePasswordToken)token;
            SimpleAccount account = this.getUser(upToken.getUsername());
            if (account != null) {
                if (account.isLocked()) {
                    throw new LockedAccountException("Account [" + account + "] is locked.");
                }
    
                if (account.isCredentialsExpired()) {
                    String msg = "The credentials for account [" + account + "] are expired";
                    throw new ExpiredCredentialsException(msg);
                }
            }
    
            return account;
        }
    

    AuthenticatingRealm.java 中assertCredentialsMatch 这个方法 ,校验账户密码

     protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
            CredentialsMatcher cm = this.getCredentialsMatcher();
            if (cm != null) {
                if (!cm.doCredentialsMatch(token, info)) {
                    String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
                    throw new IncorrectCredentialsException(msg);
                }
            } else {
                throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify credentials during authentication.  If you do not wish for credentials to be examined, you can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
            }
        }
    

    2.2.3 继承关系的讲解

    AuthenticatingRealm 认证 getAuthenticationInfo 方法

    AuthorizingRealm 授权 getAuthorizationInfo 方法

    SimpleAccountRealm 为什么能够实现认证+授权呢??

    SimpleAccountRealm 继承于AuthorizingRealm ,AuthorizingRealm 继承于AuthenticatingRealm

    public class SimpleAccountRealm extends AuthorizingRealm
    

    如果想要实现 自定义的Realm ,就必须要自己继承AuthorizingRealm 这个类,模仿SimpleAccountRealm

    2.3 实现自定义的Realm

    1. 首先自定义Realm

    package com.joker.myrealm;
    
    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.realm.AuthorizingRealm;
    import org.apache.shiro.realm.SimpleAccountRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    
    /**
     *  上章讲解过 自定义Realm 就需要 继承 AuthorizingRealm 中的两个方法
     */
    public class CumstomRealm extends AuthorizingRealm {
    
        /**
         * 授权
         * @param principalCollection
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            return null;
        }
    
        /**
         * 认证
         * @param authenticationToken
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
           // 取到用户名
            String name=(String)authenticationToken.getPrincipal();
           System.out.println("自定义Realm "+name+" ");
           // 判断用户是否存在,连接数据库
           if(name.equals("joker")){
    
               // 这里的 “123456” 是数据库中查到的密码
               SimpleAuthenticationInfo simpleAccountRealm=new SimpleAuthenticationInfo(name,"123456",this.getName());
               return simpleAccountRealm;
           }
            return null;
        }
    }
    
    

    2. 使用自定义Realm

    package com.joker.myrealm;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.IncorrectCredentialsException;
    import org.apache.shiro.authc.UnknownAccountException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.mgt.DefaultSecurityManager;
    import org.apache.shiro.realm.Realm;
    import org.apache.shiro.realm.text.IniRealm;
    import org.apache.shiro.subject.Subject;
    
    /**
     * 在这里我们使用 自定义的Realm
     */
    public class TestAuthorRealm {
        public static void main(String[] args) {
    
            DefaultSecurityManager securityManager=new DefaultSecurityManager();
            // 2. 给安全管理器设置Realm
            securityManager.setRealm(new CumstomRealm());
            // 给SecurityUtils 给全局安全工具类 设置安全管理器
            SecurityUtils.setSecurityManager(securityManager);
    
            //  关键对象 Subject 主体
            Subject subject=SecurityUtils.getSubject();
    
            // 创建令牌
            UsernamePasswordToken token=new UsernamePasswordToken("joker","123456");
    
            try {
                // 如果输入的 token 是错误的信息,会报错
                System.out.println("自定义认证状态"+subject.isAuthenticated());
                subject.login(token);
                System.out.println("自定义认证状态"+subject.isAuthenticated());
                Realm realm;
            }catch (UnknownAccountException e){
                e.printStackTrace();
                System.out.println("用户名错误");
            } catch (IncorrectCredentialsException e){
                System.out.println("密码错误");
                e.printStackTrace();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    
    

    2.4 Md5 +Salt 认证流程

    1. Service 层 业务层中算出Md5 算出来的结果

    package com.joker.md5p;
    
    import org.apache.shiro.crypto.hash.Md5Hash;
    
    public class Md5Test {
        public static void main(String[] args) {
    
            /**
             * 第一个参数 source ,就是密码
             * 第二个参数 salt ,加盐 ,随机字符串
             * 第三个参数 hashlterations ,哈希散列 ,越大散列越均匀
             */
            Md5Hash hash=new Md5Hash("123456","xod",1024);
            System.out.println(hash.toHex());
        }
    }
    
    

    2.设置自定义的Realm

    package com.joker.md5p;
    
    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.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.util.ByteSource;
    
    public class CumstomMd5Realm extends AuthorizingRealm {
    
        /**
         * 授权
         * @param principalCollection
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            return null;
        }
    
        /**
         * 认证
         * @param authenticationToken
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            // 取到用户名
            String name=(String)authenticationToken.getPrincipal();
            System.out.println("自定义Realm "+name+" ");
            // 判断用户是否存在,连接数据库
            if(name.equals("joker")){
                // 这里的 “f3aed468e9246cbdff61fc59084154e0” Md5Test 算出来的 ,是数据库中查到的密码
                // ByteSource.Util.bytes("xod") 这个是随机盐salt
                SimpleAuthenticationInfo simpleAccountRealm=new SimpleAuthenticationInfo(name,"f3aed468e9246cbdff61fc59084154e0", ByteSource.Util.bytes("xod"),this.getName());
                return simpleAccountRealm;
            }
            return null;
        }
    }
    
    

    3. 使用 Realm

    package com.joker.md5p;
    
    import com.joker.myrealm.CumstomRealm;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.IncorrectCredentialsException;
    import org.apache.shiro.authc.UnknownAccountException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
    import org.apache.shiro.mgt.DefaultSecurityManager;
    import org.apache.shiro.realm.Realm;
    import org.apache.shiro.subject.Subject;
    
    /**
     * 在这里我们使用 自定义的Realm
     */
    public class TestMd5AuthorRealm {
        public static void main(String[] args) {
    
            DefaultSecurityManager securityManager=new DefaultSecurityManager();
            CumstomMd5Realm cumstomMd5Realm=new CumstomMd5Realm();
            // 设置匹配策略,
            HashedCredentialsMatcher hashedCredentialsMatcher=new HashedCredentialsMatcher();
            //设置Md5 加密
            hashedCredentialsMatcher.setHashAlgorithmName("md5");
            // 设置hash 散列度
            hashedCredentialsMatcher.setHashIterations(1024);
            cumstomMd5Realm.setCredentialsMatcher(hashedCredentialsMatcher);
            // 2. 给安全管理器设置Realm
            securityManager.setRealm(cumstomMd5Realm);
    
            // 给SecurityUtils 给全局安全工具类 设置安全管理器
            SecurityUtils.setSecurityManager(securityManager);
    
            //  关键对象 Subject 主体
            Subject subject=SecurityUtils.getSubject();
    
            // 创建令牌
            UsernamePasswordToken token=new UsernamePasswordToken("joker","123456");
    
            try {
                // 如果输入的 token 是错误的信息,会报错
                System.out.println("自定义认证状态"+subject.isAuthenticated());
                subject.login(token);
                System.out.println("自定义认证状态"+subject.isAuthenticated());
                Realm realm;
            }catch (UnknownAccountException e){
                e.printStackTrace();
                System.out.println("用户名错误");
            } catch (IncorrectCredentialsException e){
                System.out.println("密码错误");
                e.printStackTrace();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    
    

    2.5 授权流程

    1. 基于角色的控制访问

    2. 基于资源的控制访问

    3. 权限字符串

    命名规则 资源标识符:操作:资源实例标识符

    4. 授权实现方式

    • 编程式

      package com.joker.other;
      
      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;
      
      public class MyRealm extends AuthorizingRealm {
      
          /**
           * 授权
           * @param principalCollection
           * @return
           */
          @Override
          protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
              System.out.println("授权道路开始了");
              String pr=(String)principalCollection.getPrimaryPrincipal();
              System.out.println("身份信息为 "+pr);
              SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
              simpleAuthorizationInfo.addRole("admin");
              simpleAuthorizationInfo.addRole("user");
              // 这个用户对于 001 这个资源有着所有的操作权限 
              simpleAuthorizationInfo.addStringPermission("user:*:001");
              return simpleAuthorizationInfo;
          }
      
          /**
           * 认证
           * @param authenticationToken
           * @return
           * @throws AuthenticationException
           */
          @Override
          protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
              // 取到用户名
              String name=(String)authenticationToken.getPrincipal();
              System.out.println("自定义Realm "+name+" ");
              // 判断用户是否存在,连接数据库
              if(name.equals("joker")){
      
                  // 这里的 “f3aed468e9246cbdff61fc59084154e0” Md5Test 算出来的 ,是数据库中查到的密码
                  // ByteSource.Util.bytes("xod") 这个是随机盐salt
                  SimpleAuthenticationInfo simpleAccountRealm=new SimpleAuthenticationInfo(name,"f3aed468e9246cbdff61fc59084154e0", ByteSource.Util.bytes("xod"),this.getName());
                  return simpleAccountRealm;
              }
              return null;
          }
      }
      
      
      package com.joker.other;
      
      import org.apache.shiro.SecurityUtils;
      import org.apache.shiro.authc.IncorrectCredentialsException;
      import org.apache.shiro.authc.UnknownAccountException;
      import org.apache.shiro.authc.UsernamePasswordToken;
      import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
      import org.apache.shiro.mgt.DefaultSecurityManager;
      import org.apache.shiro.realm.Realm;
      import org.apache.shiro.subject.Subject;
      
      import java.util.Arrays;
      
      /**
       * 在这里我们使用 自定义的Realm
       */
      public class TestMAuthorRealm {
          public static void main(String[] args) {
      
              DefaultSecurityManager securityManager=new DefaultSecurityManager();
              MyRealm cumstomMd5Realm=new MyRealm();
              // 设置匹配策略,
              HashedCredentialsMatcher hashedCredentialsMatcher=new HashedCredentialsMatcher();
              //设置Md5 加密
              hashedCredentialsMatcher.setHashAlgorithmName("md5");
              // 设置hash 散列度
              hashedCredentialsMatcher.setHashIterations(1024);
              cumstomMd5Realm.setCredentialsMatcher(hashedCredentialsMatcher);
              // 2. 给安全管理器设置Realm
              securityManager.setRealm(cumstomMd5Realm);
      
              // 给SecurityUtils 给全局安全工具类 设置安全管理器
              SecurityUtils.setSecurityManager(securityManager);
      
              //  关键对象 Subject 主体
              Subject subject=SecurityUtils.getSubject();
      
              // 创建令牌
              UsernamePasswordToken token=new UsernamePasswordToken("joker","123456");
      
              try {
                  // 如果输入的 token 是错误的信息,会报错
                  System.out.println("自定义认证状态"+subject.isAuthenticated());
                  subject.login(token);
                  System.out.println("自定义认证状态"+subject.isAuthenticated());
                  Realm realm;
              }catch (UnknownAccountException e){
                  e.printStackTrace();
                  System.out.println("用户名错误");
              } catch (IncorrectCredentialsException e){
                  System.out.println("密码错误");
                  e.printStackTrace();
              }catch (Exception e){
                  e.printStackTrace();
              }
      
              //开始授权
              if(subject.isAuthenticated()){
                  System.out.println(subject.hasRole("admin"));
                  // 这个是多角色权限
                  System.out.println(subject.hasAllRoles(Arrays.asList("admin","user")));
      
                  // 基于字符串的权限控制
                  System.out.println( subject.isPermitted("user:*:001"));
      
              }
          }
      }
      
      
    • 注解式

    • 标签式

    项目结构如下

    3. Shiro 整和SpringBoot 使用

    3.1 环境搭建

    Shiro+SpringBoot+Mysql+Redis(缓存)

    1. 添加依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.3.1.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>demo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>demo</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.3</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
    
            <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.23</version>
            </dependency>
    
            <!--        shiro+springboot 启动 -->
            <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring-boot-web-starter -->
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring-boot-web-starter</artifactId>
                <version>1.5.3</version>
            </dependency>
            <!-- shiro 的缓存-->
            <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-ehcache -->
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-ehcache</artifactId>
                <version>1.5.3</version>
            </dependency>
            <!--        jwt-->
            <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
            <dependency>
                <groupId>com.auth0</groupId>
                <artifactId>java-jwt</artifactId>
                <version>3.10.3</version>
            </dependency>
            <!--        使用自定义的配置信息,就必须使用这样子的-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <optional>true</optional>
            </dependency>
            <!--        连接redis-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-freemarker</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.67</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    

    2. 配置 yml

    server:
      port: 9099
    spring:
      application:
        name: demo1
      #    配置 mybatis
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://rm-uf682626j42h0nn6ljo.mysql.rds.aliyuncs.com:3306/faker?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
        username: joker
        password: 
        type: com.alibaba.druid.pool.DruidDataSource
    
    #    resources:
    #      static-locations:
    
      redis:
        host: 47.100.104.187
        database: 0
        password: 
        port: 6379
        jedis:
          pool:
            max-wait: 3600
            max-active: 8
        timeout: 3600
      mvc:
        static-path-pattern: classpath:/static/ classpath:/templates/
        view:
          suffix: .html
          prefix: classpath:/templates/
    
      resources:
        static-locations: classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/
      thymeleaf:
        cache: false
        suffix: .html
        prefix: classpath:/templates/
    
    mybatis:
      mapper-locations: classpath:mapper/*.xml
      type-aliases-package: com.example.demo.entity
    
    

    3. 创建 实体类

    package com.example.demo.entity;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import lombok.experimental.Accessors;
    
    /**
     *  是 用户的 信息
     */
    
    @Data
    @NoArgsConstructor
    @Accessors(chain = true)
    @AllArgsConstructor
    public class Person {
        private int user_id;
        private String user_name;
        private String user_password;
        private Double user_money;
        private int user_lev;
        private String user_email;
        private String user_address;
        private String user_banner_img;
        private String user_telephone;
        private String user_introduce;
        private int user_gender;
        private String user_salt;
    }
    
    package com.example.demo.entity;
    
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import lombok.experimental.Accessors;
    
    /**
     *  返回结果
     * @param <T>
     */
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Accessors(chain = true)
    public class ResultData<T>{
        private String message;
        private Integer code;
        private T data;
    }
    
    

    4. 创建Mapper

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.example.demo.mapper.PersonMapper">
    
        <!--    -->
        <resultMap id="PersonMap" type="com.example.demo.entity.Person">
            <id column="user_id" property="user_id" jdbcType="INTEGER"/>
            <result column="user_lev" jdbcType="INTEGER" property="user_lev"/>
            <result column="user_gender" jdbcType="INTEGER" property="user_gender"/>
            <result column="user_password" jdbcType="VARCHAR" property="user_password"/>
            <result column="user_email" jdbcType="VARCHAR" property="user_email"/>
            <result column="user_address" jdbcType="VARCHAR" property="user_address"/>
            <result column="user_banner_img" jdbcType="VARCHAR" property="user_banner_img"/>
            <result column="user_telephone" jdbcType="VARCHAR" property="user_telephone"/>
            <result column="user_introduce" jdbcType="VARCHAR" property="user_introduce"/>
            <result column="user_password" jdbcType="VARCHAR" property="user_password"/>
            <result column="user_money" property="user_money" jdbcType="DOUBLE"/>
            <result column="user_salt" property="user_salt" jdbcType="VARCHAR"/>
        </resultMap>
    
        <select id="checkLogin" resultType="int">
            SELECT count(*) FROM person
            where user_id=#{user_id} and user_password=#{user_password}
        </select>
    
        <select id="findPersonById" resultMap="PersonMap">
            SELECT * FROM person
            where user_id=#{user_id}
        </select>
    
        <select id="findPersonByName" resultMap="PersonMap">
            SELECT * FROM person
            where user_name=#{user_name}
        </select>
    
        <select id="findPersonAll" resultMap="PersonMap" parameterType="int">
            select * from person
            where user_lev=#{user_lev}
        </select>
    
    
        <insert id="addPerson" parameterType="Person">
           insert into person(user_name,user_password,user_money,user_lev,user_gender,user_salt)
           values(#{user_name},#{user_password},#{user_money},#{user_lev},#{user_gender},#{user_salt});
    
        </insert>
    
        <select id="findid" resultType="int">
               SELECT @@IDENTITY
        </select>
    
        <update id="modifyPerson" parameterType="Person">
            update person
            <set>
                <if test="user_name!=null and user_name!=''">
                    user_name=#{user_name},
                </if>
    
                <if test="user_money!=null and user_money!=''">
                    user_money=#{user_money},
                </if>
                <if test="user_email!=null and user_email!=''">
                    user_email=#{user_email},
                </if>
                <if test="user_address!=null and user_address!=''">
                    user_address=#{user_address},
                </if>
                <if test="user_banner_img!=null and user_banner_img!=''">
                    user_banner_img=#{user_banner_img},
                </if>
                <if test="user_telephone!=null and user_telephone!=''">
                    user_telephone=#{user_telephone},
                </if>
    
                <if test="user_introduce!=null and user_introduce!=''">
                    user_introduce=#{user_introduce},
                </if>
                <if test="user_gender!=null and user_gender!=''">
                    user_gender=#{user_gender},
                </if>
    
            </set>
            where user_id=#{user_id}
        </update>
    
        <update id="addMoney">
           update person set user_money=#{user_money} where user_id=#{user_id}
        </update>
    
        <delete id="deletePerson" parameterType="int">
            delete from person where user_id=#{user_id}
        </delete>
    
    </mapper>
    

    5. 创建 db

    CREATE DATABASE  IF NOT EXISTS `faker` /*!40100 DEFAULT CHARACTER SET utf8 */ /*!80016 DEFAULT ENCRYPTION='N' */;
    USE `faker`;
    -- MySQL dump 10.13  Distrib 8.0.17, for Win64 (x86_64)
    --
    -- Host: rm-uf682626j42h0nn6ljo.mysql.rds.aliyuncs.com    Database: faker
    -- ------------------------------------------------------
    -- Server version	8.0.18
    
    /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
    /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
    /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
    /*!50503 SET NAMES utf8 */;
    /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
    /*!40103 SET TIME_ZONE='+00:00' */;
    /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
    /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
    /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
    /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
    SET @MYSQLDUMP_TEMP_LOG_BIN = @@SESSION.SQL_LOG_BIN;
    SET @@SESSION.SQL_LOG_BIN= 0;
    
    --
    -- GTID state at the beginning of the backup 
    --
    
    SET @@GLOBAL.GTID_PURGED=/*!80000 '+'*/ '58413bdc-b9ef-11ea-8020-00163e0ab18f:1-66';
    
    --
    -- Table structure for table `person`
    --
    
    DROP TABLE IF EXISTS `person`;
    /*!40101 SET @saved_cs_client     = @@character_set_client */;
    /*!50503 SET character_set_client = utf8mb4 */;
    CREATE TABLE `person` (
      `user_id` int(10) NOT NULL AUTO_INCREMENT,
      `user_name` varchar(20) NOT NULL,
      `user_password` varchar(100) NOT NULL,
      `user_money` double NOT NULL DEFAULT '0',
      `user_lev` int(11) NOT NULL DEFAULT '1',
      `user_address` varchar(100) DEFAULT '"没有写地址"',
      `user_email` varchar(100) DEFAULT NULL,
      `user_banner_img` varchar(1000) DEFAULT NULL,
      `user_telephone` varchar(100) DEFAULT NULL,
      `user_introduce` varchar(1000) DEFAULT NULL,
      `user_gender` int(11) NOT NULL DEFAULT '1',
      `user_salt` varchar(45) DEFAULT NULL,
      PRIMARY KEY (`user_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
    /*!40101 SET character_set_client = @saved_cs_client */;
    
    --
    -- Dumping data for table `person`
    --
    
    LOCK TABLES `person` WRITE;
    /*!40000 ALTER TABLE `person` DISABLE KEYS */;
    INSERT INTO `person` VALUES (1,'joker','123',126,1,'南京','203462009@qq.com','https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1590311354953&di=ea3b02c8f9e45664f87794a4c3692e26&imgtype=0&src=http%3A%2F%2Fa3.att.hudong.com%2F35%2F34%2F19300001295750130986345801104.jpg','15651771520','炸鸡店',0,NULL),(2,'peter','123456',997,2,'北京','203462009@qq.com','https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1590311354953&di=ea3b02c8f9e45664f87794a4c3692e26&imgtype=0&src=http%3A%2F%2Fa3.att.hudong.com%2F35%2F34%2F19300001295750130986345801104.jpg','15651771520','新石器烤肉',0,NULL),(3,'admin','123wan',888,2,'北京','203462009@qq.com','https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1590311354953&di=ea3b02c8f9e45664f87794a4c3692e26&imgtype=0&src=http%3A%2F%2Fa3.att.hudong.com%2F35%2F34%2F19300001295750130986345801104.jpg','15651771520','黄焖鸡店',0,NULL),(5,'pp','213',0,1,NULL,'','https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1590311354953&di=ea3b02c8f9e45664f87794a4c3692e26&imgtype=0&src=http%3A%2F%2Fa3.att.hudong.com%2F35%2F34%2F19300001295750130986345801104.jpg','15651771520',NULL,1,NULL),(6,'dd','123',0,1,NULL,'','https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1590311354953&di=ea3b02c8f9e45664f87794a4c3692e26&imgtype=0&src=http%3A%2F%2Fa3.att.hudong.com%2F35%2F34%2F19300001295750130986345801104.jpg','15651771520',NULL,1,NULL),(7,'joker','123',19988,1,NULL,'203462009@qq.com','D:\\code\\shopping\\src\\main\\resources\\upload\\8474d5bf-9486-4a60-a349-59448e408972.jpg','15651771520','苦而不言,喜而不语',1,NULL),(8,'akk','f9704410c5d8cca9c920102b92e2d3e2',0,1,'\"没有写地址\"',NULL,NULL,NULL,NULL,1,'3uhku6bx'),(9,'ad','cbfc5f5fd18dc905266d0272a93ebc62',0,2,'\"没有写地址\"',NULL,NULL,NULL,NULL,1,'1chsswry');
    /*!40000 ALTER TABLE `person` ENABLE KEYS */;
    UNLOCK TABLES;
    
    --
    -- Dumping events for database 'faker'
    --
    
    --
    -- Dumping routines for database 'faker'
    --
    SET @@SESSION.SQL_LOG_BIN = @MYSQLDUMP_TEMP_LOG_BIN;
    /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
    
    /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
    /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
    /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
    /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
    /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
    /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
    /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
    
    -- Dump completed on 2020-07-24 12:20:42
    
    

    6. 创建Dao 层

    package com.example.demo.mapper;
    
    
    import com.example.demo.entity.Person;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Param;
    import org.springframework.stereotype.Repository;
    
    import java.util.List;
    
    @Mapper
    @Repository
    public interface PersonMapper {
    
        /**
         * 表示登录
         *
         * @param user_id
         * @param user_password
         * @return
         */
        int checkLogin(@Param("user_id") int user_id, @Param("user_password") String user_password);
    
        /**
         * 返回的是刚刚插入的主键值
         * @param p
         * @return
         */
        int addPerson(Person p);
    
        /**
         * 根据用户 的id 来查找用户信息
         * @param id
         * @return
         */
        Person findPersonById(int id);
    
        /**
         * 修改用户 信息
         * @param person
         * @return
         */
        int modifyPerson(Person person);
    
        /**
         * 根据 用户等级,查找所有用户
         * @param user_lev
         * @return
         */
        List<Person> findPersonAll(int user_lev);
    
        /**
         * 删除 用户
         * @param user_id
         * @return
         */
        int deletePerson(int user_id);
    
        /**
         * 修改钱
         *
         * @param user_money
         * @param user_id
         * @return
         */
        int addMoney(@Param("user_money") Double user_money, @Param("user_id") int user_id);
    
        int findid();
    
        Person findPersonByName(String user_name);
    }
    
    

    7. 创建 service

    package com.example.demo.service;
    
    import com.example.demo.entity.Person;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.List;
    
    @Service
    public interface LoginService{
    
        Person loginCheck(int user_id, String user_name);
    
        List<Person> findall(int user_lev);
    
        Person selectPersonById(Integer id);
    
        Person selectPersonByName(String name);
    
        Boolean addPerson(Person person);
    }
    
    package com.example.demo.service.impl;
    
    import com.example.demo.entity.Person;
    import com.example.demo.mapper.PersonMapper;
    import com.example.demo.service.LoginService;
    import lombok.extern.java.Log;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.List;
    
    @Service
    @Transactional
    public class LoginServiceImpl implements LoginService {
    
        @Autowired
        PersonMapper personMapper;
    
        /**
         *
         * @param user_id
         * @param user_name
         * @return
         */
        @Override
        public Person loginCheck(int user_id, String user_name) {
            return personMapper.findPersonById(user_id);
        }
    
    
        /**
         * 按照等级查找用户
         * @param user_lev
         * @return
         */
        @Override
        public List<Person> findall(int user_lev) {
            return personMapper.findPersonAll(user_lev);
        }
    
        @Override
        public Person selectPersonById(Integer id) {
            return personMapper.findPersonById(id);
        }
    
        @Override
        public Person selectPersonByName(String name) {
            return personMapper.findPersonByName(name);
        }
    
        @Override
        public Boolean addPerson(Person person) {
            int result=personMapper.addPerson(person);
            return result>0;
        }
    
    
    }
    
    

    8. 创建 Controller

    package com.example.demo.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    /**
     * @author bn
     */
    @Controller
    public class JumpController {
    
        @RequestMapping("/login")
        public String user(){
            return "login";
        }
    
        @RequestMapping("/register")
        public String red(){
            return "register";
        }
    }
    
    
    package com.example.demo.controller;
    
    
    import com.example.demo.entity.Person;
    import com.example.demo.entity.ResultData;
    import com.example.demo.service.LoginService;
    import com.example.demo.utils.ShiroUtils;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.IncorrectCredentialsException;
    import org.apache.shiro.authc.UnknownAccountException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.authz.annotation.RequiresRoles;
    import org.apache.shiro.subject.Subject;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 用户控制类
     */
    @Controller
    @RequestMapping("/person")
    public class LoginController {
    
        protected static final Logger logger = LoggerFactory.getLogger(com.example.demo.controller.LoginController.class);
    
        LoginService loginService;
    
        @Autowired
        public void setLoginService(LoginService loginService) {
            this.loginService = loginService;
        }
    
    
        @RequestMapping("/test")
        @RequiresRoles("user")
        public String test(){
            return "test";
        }
    
        @RequestMapping("/admin")
        @RequiresRoles("admin")
        public String amdin(){
            return "admin";
        }
    
        @RequestMapping("/user")
        public String user(){
            return "user";
        }
    
    
    
        @RequestMapping("/login")
        public String login(String user_name, String user_password){
            System.out.println(user_name+"  "+user_password);
    
            try {
                UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken(user_name,user_password);
                Subject subject= SecurityUtils.getSubject();
                subject.login(usernamePasswordToken);
            }catch (UnknownAccountException e){
                e.printStackTrace();
                System.out.println("用户名错误");
                return "login";
            } catch (IncorrectCredentialsException e){
                System.out.println("密码错误");
                e.printStackTrace();
                return "login";
            }catch (Exception e){
                e.printStackTrace();
                return "login";
            }
           return "success";
        }
    
        @RequestMapping("/findAll")
        @ResponseBody
        public ResultData<List<Person>> findAll(@RequestParam("user_lev") int user_lev){
            ResultData<List<Person>> data=new ResultData<>();
            System.out.println(user_lev);
            try {
                List<Person> result= new ArrayList<>();
                result=loginService.findall(user_lev);
    
                if(result!=null&&result.size()>=0){
                    data.setCode(200);
                    data.setMessage("查找成功");
                    data.setData(result);
                }else {
                    data.setCode(-1);
                    data.setMessage("查找失败");
                    data.setData(null);
                }
            }catch (Exception e){
                e.printStackTrace();
                logger.info("对不起 登录出现问题");
            }
            return data;
        }
    
        @RequestMapping("/register")
        public String register(String user_name, String user_password,Integer user_lev){
            System.out.println(user_name+"  "+user_password);
            Person person=new Person();
            String salt=ShiroUtils.getRandomSalt(8);
    
            // 这里进行盐处理
            String password=ShiroUtils.md5(user_password,salt);
            person.setUser_name(user_name);
            person.setUser_password(password);
            person.setUser_address("");
            person.setUser_lev(user_lev);
            person.setUser_salt(salt);
            person.setUser_gender(1);
            person.setUser_money(0.0);
            System.out.println("存储的"+person);
            Boolean result=loginService.addPerson(person);
            if(result){
                return "login";
            }else {
                return "register";
            }
        }
    }
    
    

    9. 创建Redis 配置类

    package com.example.demo.config.redis;
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.serializer.SerializerFeature;
    import com.fasterxml.jackson.databind.JavaType;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.type.TypeFactory;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.SerializationException;
    import com.alibaba.fastjson.parser.ParserConfig;
    import org.springframework.util.Assert;
    import java.nio.charset.Charset;
    import java.nio.charset.StandardCharsets;
    /**
     * FastJson2JsonRedisSerializer
     *  Redis使用FastJson序列化
     *  by zhengkai.blog.csdn.net
     */
    public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
        private ObjectMapper objectMapper = new ObjectMapper();
        public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
    
        private Class<T> clazz;
        
       static {
       		ParserConfig.getGlobalInstance().setAutoTypeSupport(true); 
       		//如果遇到反序列化autoType is not support错误,请添加并修改一下包名到bean文件路径
           // ParserConfig.getGlobalInstance().addAccept("com.xxxxx.xxx");
        }
        public FastJson2JsonRedisSerializer(Class<T> clazz) {
            super();
            this.clazz = clazz;
        }
    
        @Override
        public byte[] serialize(T t) throws SerializationException {
            if (t == null) {
                return new byte[0];
            }
            return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
        }
    
        @Override
        public T deserialize(byte[] bytes) throws SerializationException {
            if (bytes == null || bytes.length <= 0) {
                return null;
            }
            String str = new String(bytes, DEFAULT_CHARSET);
    
            return JSON.parseObject(str, clazz);
        }
        public void setObjectMapper(ObjectMapper objectMapper) {
            Assert.notNull(objectMapper, "'objectMapper' must not be null");
            this.objectMapper = objectMapper;
        }
    
        protected JavaType getJavaType(Class<?> clazz) {
            return TypeFactory.defaultInstance().constructType(clazz);
        }
    
    }
    
    
    package com.example.demo.config.redis;
     
    import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
    import lombok.Data;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    import org.springframework.stereotype.Component;
     
    /**
     * @author bn
     */
    @Data
    @Component
    @Configuration
    @Slf4j
    public class RedisConfig {
     
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    
            RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
            redisTemplate.setConnectionFactory(redisConnectionFactory);
    
            FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
    
            // 设置值value的序列化方式
            redisTemplate.setValueSerializer(fastJsonRedisSerializer);
            redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
    
            // 设置键key的序列化方式
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    
            redisTemplate.afterPropertiesSet();
            return redisTemplate;
        }
     
    }
    

    10 .创建 RedisUtils

    package com.example.demo.utils;
    
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    
    import javax.annotation.Resource;
    
    @Component
    public  class RedisUtil implements ApplicationContextAware {
    
    
        private static RedisTemplate redisTemplate;
    
        public static   RedisTemplate get(){
            return redisTemplate;
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            redisTemplate=(RedisTemplate)applicationContext.getBean("redisTemplate");
        }
    
    //    public String get(String key) {
    //        if (StringUtils.isEmpty(key)) {
    //            return null;
    //        }
    //        return redisTemplate.opsForValue().get(key);
    //    }
    //
    //    public void set(String key, String value) {
    //        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value)) {
    //            return;
    //        }
    //        redisTemplate.opsForValue().set(key, value);
    //    }
    }
    

    11. 创建自定义 Realm

    package com.example.demo.config.shiro;
    
    import com.example.demo.entity.Person;
    import com.example.demo.service.LoginService;
    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.Permission;
    import org.apache.shiro.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.crypto.hash.Md5Hash;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.springframework.beans.factory.annotation.Autowired;
    
    /**
     * 自定义的 Realm 这里进行 用户认证和授权的操作
     * 我们这里假定 user_lev =1 就是用户的操作,user_lev=2 就是 管理员的操作
     */
    public class ShiroRealm extends AuthorizingRealm {
    
        @Autowired
        LoginService loginService;
    
    
        /**
         * 这里进行授权
         * @param principalCollection
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
            System.out.println("=======授权=====");
            String name=(String)principalCollection.getPrimaryPrincipal();
            System.out.println("身份信息为 "+name);
    
            // 查询数据库,获取 角色信息
            Person person=loginService.selectPersonByName(name);
            int lev=person.getUser_lev();
    
            SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
            // 等级为 1 ,就是 user
            if(lev==1){
                simpleAuthorizationInfo.addRole("user");
                // 等级为 2 ,就是 admin
            }else{
                simpleAuthorizationInfo.addRole("admin");
            }
            return simpleAuthorizationInfo;
        }
    
    
    
        /**
         * 认证是否有这个用户
         * @param authenticationToken
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    
            // 获取用户的 name
            String name=(String) authenticationToken.getPrincipal();
            Person person=loginService.selectPersonByName(name);
            System.out.println("=======认证=====");
            System.out.println(name);
            // 表示有这个用户
            if(person!=null&&person.getUser_name()!=null){
                String password=person.getUser_password();
                String salt=person.getUser_salt();
    
                // 这里是 和 Controller 层中的UsernamePasswordToken 密码进行比较
                SimpleAuthenticationInfo simpleAuthorizationInfo=new SimpleAuthenticationInfo(name,password,new Md5Hash(salt),this.getName());
                return simpleAuthorizationInfo;
            }
            return null;
        }
    }
    
    

    12. 创建Shiro缓存

    package com.example.demo.config.shiro;
    
    
    import com.example.demo.utils.RedisUtil;
    import org.apache.shiro.cache.Cache;
    import org.apache.shiro.cache.CacheException;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Component;
    
    import javax.annotation.Resource;
    import java.util.Collection;
    import java.util.Set;
    
    /**
     * 重写Shiro 中的缓存
     * @param <K>
     * @param <V>
     */
    //@Component
    public class ShiroCache<K,V> implements Cache<K,V> {
    
    
    
    
    
    
    
        /**
         * 获取Cache
         * @param k
         * @return
         * @throws CacheException
         */
        @Override
        public V get(K k) throws CacheException {
            System.out.println("ShiroCache get===key "+ k);
            if(k==null){
                return  null;
            }else {
                RedisTemplate redisTemplate=RedisUtil.get();
                return (V)redisTemplate.opsForValue().get(k);
            }
    
        }
    
        /**
         *
         * @param k
         * @param v
         * @return
         * @throws CacheException
         */
        @Override
        public V put(K k, V v) throws CacheException {
            System.out.println(k+"=ShiroCache=="+v);
    //        System.out.println("redisUtils=="+redisUtil);
            RedisTemplate redisTemplate=RedisUtil.get();
    //        System.out.println("redisTemplate=="+redisTemplate);
    //        System.out.println(redisUtil==null);
            redisTemplate.opsForValue().set(k.toString(),v);
            return null;
        }
    
        @Override
        public V remove(K k) throws CacheException {
            RedisTemplate redisTemplate=RedisUtil.get();
            redisTemplate.delete(k);
            return null;
        }
    
        @Override
        public void clear() throws CacheException {
            RedisTemplate redisTemplate=RedisUtil.get();
            Set<String> keys = redisTemplate.keys("*");
            redisTemplate.delete(keys);
        }
    
        @Override
        public int size() {
            return 0;
        }
    
        @Override
        public Set<K> keys() {
            return null;
        }
    
        @Override
        public Collection<V> values() {
            return null;
        }
    }
    
    
    package com.example.demo.config.shiro;
    
    import org.apache.shiro.cache.Cache;
    import org.apache.shiro.cache.CacheException;
    import org.apache.shiro.cache.CacheManager;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.stereotype.Service;
    
    /**
     * @author bn
     */
    @Component
    public class ShiroCacheManager implements CacheManager {
    
        @Override
        public <K, V> Cache<K, V> getCache(String s) throws CacheException {
            System.out.println("shiro 缓存"+s);
            return new ShiroCache<K,V>();
        }
    }
    
    

    13. 创建 自定义的ShiroFilter

    package com.example.demo.config.shiro;
    
    import com.example.demo.service.LoginService;
    import com.example.demo.utils.ShiroUtils;
    import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
    import org.apache.shiro.realm.Realm;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 配置shiro
     */
    @Configuration
    public class ShiroConfig {
    
        @Autowired
        LoginService loginService;
    
        // 1. 创建 ShiroFilter
        @Bean("shiroFilterFactoryBean")
        public ShiroFilterFactoryBean getshiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
    
            ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
            // 设置 安全管理器
            System.out.println("=======ShiroFilterFactoryBean======");
            bean.setSecurityManager(defaultWebSecurityManager);
    
            // 设置文件资源
            Map<String ,String> map=new HashMap<>();
            // 开放了 login ,和 register 页面
            map.put("/login","anon");// 开放首页
            map.put("/register","anon");
            map.put("/person/login","anon");
            map.put("/person/register","anon");
            // 拦截了 /findAll 这个请求 ,这个资源需要 授权加上认证。所以访问不了
            map.put("/person/findAll","authc");
            // 剩下的两个 路由,因为添加了 注解,所以这里不需要添加 ,也是有用的
            bean.setFilterChainDefinitionMap(map);
    
            bean.setUnauthorizedUrl("quanxian");
            // 如果文件资源没有权限,跳转到那个页面
            bean.setLoginUrl("login");
    
            return bean;
        }
    
        /**
         * 2. 创建 web 类型的 安全管理器
         * @param realm
         * @return
         */
        @Bean
        DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getRealm") Realm realm){
            DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
            manager.setRealm(realm);
            return manager;
        }
    
        /**
         * 自定义一个 Realm 管理器
         * @return
         */
        @Bean("getRealm")
        public Realm realm(){
            // 设置算法的密度
            HashedCredentialsMatcher hashedCredentialsMatcher=new HashedCredentialsMatcher();
            //设置Md5 加密
            hashedCredentialsMatcher.setHashAlgorithmName(ShiroUtils.hashAlgorithmName);
            // 设置hash 散列度
            hashedCredentialsMatcher.setHashIterations(ShiroUtils.hashIterations);
            ShiroRealm realm=new ShiroRealm();
            realm.setCredentialsMatcher(hashedCredentialsMatcher);
    
            realm.setCacheManager(new ShiroCacheManager());
            realm.setCachingEnabled(true);// 开启全局缓存
            realm.setAuthenticationCachingEnabled(true);
            realm.setAuthenticationCacheName("renZhenCache");
            realm.setAuthorizationCachingEnabled(true);
            realm.setAuthorizationCacheName("shouQuanCache");
    
    
            return realm;
        }
    }
    
    

    14. 创建异常处理器

    package com.example.demo.exception;
    
    import com.example.demo.entity.ResultData;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    /**
     * @author bn
     */
    @ControllerAdvice
    public class MyControllerAdvice {
    
        /**
         * 全局异常捕捉处理
         * @param ex
         * @return
         */
        @ResponseBody
        @ExceptionHandler(value = Exception.class)
        public ResultData<String> errorHandler(Exception ex) {
           ResultData<String> data=new ResultData<>();
           data.setCode(-1);
           data.setMessage("对不起没有权限");
           data.setData("错误");
           return data;
        }
    
        /**
         * 拦截捕捉自定义异常 MyException.class
         * @param ex
         * @return
         */
    //    @ResponseBody
    //    @ExceptionHandler(value = MyException.class)
    //    public Map myErrorHandler(MyException ex) {
    //        Map map = new HashMap();
    //        map.put("code", ex.getCode());
    //        map.put("msg", ex.getMsg());
    //        return map;
    //    }
    
    }
    

    3.2 内置过滤器

    过滤器名字 名称 用途
    anon(匿名) org.apache.shiro.web.filter.authc.AnonymousFilter 公共资源
    authc(身份验证) org.apache.shiro.web.filter.authc.FormAuthenticationFilter
    authcBasic(http基本验证) org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
    logout(退出) org.apache.shiro.web.filter.authc.LogoutFilter
    noSessionCreation(不创建session) org.apache.shiro.web.filter.session.NoSessionCreationFilter
    perms(许可验证) org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
    port(端口验证) org.apache.shiro.web.filter.authz.PortFilter
    rest (rest方面) org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
    roles(权限验证) org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
    ssl (ssl方面) org.apache.shiro.web.filter.authz.SslFilter
    user (用户方面) org.apache.shiro.web.filter.authc.UserFilter

    4. Shiro+JWT 的使用,

    实现无session ,前后端分离

    1.用户登陆之后,使用密码对账号进行签名生成并返回token并设置过期时间;

    2.将token保存到本地,并且每次发送请求时都在header上携带token。

    3.shiro过滤器拦截到请求并获取header中的token,并提交到自定义realm的doGetAuthenticationInfo方法。

    4.通过jwt解码获取token中的用户名,从数据库中查询到密码之后根据密码生成jwt效验器并对token进行验证。

    展开全文
  • shiro的RequiresPermissions注解使用

    万次阅读 2017-07-11 10:21:53
    权限控制是shiro最核心的东西 Shiro权限声明通常是使用以冒号分隔的表达式。一个权限表达式可以清晰的指定资源类型,允许的操作,可访问的数据。同时,Shiro权限表达式支持简单的通配符,可以更加灵活的进行权限...
  • 如题,希望这篇文章能解答你对 shiro 的一些疑问。
  • SpringBoot2.0集成Shiro

    万次阅读 多人点赞 2019-01-28 17:14:08
    最近搞了下shiro安全框架,网上找了好多篇博客,感觉要么都是复制粘贴,要么就是错误百出。至于稍微讲解一下为什么要这么做,就更别说了。这篇文章就教大家如何将 Shiro 整合到 SpringBoot 中,并且避开一些小坑,...
  • Shiro】Apache Shiro架构之实际运用(整合到Spring中)

    万次阅读 多人点赞 2016-07-06 21:05:38
    写在前面:如题,这篇博文主要是总结一下如何将Shiro运用到实际项目中,本来准备将Shiro整到Spring中就行了,后来想想既然要整,就索性把SpringMVC和MyBatis也整进去吧,整个比较完整的,也能帮助更多的初学者。...
  • Shiro Review——Shiro介绍

    千次阅读 2016-06-09 13:06:09
    一,Shiro整体介绍 shiro是apache的一个开源框架,是一个权限管理的框架,实现 用户认证、用户授权。 spring中有spring security (原名Acegi),是一个权限框架,它和spring依赖过于紧密,没有shiro使用简单。shiro...
  • shiro基础

    千次阅读 多人点赞 2019-05-15 09:57:14
    shiro基础 文章目录shiro基础一. Shiro框架简介二. Shiro框架代码流程:三. Shiro的适用范围:四. 基础代码五. 整合ssm框架,使用shiro自带的数据库六. 自定义数据库 一. Shiro框架简介 shiro:是一个轻量级的安全...
  • Shiro系列-Shiro简介

    千次阅读 2019-10-26 12:32:41
      Apache Shiro是一个Java安全框架,现在在很多的场景下使用Shiro的人越来越多。因为它与Spring Security 相比较来说相对比较简单,从功能上来讲也没有Spring Security那么强大,但是在实际的工作和开发中并不是...
  • Shiro学习

    千次阅读 2018-10-28 12:20:44
    Shiro】Apache Shiro架构之身份认证(Authentication) 【Shiro】Apache Shiro架构之权限认证(Authorization) 【Shiro】Apache Shiro架构之集成web 【Shiro】Apache Shiro架构之自定义realm 【Shiro】Apache ...
  • shiro讲解之 Shiro Filter

    2017-10-26 17:02:52
    shiro讲解之 Shiro Filter本章节将详细说明Shiro Filter。概念 什么是Shiro FilterShiro 提供了与 Web 集成的支持,其通过一个ShiroFilter 入口来拦截需要安全控制的URL,然后进行相应的控制。 ShiroFilter 类似于如...
  • shiro授权

    万次阅读 2016-05-22 10:39:42
    shiro授权shiro授权,分享牛系列,分享牛专栏,分享牛。shiro授权原理,shiro授权分析。shiro授权1.1 授权流程 1.2 授权方式Shiro 支持三种方式的授权: 编程式:通过写if/else 授权代码块完成:Subject ...
  • shiro的概述

    千次阅读 2020-12-24 12:50:05
    1, 什么是shiro Apache Shiro 是Java 的一个安全框架。Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE 环境,也可以用在JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与Web ...
  • shiro架构

    千次阅读 2016-05-22 10:10:42
    shiro介绍 1.1 什么是shiro分享牛系列,分享牛专栏,分享牛。shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用...
  • Shiro详解

    千次阅读 多人点赞 2018-08-07 08:21:31
    Shiro 简介 简介 • Apache Shiro 是 Java 的一个安全(权限)框架。 • Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE 环境,也可以用在 JavaEE 环境。 • Shiro 可以完成:认证、授权、加密、...
  • 初识Shiro

    千次阅读 2017-03-19 14:08:08
    Shiro是Apache基金会下的一个开源安全框架,提供了身份验证、授权、密码学和会话管理等功能,Shiro框架不仅直观易用,而且也能提供健壮的安全性,另外一点值得说的是Shiro的前身是一个始于2004的开源项目JSecurity,...
  • shiro面试

    千次阅读 2019-05-26 21:26:05
    什么是shiro shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证、权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。 Shiro我的理解: Shiro就是自定义...
  • Shiro权限控制+整合shiro

    万次阅读 多人点赞 2019-03-02 13:17:56
    Shiro权限控制 0.1传统的权限认证方式 特点:为每个人单独的分配权限模块,能够实现权限控制,但是当公司人员庞大之后,非常难管理 上述权限控制如何设计表? 关系:员工和菜单权限的关系:多对多 员工id 菜单...
  • shiro 简介

    千次阅读 2019-09-18 19:23:31
    一.What is Apache Shiro? shiro 可以在command line、mobile application、web 中使用的安全框架,其中有4个核心组件。 Authentication: 认证(校验用户身份,常叫做登录) Authorization:授权(可简单理解为授权登录...
  • 第一个可能配置文件:shiroConfig.java没加这个 /** * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 * 配置以下两个bean...
  • shiro认证

    千次阅读 2016-05-22 10:27:20
    shiro认证1.1 认证流程分享牛系列,分享牛专栏,分享牛。shiro认证分析,shiro认证原理分析。 1.2 入门程序(用户登陆和退出)1.2.1 创建java工程jdk版本:1.7.0_72eclipse:elipse-indigo1.2.2 加入shiro-...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 53,547
精华内容 21,418
关键字:

shiro