栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Shiro

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Shiro

Shiro

RBAC是你公司内部用的系统:只有你公司的去用,如果你公司的人去用,如果找一个新的开发人员的话,这个人就需要花很长时间去学习你的权限管理系统

Shiro一个开源的安全管理框架:开源框架就意味这着很多人都在用

一、主要组件
  1. subject:主体,委托方,表示当前操作的用户
  2. SecurityManager:是shiro的核心,类似于SpringMVC的DispathchServlet
  3. Realm:数据源,负责查询数据
  4. Authenticator:认证器,用于的登录是否成功在此验证
  5. Authorizer:授权器,用户检测用户的权限
二、准备环境

配置依赖


    commons-logging
    commons-logging
    1.1.3


    org.apache.shiro
    shiro-core
    1.5.2



    junit
    junit
    4.13.2


    org.projectlombok
    lombok
    1.16.22
    provided

二、常见异常

账号错误:unknowAccountException

密码错误:IncorrectCredentialsException

根据token带的参数,去获取relm中的account…的账号信息,为空就抛unknowAccountException,在判断account对象中的密码是否正确,错误就抛in…Exception异常。

三、登录认证快速入门

使用shiro-authc.ini文件来存储用户信息

#用户的身份、凭据
[users]
zhangsan=555 
lanxw=666

测试类的编写

public class test{
    @Test
    public void testIni(){
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        IniRealm realm = new IniRealm("classpath:shiro-authc.ini");
        securityManager.setRealm(iniRealm);
        SecurityUtils.setSecurityManager(securityManager);
        Studject subject = SecurityUtils.getSubject();
        UsernamePassworkToken token = new UsernamePassworkToken("账号","密码");
        shbject,login(token);
    }
}
四、自定义Realm

​ 因为在实际开发过程中,数据不可能存储在shiro-authc.ini文件中,而是存储中数据库中

​ 实现流程

  1. 自定义realm,继承 AuthorizingRealm

    1. 会根据token.getPrincipal( )方法获取到账号的名

    2. 调用数据库的查询方法根据username查询出用户对象

    3. 如果没数据,即账号为false

    4. 有数据,把数据封装到Info对象中,返回对象 new …Info(user,use.password,getName( ) );

    5. 会把…Info对象存入到Map中

      ​ key = username

      ​ value = Info User,user.getPassword ,getName()

    public class MyRealm extends AuthorizingRealm {
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            return null;
        }
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            String username = ((String) token.getPrincipal());//获取账号
            User user = DataMapper.getUserByName(username);//到数据库中查找
            if (user==null) {//判断账号是否正确
                return null;
            }
            String slat = user.getSlat();//盐
            return new SimpleAuthenticationInfo(user,user.getPassword(), ByteSource.Util.bytes(slat),getName());
        }
    }
    
  2. 在编写测试类

    1. 创建安全管理器对象 securityManager
    2. 创建自定义Realm对象 realm
    3. 把 realm 设置到 securityManager 中
    4. 通过SecurityUtils的静态方法,把 securityManager 设置到当前环境中(上下文)
    5. 通过SecurityUtils的静态方法获取 Subject对象 subject
    6. 创建token(令牌)对象,封装了账号,密码
    7. 调用subject的login(token)进行登录的验证
public class test{
    @Test
    public void testIni(){
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        MyRealm realm = new MyRealm();
        securityManager.setRealm(realm);
        SecurityUtils.setSecurityManager(securityManager);
        Studject subject = SecurityUtils.getSubject();
        UsernamePassworkToken token = new UsernamePassworkToken("账号","密码");
        shbject,login(token);
    }
}
五、授权管理

在用户登录之后,根据用户的名字,在授权器中判断用户的是否有角色和权限

大致上与认证管理相同

  1. 在自定义realm中,在 doGetAuthorizationInfo()方法中获取用户的拥有的角色,权限
  2. 返回使用 SimpleAuthorizationInfo 对象封装用户的角色信息,权限信息
 @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        User user = (User) principals.getPrimaryPrincipal();
        List permissions = DataMapper2.getPermissionByName(user.getUsername());
        List roles = DataMapper2.getRoleByName(user.getUsername());
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(permissions);
        info.addRoles(roles);

        return info;
   }

