• 【shiro-安全框架】入门基础学习


    安全框架常见的由两种,一种是shiro,另一种是spring security。

    shiro相较于后者更加简洁,spring security可以无缝的整合spring框架

    0 shiro介绍

    Apache Shiro是Java 的一个安全框架。Shiro可以非常容易的开发出足够好的应用,其不仅可以用在]avaSE环境,也可以用在]avaEE环境。Shiro可以帮助我们完成:认证【登陆】、授权【权限】、加密【密码】、会话管理、与Web集成、缓存等。

    1 shiro中常见的概念

    在这里插入图片描述

    Subject:

    Subject主体,外部应用与Subject进行交互,Subject将用户作为当前操作的主体,这个主体:可以是一个通过浏览器请求的用户,也可能是一个运行的程序。Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过Subject进行认证授,而Subject是通过SecurityManager安全管理器进行认证授权。

    SecurityManager:

    SecurityManager权限管理器,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等。SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。SecurityManager是一个接口,继承了Authenticator,Authorizer,SessionManager这三个接口。

    Authenticator:

    Authenticator即认证器,对用户登录时进行身份认证。

    Authorizer:

    Authorizer授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。

    Realm(数据库读取+认证功能+授权功能实现):

    Realm领域,相当于datasource数据源,SecurityManager进行安全认证需要通过Realm获取用户权限数据。

    比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。

    注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。

    SessionManager:

    SessionManager会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。

    SessionDAO:

    SessionDAO即会话dao,是对session会话操作的一套接口。

    比如:可以通过jdbc将会话存储到数据库也可以把session存储到缓存服务器。

    CacheManager:

    cacheManager缓存管理,将用户权限数据存储在缓存,这样可以提高性能。

    Cryptography:

    Cryptography密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。

    2 快速入门shiro

    2.1 认证的流程

    在这里插入图片描述

    流程如下:
    
    首先调用 Subject.login(token)进行登录,其会自动委托给 Security Manager,调用之前必须通过 Securityutils.setSecurityManager()设置;
    
    SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;
    
    Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现;
    
    Authenticator可能会委托给相应的AuthenticationStrategy 进行多Realm身份验证,默认ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;
    
    Authenticator会把相应的 token传入 Realm,从 Realm获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。
    
    (1)在maven项目中引入相关依赖
    <dependency>
        <groupId>org.apache.shirogroupId>
        <artifactId>shiro-coreartifactId>
        <version>${shiro-version}version>
    dependency>
    
    (2)创建.ini文件模拟数据
    [users]===表示user表
    admin=123456
    zs=123456
    
    (3)编写代码测试
    package test;
    
    public class test01 {
        public static void main(String[] args) {
            
            //创建SecurityManager对象。
            DefaultSecurityManager securityManager = new DefaultSecurityManager();
            
            //为SecurityManager指定Realm对象,这里要使用classpath指定编译后的资源文件
        	IniRealm realm=new IniRealm("classpath:shiro.ini");
            
            //通过SecurityUtil把SecurityManager设置上下文可用。生效问题。
            SecurityUtils.setSecurityManager(securityManager);
            
            //获取Subject对象
            Subject subject = SecurityUtils.getSubject();
            
            //完成认证功能AuthenticationToken:该类把账号和密码封装到该类中
            UsernamePasswordToken token=new UsernamePasswordToken("admin","123456");
            try {
                subject.login(token);
                System.out.println("登录成功";
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("账号或密码错误");
            }
           
            System.out.println("是否认证成功:"+subject.isAuthenticated());
            //退出登录
            subject.logout();
            System.out.println("是否认证成功:"+subject.isAuthenticated());                       
        }
    }
    
    
    

    2.2 授权

    一定是在认证成功后才会执行的代码。

    ini文件:

    [users]
    #账号=密码,角色
    admin=123456,admin
    ldh=123456
    
    [roles]---权限
    #角色=权限
    admin=user:query,user:delete,user:save,user:update
    role1=user:query,user:export
    
    package test;
    
    public class test02 {
        public static void main(String[] args) {
            
            //创建SecurityManager对象。
            DefaultSecurityManager securityManager = new DefaultSecurityManager();
            
            //为SecurityManager指定Realm对象,这里要使用classpath指定编译后的资源文件
        	IniRealm realm=new IniRealm("classpath:shiro.ini");
            
            //通过SecurityUtil把SecurityManager设置上下文可用。生效问题。
            SecurityUtils.setSecurityManager(securityManager);
            
            //获取Subject对象
            Subject subject = SecurityUtils.getSubject();
            
            //完成认证功能AuthenticationToken:该类把账号和密码封装到该类中
            UsernamePasswordToken token=new UsernamePasswordToken("admin","123456");
            try {
                subject.login(token);
                System.out.println("登录成功";
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("账号或密码错误");
            }
            
            //判断当前用户是否具有某个权限
            boolean permitted = subject.isPermitted("user:query");
            System.out.println("判断当前用户是否具有User:query权限:" + permitted);
               
            //判断当前用户是否同时具有权限,与关系
            boolean permittedAll = subject.isPermittedAll("user:query", "user:save");
            System.out.println("判断当前用户是否同时具有user:query和user:save权限:"+permittedAll);
                                   
            //只要有一个权限就输出一个true,或关系
            boolean[] permitted1 = subject.isPermitted("user:query", "user:export");
            System.out.println(Arrays.toString(permitted1));
        }
    }
                                
    
    

    上面写的代码中所有的数据都是基于ini文件中得来,实际开发中,数据的来源应该是从数据库中拿到的。这时我们可以自定义realm,可以帮我们完成相应的认证功能。

    3.自定义realm请求

    3.1 认证方法

    (1)User实体类
    package entity;
    
    @Data
    public class User {
        private Integer id;
        private String username;
        private String password;
        private String realname;
        
    }
    
    (2)service代码
    package service;
    
    public class UserService {
        public User findByUsername(String username) {
        	if("admin".equals(username)) {
                return new User(1,username,"123456","管理员");
            } else if ("YKQ".equals(username)) {
                return new User(2, username, "123456", "ykq");
            } else {
                return null;
            }
        }
    } 
    
    (3)创建一个类并继承AuthorizingRealm
    package realm;
    
    public class MyRealm extends AuthorizingRealm {
        
        private UserService userService = new UserService();
        
        //授权时执行该方法
    	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            System.out.println("授权方法===");
            return null;
        }
        
        //认证时执行该方法
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException{
            System.out.println("认证方法===");
            
            //获取登录者的账号
            String username = authenticationToken.getPrincipal.toString();
            
            //根据账号查询数据库信息
            User user = userService.findByUsername(username);
            if(user != null) {
                //Object principal,数据库中的账号
                //Object credentials,密码
                //String realmName,realmName名称,随便起
                //密码的比对交给了 SimpleAuthenticationInfo 这个类
                SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(username, user.getPassword, this.getName);
                return info;
            }
            
        	return null;
        }
    }
    
    (4)修改securityManager的realm对象
    package test;
    
    public class test03 {
        public static void main(String[] args) {
            
            //创建SecurityManager对象。
            DefaultSecurityManager securityManager = new DefaultSecurityManager();
            
            //为SecurityManager指定Realm对象,这里要使用classpath指定编译后的资源文件
        	MyRealm realm=new MyRealm();
            securityManager.setRealm(realm);
            //通过SecurityUtil把SecurityManager设置上下文可用。生效问题。
            SecurityUtils.setSecurityManager(securityManager);
            
            //获取Subject对象
            Subject subject = SecurityUtils.getSubject();
            
            //完成认证功能AuthenticationToken:该类把账号和密码封装到该类中
            UsernamePasswordToken token=new UsernamePasswordToken("admin","123456");
            try {
                subject.login(token);
                System.out.println("登录成功";
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("账号或密码错误");
            }
            
            //判断当前用户是否具有某个权限
            boolean permitted = subject.isPermitted("user:query");
            System.out.println("判断当前用户是否具有User:query权限:" + permitted);
               
            //判断当前用户是否同时具有权限,与关系
            boolean permittedAll = subject.isPermittedAll("user:query", "user:save");
            System.out.println("判断当前用户是否同时具有user:query和user:save权限:"+permittedAll);
                                   
            //只要有一个权限就输出一个true,或关系
            boolean[] permitted1 = subject.isPermitted("user:query", "user:export");
            System.out.println(Arrays.toString(permitted1));
        }
    }
                  
    

    3.2 完成自定义授权

    package realm;
    
    public class MyRealm extends AuthorizingRealm {
        
        private UserService userService = new UserService();
        
        //只有登录者登录成功时才会执行,授权时执行该方法
    	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            System.out.println("授权方法===");
            //获取当前登录者的账号
            String username = (String)principalCollection.getPrimaryPrincipal();
            
            //根据账号查询当前账号具有的权限
            List<String> permissions = userService.findPermissionByUsername(username);
            
            if(permissions.size() > 0){
                SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
                info.addStringPermissions(permissions);
                return info;
            }
            return null;
        }
        
        //认证时执行该方法
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException{
            System.out.println("认证方法===");
            
            //获取登录者的账号
            String username = authenticationToken.getPrincipal.toString();
            
            //根据账号查询数据库信息
            User user = userService.findByUsername(username);
            if(user != null) {
                //Object principal,数据库中的账号
                //Object credentials,密码
                //String realmName,realmName名称,随便起
                //密码的比对交给了 SimpleAuthenticationInfo 这个类
                SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(username, user.getPassword, this.getName);
                return info;
            }
            
        	return null;
        }
    }
    
    package service;
    
    public class UserService {
        public User findByUsername(String username) {
        	if("admin".equals(username)) {
                return new User(1,username,"123456","管理员");
            } else if ("YKQ".equals(username)) {
                return new User(2, username, "123456", "ykq");
            } else {
                return null;
            }
        }
        
        public List<String> findPermissionByUsername(String username) {
            List<String> list=new ArrayList<String>();
            if("admin".equals(username)){
                list.add("user:query");
                list.add("user:save");
                list.add("user:delete");
                list.add("user:update");l
            } else if ("YKQ".equals(username)){
                list.add("user:query");
                list.add("user:export");
            }
            return list;
        }
    } 
    

    4 密码加密

    4.1 加密演示

    在这里插入图片描述

    shiro默认采用md5加密,我们可以写一个单元测试来体现这一点。

    public class Test04 {
        public static void main(String[] args) {
            //Md5Hash中的参数表示要加密的明文
            Md5Hash md5Hash = new Md5Hash("123456");
            //输出加密后的密文
            System.out.println(md5Hash);
        }
    }
    

    解密网址会把常见的加密的内容搜集起来—造成了数据不安全。我们可以使用加盐加密来避免这一点。

    public class Test04 {
        public static void main(String[] args) {
            
            //加盐加密,第二个参数就是 盐值 ,这个盐不要泄露
            Md5Hash md5Hash1=new Md5Hash("123456", "aaa");
            
            //输出加密后的密文 
            System.out.println(md5Hash);
        }
    }
    

    我们还可以使用加盐加密,同时设置加密次数

    public class Test04 {
        public static void main(String[] args) {
            
            //加盐加密,同时设置加密次数, 第三个参数就是 加密次数
            Md5Hash md5Hash1=new Md5Hash("123456", "aaa", 1024);
            
            //输出加密后的密文 
            System.out.println(md5Hash1);
        }
    }
    

    4.2 shiro中使用md5加密

    (1)设置realm加密器
    package test;
    
    public class test05 {
        public static void main(String[] args) {
            
            //创建SecurityManager对象。
            DefaultSecurityManager securityManager = new DefaultSecurityManager();
            
            //为SecurityManager指定Realm对象,这里要使用classpath指定编译后的资源文件
        	MyRealm realm=new MyRealm();
            
            //创建Hash加密器
            HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
            matcher.setHashAlgorithmName("MD5");//设置加密方式
            MD5matcher.setHashIterations(1024);//散列的次数1024
            myRealm.setcredentialsMatcher(matcher);//设置realm的密码匹配器
            
            securityManager.setRealm(realm);
            //通过SecurityUtil把SecurityManager设置上下文可用。生效问题。
            SecurityUtils.setSecurityManager(securityManager);
            
            //获取Subject对象
            Subject subject = SecurityUtils.getSubject();
            
            //完成认证功能AuthenticationToken:该类把账号和密码封装到该类中
            UsernamePasswordToken token=new UsernamePasswordToken("admin","123456");
            try {
                subject.login(token);
                System.out.println("登录成功";
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("账号或密码错误");
            }
            
            //判断当前用户是否具有某个权限
            boolean permitted = subject.isPermitted("user:query");
            System.out.println("判断当前用户是否具有User:query权限:" + permitted);
               
            //判断当前用户是否同时具有权限,与关系
            boolean permittedAll = subject.isPermittedAll("user:query", "user:save");
            System.out.println("判断当前用户是否同时具有user:query和user:save权限:"+permittedAll);
                                   
            //只要有一个权限就输出一个true,或关系
            boolean[] permitted1 = subject.isPermitted("user:query", "user:export");
            System.out.println(Arrays.toString(permitted1));
        }
    }
                
    

    在上面的创建Hash加密器中,我们并没有使用加盐值的方式。原因是在这里设置不合适,如果在这里设置盐值的话,就不能再改变盐值了。应该每个账号都应该有自己的盐值。

    我们可以再realm中设置盐值。

    (2)修改自定义的realm参数(多了盐值)
    package realm;
    
    public class MyRealm extends AuthorizingRealm {
        
        private UserService userService = new UserService();
        
        //只有登录者登录成功时才会执行,授权时执行该方法
    	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            System.out.println("授权方法===");
            //获取当前登录者的账号
            String username = (String)principalCollection.getPrimaryPrincipal();
            
            //根据账号查询当前账号具有的权限
            List<String> permissions = userService.findPermissionByUsername(username);
            
            if(permissions.size() > 0){
                SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
                info.addStringPermissions(permissions);
                return info;
            }
            return null;
        }
        
        //认证时执行该方法
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException{
            System.out.println("认证方法===");
            
            //获取登录者的账号
            String username = authenticationToken.getPrincipal.toString();
            
            //根据账号查询数据库信息
            User user = userService.findByUsername(username);
            if(user != null) {
                //Object principal,数据库中的账号
                //Object credentials,密码
                //ByteSource credentialsSalt:使用的盐
                //String realmName,realmName名称,随便起
                //密码的比对交给了 SimpleAuthenticationInfo 这个类
                //由于ByteSource是个接口 我们不可以直接new出来,我们要么new它的子类,要么通过工厂来拿到它
                ByteSource salt=ByteSource.Util.bytes( string: "aaa");//盐值未来应该存在数据库中,从数据库拿到user的时候一并把盐值拿到 user.getSalt
                
                //shiro会帮我们进行密码的比对。shiro会先对你的密码进行加密,再和数据库中的加密后的密码进行比较
                SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(username, user.getPassword, salt, this.getName);
                return info;
            }
            
        	return null;
        }
    }
    
    (3)修改数据库的明文密码,改为加密后的密码
  • 相关阅读:
    java计算机毕业设计舞蹈网站源代码+数据库+系统+lw文档
    注解【元数据,自定义注解等概念详解】(超简单的好吧)
    Windows安装MySQL及Python操作MySQL数据库脚本实例详解
    delphi中Message消息的使用方法
    设计模式学习(二十四):Spring 中使用到的设计模式
    elasticsearch的docker安装与使用
    高速无源链路阻抗匹配套路
    Cesium初学者:如何在本地查看示例、文档
    Runc 漏洞(CVE-2021-30465)离线修复
    常见的一些威胁情报分析平台
  • 原文地址:https://blog.csdn.net/qq_38594872/article/details/126960439