Shiro学习笔记

Shiro学习笔记,小白记录学习过程,大佬绕道

一.什么是Shiro

        Shiro和Security一样,都是一个安全框架。可以帮我们实现认证、授权、MD5加密、缓存等功能。

        Shiro中有三大要素:Subject、Security Manager、Realm。

  • Subject:当前主体,可以理解为用户。
  • Security Manager:安全管理器,管理所有的Subject。
  • Realm:具体实现授权、认证等操作。

        三者之间的关系为:

        1.创建Realm且继承AuthorizingRealm类,并实现其中的doGetAuthorizationInfo(授权)和doGetAuthenticationInfo(认证)方法。
        2.创建安全管理器,并将步骤1中创建的realm注入安全管理器中。
        3.创建Subject。并设置其安全管理器为步骤2中所创建的安全管理器。

二. Shiro三部曲

        Realm是实现认证、授权以及数据读取的关键,也是我们构建Shiro三部曲的第一步。我们要自己创建Realm,并继承AuthorizingRealm类,再重写其中的认证和授权两个方法。至于怎么实现这两个方法,可以根据我们实际的业务需求。

2.1 创建并得到Realm

创建Myrealm类:

public class MyRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null; 
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        return null;
    }
}

获得Myrealm类:

创建好一个Realm类之后,我们需要获得一个这个类的对象,并且将其注入Security Manager中。
创建ShiroConfig.java文件,并创建get函数。

    @Bean  //要添加Bean注解,方便容器识别
    public MyRealm getMyRealm(){
        return new MyRealm();
    }

2.2 设置安全管理器

        Security Manager(安全管理器)是管理所有Subject的。安全管理器也是Subject和Realm的中间枢纽。当用户提交认证、授权请求的时候,Subject会托管给安全管理器,安全管理器会去调用Realm中的认证和授权方法进行认证授权。

        在上述ShiroConfig.java文件中,创建安全管理管理器获得方法。

    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getUserRealm") UserRealm userRealm){ //将2.1中获得的realm传递过来,并注入
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);
        return securityManager;
    }

2.3 设置Subject

        虽然Subject是Shiro的一大要素,但是在编码时,Subject需要写为ShiroFilterFactoryBean。因此在上述创建的ShiroConfig.java文件中创建ShiroFilterFactoryBean获得方法。

@Bean
//同样将2.2中获得的安全管理器通过参数的方式传递过来
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager securityManager){ 
    
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        return bean;
    }

        通过上述三个步骤,一个基本的框架已经搭好,后续可以通过完善Realm类中的方法以及修改ShiroConfig中三部曲的具体信息进行认证、授权以及MD5加密和缓存处理。

三.认证

        认证其实说白了就是登陆。之前的登陆是我们自己编写代码实现数据库读取数据,并且匹配判断能否登录。在Shiro中,这一功能Shiro帮我们做了,我们在代码中根本看不到有关账号密码等信息。保证了其安全性。

        Shiro认证实现包括:

        1.编写数据库读取的mapper。
        2.Realm中doGetAuthenticationInfo的具体实现。
        3.编写Controller

        关于如何编写读取数据库的Mapper,在Springboot登陆demo实战中已经详细介绍过了,这里我们已经实现了第一步。直接从第2步开始。

3.1 Realm中doGetAuthenticationInfo的具体实现

public class MyRealm extends AuthorizingRealm {

    @Autowired
    UserServiceImpl userService;  //注入我们的数据读取的类

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

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

		//拿到由账号密码生成的令牌  token
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //通过令牌中的账号去查询,如果查询不到返回null
        User user = userService.findOneByName(token.getUsername());
        if (user == null) {
            return null;
        }
		//查询到了,返回一个SimpleAuthenticationInfo的对象,其中第二个参数是数据库中读取出来的密码
        return new SimpleAuthenticationInfo("", user.getPassword(), "");
    }
}
}

3.2 编写Controller:

@Controller
public class LoginController {

   @RequestMapping("/logging") //获得页面的提交的表单
    public String login(String username, String password, Model model){
       Subject subject = SecurityUtils.getSubject(); //获取当前用户
       //使用页面提交过来的账号和密码生成令牌 token
       UsernamePasswordToken token = new UsernamePasswordToken(username,password);
       try {
           subject.login(token);//subject.login方法会自动调用realm中的认证方法
           return "success";
       } catch (UnknownAccountException e) {
           model.addAttribute("msg","账号不存在");//realm中返回null,这里会自动判断是哪种异常并抛出
           return "index";
       }catch (IncorrectCredentialsException e){
           model.addAttribute("msg","密码错误");
           return "index";
       }
   }

四.授权

        为什么需要授权操作:

        1.比如用户并没有登录,但是他输入网址“localhost:8080/登陆成功页面“之后会自动跳转至登陆成功的页面。
        2.根据用户权限的不同,页面中显示的内容不一样。比如管理员登陆成功后可能有4个按钮,但是普通用户可能只有1个功能按钮。

        由于上述需求,所以需要根据用户的权限,来进行不一样的授权操作。Shiro的授权操作包括:

        1.Shiro三部曲中的ShiroFilterFactoryBean的编写。
        2.Realm中doGetAuthorizationInfo授权方法的实现。

4.1 Shiro三部曲中的ShiroFilterFactoryBean的编写

        在刚才我们创建Shiro三部曲的时候,只是编写了大致框架:即获得realm、获得安全管理器、将realm注入安全管理器、获得Subject、设置Subject的安全管理器等步骤,并没有对其中的内容进行详细的编写。

        这里通过调用subject.setFilterChainDefinitionMap()方法进行权限控制。该方法要求的参数是一个hashmap类型。

@Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        Map<String, String> mymap = new LinkedHashMap(); //new一个hashmap
        //存储一些值,这里表示success页面必须要有权限[login]才可以访问
        mymap.put("/success","perms[login]");
        bean.setFilterChainDefinitionMap(mymap);//将map通过setFilterChainDefinitionMap方法传入
        return bean;
    }

        通过上述步骤,我们已经对某些页面设置了某些权限,下面就要通过实现realm中的认证方法,进行认证了。

4.2 Realm中doGetAuthorizationInfo授权方法的实现

        还记得在认证方法重写的过程中,我们最后如果认证成功将返回一个SimpleAuthenticationInfo对象,其中第二个参数是数据库读取出来的密码,其实第一个参数Principal是当前用户。所以我们可以通过认证方法返回值SimpleAuthenticationInfo对象中的Principal参数,获得当前的用户。

  @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    	//通过SecurityUntils中的getSubject方法获得当前对象
        Subject subject = SecurityUtils.getSubject();
        //new一个SimpleAuthorizationInfo 对象
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //通过认证方法返回值中的Principal参数获得当前对象,并获得对象的权限字段
        User currentUser = (User) subject.getPrincipal();
        //将其添加到SimpleAuthorizationInfo中
        info.addStringPermission(currentUser.getPermission());
        return info;
    }

        通过上述方法,我们已经实现了需要授权才可以访问某些页面的方法。

五.Md5盐值加密

        虽然Shiro自己做密码匹配等一系列操作,但是由于你数据库存储的都是明文密码,所以还是存在一定的安全隐患。所以Shiro支持Md5加密功能,但是因为Md5算法公开,一些人可以通过不断尝试,有几率破解简单密码。所以Shiro也支持Md5盐值加密。
使用Shiro实现Md5盐值加密包括3个步骤:

        1.配置Realm。
        2.修改认证函数。