api: 常用

subject.hasRole("hr");//Boolean

subject.isPermitted("user:delete1");//Boolean

subject.checkPermission(String permission);//void

subject.checkRole(String role);//void

补充:

  1. getName( )的作用,支持多数据源,支持多种方式登录
  2. check方法,如果没有该内容等,会抛出异常
  3. 在对 SimpleAuthenticationInfo 对象封装 盐 参数时,要设置成 ByteSource.Util.bytes(user.getSlat())
六、加密

shiro也支持密码加密,可以提高我们系统的安全性

  1. 大致流程
    1. 获取token对象(封装了账号和密码)
    2. 在凭证匹配器中获取加密算法,加密次数,在Info中获取到盐的值
    3. 将token的密码与第二点的信息,生成一个密文
    4. 将密文与数据库中的密文密码继续匹配是否密码正确
// 第二点可以理解如下
 @Test
    public void testShrio5(){
        
        Md5Hash md5Hash = new Md5Hash("888", "c4eb61", 3);
        System.out.println(md5Hash.toString());
    }
  1. realm代码
public class MyRealm3 extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //principals.getPrimaryPrincipal()
        //其实就是在认证时放入SimpleAuthenticationInfo的第一个参数
        User user = (User) principals.getPrimaryPrincipal();
       	//查询到的信息仅供shiro检验使用
        List permissions = DataMapper2.getPermissionByName(user.getUsername());
        List roles = DataMapper2.getRoleByName(user.getUsername());
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(permissions);
        info.addRoles(roles);

        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = ((UsernamePasswordToken) token).getUsername();
        User user = DataMapper2.getUserByName(username);
        if (user==null) {
            return null;
        }
        return new SimpleAuthenticationInfo(user,user.getPassword(), ByteSource.Util.bytes(user.getSlat()),this.getName());
    }
}
  1. test代码
 @Test
    public void testRealm4() {
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        //凭证匹配器,设置加密算法
        HashedCredentialsMatcher md5 = new HashedCredentialsMatcher("md5");
        //设置加密次数
        md5.setHashIterations(3);
        MyRealm3 realm3 = new MyRealm3();
        //把凭证匹配器注入到realm中,由Realm管理凭证匹配器
        realm3.setCredentialsMatcher(md5);
        securityManager.setRealm(realm3);
        SecurityUtils.setSecurityManager(securityManager);
        UsernamePasswordToken token = new UsernamePasswordToken("yl", "888");
        Subject subject = SecurityUtils.getSubject();
        subject.login(token);
        System.out.println("subject.isAuthenticated() = " + subject.isAuthenticated());
        System.out.println("subject.hasRole("hr") = " + subject.hasRole("hr"));
        System.out.println(subject.isPermitted("user:delete1"));
        System.out.println(subject.isPermitted("user:delete"));

    }
七、集成SpringBoot 一、准备工作
  1. 删除
    1. 删除WebConfig
    2. 删除拦截器CheckLoginInterceptor和CheckPermissionInterceptor
    3. 删除UserContext和RequiredPermission
    4. 删除LoginController中的logout方法,注释login方法体内容
  2. 配置pom
1.7.1
2.0.0



    org.apache.shiro
    shiro-core
    ${shiro.version}



    org.apache.shiro
    shiro-spring
    ${shiro.version}



    org.apache.shiro
    shiro-ehcache
    ${shiro.version}



    com.github.theborakompanioni
    thymeleaf-extras-shiro
    ${thymeleaf.extras.shiro.version}

二、Shiro的工作
  1. Shiro实现拦截靠的是过滤器,而不是拦截器,因为拦截器强依赖于SpringMVC,把安全管理器关联到过滤器中,我们配置的过滤器只是起到拦截的作用,真正处理请求的是它内部的filter,也可能会调用安全管理器,是否调用靠所设置的内部过滤器
三、登录认证

由自定义Realm来提供数据,给认证器的进行校验

  1. 自定义Realm类,继承AuthorizingRealm
  2. 创建配置类,在配置类中创建对象
  3. 创建 securityManager 对象,给对象设置属性,再放入spring容器中
  4. 把 securityManager 关联到过滤器中
  5. 在处理器中使用subject调用登录方法,用异常抛出内容的处理
  6. 如果登录成功,会是subject进入登录状态
  7. 后面进入来的 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor( @Qualifier("securityManager") SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; }
    1. 贴注解 在需要权限的方法上贴上注解,并给上对应的权限表达式即可

    2. 统一处理异常

    3. 标签认证(少用)

      因为现在最多的前后端分离的项目,前端页面内不能出现后端代码

      1. 添加bean
    @Bean
    public ShiroDialect shiroDialect()
    {
        return new ShiroDialect();
    }
    
    		2. 添加约束头
    
    
    
    1. 使用标签 ThymeLeaf标签文档
    
         添加
    
    
    五、缓存处理

    无关项目规模的大小,都需要进行缓存的处理,从而减少对数据库的压力

    缓存:把数据存与内存中,需要数据时,会先到缓存中找。

    继承EhCache

    在resource目录下新建ehcache/ehcache-shiro.xml文件,内容如下:

    
    
        
        
    
    

    配置属性说明

    参数说明
    maxElementsInMemory缓存对象最大个数
    eternal对象是否永久有效,一但设置了,timeout 将不起作用。
    timeToIdleSeconds对象空闲时间,指对象在多长时间没有被访问就会失效(单位:秒)。仅当 eternal=false 对象不是永久有效时使用,可选属性,默认值是 0,也就是可闲置时间无穷大。
    timeToLiveSeconds对象存活时间,指对象从创建到失效所需要的时间(单位:秒)。仅当 eternal=false 对象不是永久有效时使用,默认是 0,也就是对象存活时间无穷大。
    memoryStoreEvictionPolicy当达到 maxElementsInMemory 限制时,Ehcache 将会根据指定的策略去清理内存。

    缓存淘汰策略:

    策略说明
    LRU默认,最近最少使用,距离现在最久没有使用的元素将被清出缓存
    FIFO先进先出, 如果一个数据最先进入缓存中,则应该最早淘汰掉
    LFU较少使用,意思是一直以来最少被使用的,缓存的元素有一个hit 属性(命中率),hit 值最小的将会被清出缓存

    添加对应的配置

    @Bean
    public EhCacheManager getEhCacheManager()
    {
        EhCacheManager em = new EhCacheManager();
        em.setCacheManagerConfigFile("classpath:ehcache/ehcache-shiro.xml");
        return em;
    
    }
    
    @Bean
    public EmployeeRealm userRealm(EhCacheManager cacheManager)
    {
        EmployeeRealm employeeRealm = new EmployeeRealm();
        employeeRealm.setCacheManager(cacheManager);
        return employeeRealm;
    }
    
    六、加盐处理

    思路:

    ​ 把用户添加是的密码进行加盐加密,生成密文1,存到数据库中,在用户进行登录验证时,对用户携带来的密码,进行加盐加密处理,生成密文2,把数据库中的密文1与生成的密文2进行对比,验证密码是否正确。

    设计:

    1. 在employee表中添加salt字段
    2. 在Employee类中添加salt属性和字段
    3. 修改Sql语句,select,insert,添加上salt
    4. 在登录的业务层中,对明文密码进行加密处理,与数据库中的密文进行对比。
    5. 添加,在添加用户信息时,通过UUID的截取获取盐,再对面进行加密处理生成密文,存入数据库中。

    第四点的实现前,需要配置凭证匹配器,交给realm进行管理,这种设计,可以实现由用户提供账号,密码,其他登录所需的参数都能在Info中获取。

     @Override
        public void insert(Employee employee, Long[] roleIds) {
            String salt = UUID.randomUUID().toString().substring(0, 5);
            Md5Hash md5Hash = new Md5Hash(employee.getPassword(), salt, 3);
            String newPassword = md5Hash.toString();
            employee.setPassword(newPassword);
            employee.setSalt(salt);
            employeeMapper.insert(employee);
            if (roleIds!=null && roleIds.length>0){
                employeeMapper.insertRelation(employee.getId(), roleIds);
            }
        }
    
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/924545.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号