5.1 配置Realm

        我们获得我们自己创建的realm对象之后,不要着急返回,我们需要对他进行一些设置。

 @Bean
    public UserRealm getUserRealm(){
        UserRealm realm = new UserRealm();
        //默认是生成SimpleCredentialsMaster,为了使用MD5这里声明一个HashCredentialsMaster
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        //告诉它你用的是什么加密算法
        credentialsMatcher.setHashAlgorithmName("md5");
        //设置一下hash散列的次数,默认是一次的
        credentialsMatcher.setHashIterations(1024);
        //将其设为realm的认证器
        realm.setCredentialsMatcher(credentialsMatcher);
        return realm;

我们设置了realm的认证管理器是hash,并且设置了认证所使用的hash算法是Md5,并且设置了hash散列的次数为1024。默认是hash散列一次

5.2 修改认证函数

 @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
       
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        User user = userService.findOneByName(token.getUsername());
        if(user == null){
            return null;
        }
        //第一个参数 principal 用户;第二个参数是数据库的密码;第三个参数是 salt。设置方法是ByteSource.Util.bytes();第四个参数是realm的名字
        return new SimpleAuthenticationInfo(user,user.getPassword(), ByteSource.Util.bytes("hky2ymf"),"");
    }

        显而易见,我们只修改了return的SimpleAuthenticationInfo对象中的第三个参数,该参数是告诉Shiro,Md5的盐是多少。像我们使用了Hash认证器以及使用了Md5加密方法,散列了1024次这些信息不用我们手工去传递,因为都已经设置在了realm中。Shiro会自动去传。

六. 缓存

        为什么需要缓存?

        缓存顾名思义就是存储在内存中的东西。举个例子,比如你登陆的时候,程序会去数据库读取信息,然后由Shiro进行认证和授权。但是当你刷新页面的时候,上述步骤会重复。也就是说你每次加载页面的时候都需要去数据库读取数据。当用户量特别大的时候,数据库的压力就很大。

        缓存的目的就是为了缓解数据库的压力。只有我们第一次加载页面的时候,会从数据库中读取数据,在后续加载的时候,Shiro会先去缓存中找,如果缓存中存在,则不访问数据库,否则再去数据库中找。一定程度上降低了数据库的压力。
        Shiro默认支持的缓存是Ehcache,需要导入Shiro-ehcache依赖。

6.1 导入依赖

<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.4.0</version> //这里的版本要和你导入的Shiro依赖版本一样
        </dependency>

6.2 配置realm

        和设置md5一样,都要来配置realm,毕竟realm是核心。

 @Bean
    public UserRealm getUserRealm(){
        UserRealm realm = new UserRealm();
        //默认是生成SimpleCredentialsMaster,为了使用MD5这里声明一个HashCredentialsMaster
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        //告诉它你用的是什么加密算法
        credentialsMatcher.setHashAlgorithmName("md5");
        //设置一下hash散列的次数,默认是一次的
        credentialsMatcher.setHashIterations(1024);
        //将其设为realm的认证器
        realm.setCredentialsMatcher(credentialsMatcher);
        //设置缓存管理器   shiro默认缓存管理器使用的是ehcache
        realm.setCacheManager(new EhCacheManager());
        //开启shiro的缓存
        realm.setCachingEnabled(true);
        //开启认证缓存
        realm.setAuthenticationCachingEnabled(true);
        //开启授权缓存
        realm.setAuthorizationCachingEnabled(true);
        //设置认证缓存名字
        realm.setAuthenticationCacheName("authentication");
        //设置授权缓存名字
        realm.setAuthorizationCacheName("authorization");

        return realm;

    }

        通过设置realm的cachemanager为EhcacheManager、开启Shiro的缓存、开启授权缓存、开启认证缓存就实现了整个Shiro的开启缓存。
        但是这种默认的缓存也有弊端,当服务器断电或者项目重启的时候,还是需要重新访问数据库。为了解决这种情况可以使用redis。这里没有过多研究,就不赘述了。

7 总结

通过上述6个步骤,实现了Shiro的认证、授权、加密、缓存的基本功能。由于都是调用API,没有深究源码,所以还有很大的进步空间。不过对于新手入门Shiro已经可以了。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值