Shiro学习笔记

Shiro

​ Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

主要组件

  • Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。

  • SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。

  • Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。


    从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个

    Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。

基本要求

1️⃣认识shiro的整体架构,各组件的概念

2️⃣shiro认证,授权的过程

3️⃣shiro自定义的realm、Filter

4️⃣shiro session管理

5️⃣ shiro的缓存管理

6️⃣shiro继承spring

整体架构

img

1️⃣上面标记为1的是shiro的主体部分subject,可以理解为当前的操作用户

2️⃣Security Manager为Shiro的核心,shiro是通过security Manager来提供安全服务的,security Manager管理着Session Manager、Cache Manager等其他组件的实例:Authenticator(认证器,管理我们的登录登出) Authorizer(授权器,负责赋予主体subject有哪些权限) Session Manager(shiro自己实现的一套session管理机制,可以不借助任何web容器的情况下使用session) Session Dao(提供了session的增删改查操作) cache Manager(缓存管理器,用于缓存角色数据和权限数据) Pluggable Realms(shiro与数据库/数据源之间的桥梁,shiro获取认证信息、权限数据、角色数据都是通过Realms来获取)

3️⃣上图标记为2的cryptography是用来做加密的,使用它可以非常方便快捷的进行数据加密。

4️⃣上面箭头的流程可以这样理解:主体提交请求到Security Manager,然后由Security Manager调用Authenticator去做认证,而Authenticator去获取认证数据的时候是通过Realms从数据源中来获取的,然后把从数据源中拿到的认证信息与主体提交过来的认证信息做比对。授权器Authorizer也是一样。

img

四大基石----身份验证,授权,会话管理,加密

  1. Authentication:身份认证/登录,验证用户是不是拥有相应的身份;

  2. Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

  3. Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;

  4. Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

认证流程

img

应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject

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

最简单的一个Shiro应用:

1、应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;

2、我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。

Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入。

授权流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bl1KEfhw-1667800993737)(https://pic1.zhimg.com/80/v2-66b6458df10fd05db4aea732b0199080_720w.jpg)]

Realm

作为用户用于验证的部分,该部分制定了关系验证和授权的过程和信息

定义方式

  1. 查询shiro的ini配置文件

    • IniRealm文件中定义了关系当前用户的信息,用户信息,拥有角色,权限

      #定义用户
      [users]
      #用户名 zhang3  密码是 12345, 角色是 admin
      zhang3 = 12345, admin
      #用户名 li4  密码是 abcde, 角色是 产品经理
      li4 = abcde,productManager
      #定义角色
      [roles]
      #管理员什么都能做
      admin = *
      #产品经理只能做产品管理
      productManager = addProduct,deleteProduct,editProduct,updateProduct,listProduct
      #订单经理只能做订单管理
      orderManager = addOrder,deleteOrder,editOrder,updateOrder,listOrder
      

      以上是ini配置文件中的保存的信息,看上去是比较明显的

      属于键值对的定义模式

    • 使用方式

          private static Subject getSubject(User user) {
              //加载配置文件,并获取工厂
              Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
              //获取安全管理者实例
              SecurityManager sm = factory.getInstance();
              //将安全管理者放入全局对象
              SecurityUtils.setSecurityManager(sm);
              //全局对象通过安全管理者生成Subject对象
              Subject subject = SecurityUtils.getSubject();
       
              return subject;
          }
      

      通过IniSecurityManagerFactory生产一个安全管理者,使用这个安全管理者创建一个环境从而生成一个校验对象。

  2. 调取数据库信息

    • 通过创建继承AuthorizingRealm ,重写认证和授权信息的方法

    • Authorization:授权

      • 角色

      • 角色拥有的权限

    • Authentication:认证

      认证用户是否存在

      用法

    @Override  //授权
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取当前用户的主要信息。主要是需要用户名和id
        User user = (User) principalCollection.getPrimaryPrincipal();
        //从数据库中获取对应的角色和权限
        Set<Role> roles = roleService.getRolesbyId(user.getId());
        Set<Permission> permissions = permissionService.getPermissionsByRoles(roles);
        //将角色和权限添加到当前用户的授权信息当中
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        for (Role role : roles) {
            info.addRole(role.getName());
        }
        info.setStringPermissions(permissions);
        return info;
    }

    @Override  //认证
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //检查当前的输入的用户名是否为空
        if (authenticationToken.getPrincipal() == null) {
            return null;
        }
        String name = authenticationToken.getPrincipal().toString();
        //从数据库中查询该用户的相关信息
        User user = userService.findUserByName(name);
        if (user == null) {
            return null;
        } else {
            //返回这个用户组成的验证信息,用户名,密码和盐
            return new SimpleAuthenticationInfo(name, user.getPwd(), ByteSource.Util.bytes(user.getSalt()), getName());
        }
    }

ShiroConfig

除了用户的验证和授权定义,Shiro需要进行环境的配置,进行对项目的权限的全局控制设置

在这里插入图片描述

在这个环境中需要进行注入三个部分

  1. ShiroFilter 权限过滤器

    对发出请求的“用户”进行过滤,进行认证和授权,从而对请求的放行处理

  2. SecurityManager 安全管理者

    用户管理者,一个实体环境,对Realm进行管理

  3. Realm 验证和权限信息

ShiroFilter

    //Filter工厂,设置对应的过滤条件和跳转条件
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, String> map = new LinkedHashMap<>();
        map.put("/doLogin", "anon");
        map.put("/doRegister", "anon");
        map.put("/admin/**", "anon");
        //登出
        map.put("/logout", "logout");
        //对所有用户认证
        //建议从细到粗地进行链式的过滤,最后为所有资源进行上锁
        map.put("/**", "authc");
        //登录
        shiroFilterFactoryBean.setLoginUrl("/login");
        //首页
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //错误页面,认证不通过跳转
        shiroFilterFactoryBean.setUnauthorizedUrl("/401");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

过滤器的设置是依据格式进行的------ url [权限设置]

其中权限的设置对象包括

img

设置方式参考

* @see /admin=authc,roles[admin] 表示用户必需已通过认证,并拥有admin角色才可以正常发起’/admin’请求

* @see /edit=authc,perms[admin:edit] 表示用户必需已通过认证,并拥有admin:edit权限才可以正常发起’/edit’请求

* @see /home=user 表示用户不一定需要已经通过认证,只需要曾经被Shiro记住过登录状态就可以正常发起’/home’请求

SecurityManager

//创建安全管理器
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm) {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(realm);
    return securityManager;
}

将Realm加入到环境当中

Realm

    //创建自定义Realm
    @Bean
    public Realm getRealm() {
        CustomerRealm realm = new CustomerRealm();
        return realm;
    }

创建一个自定义的验证授权类

Shiro加密

数据库当中为了防止数据库中的密码泄露,一般密码都是使用加盐和多次加密的方式进行密码的重定义再保存到数据库当中

使用md5算法进行不可逆的加密

但是不可逆的加密对于一样的密码输出的字符是一样的,因此在原始密码的基础上加一些“盐”进行搭配形成干扰,在进行注册的时候将盐也一并保存到数据库当中。

  • 注册用户

    Shiro中提供了加密的工具类进行使用,使用的过程如下:

    String password = "123456";
    //生成随机数的二进制编码盐,生成字符串进行保存
    String salt = new SecureRandomNumberGenerator().nextBytes().toString();
    int times = 2;  // 加密次数:2
    String alogrithmName = "md5";   // 加密算法
    //编译成新的密码字符段
    //将盐和密码一起保存到数据库当中
    String encodePassword = new SimpleHash(alogrithmName, password, salt, times).toString();
    
  • 参与验证

    因为我们在加密的过程中,使用了md5加密算法,加密转码次数,以及盐

    而在登录验证的时候,输入到服务器当中的依旧是明文的原密码

    所以在服务器当中需要设置加密匹配的策略(没有使用加密,则不需要,会默认进行最原始的匹配)

    使用

    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5"); // 散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(2); // 散列的次数,比如散列两次,相当于 md5(md5(""));
        return hashedCredentialsMatcher;
    }
    

    在设置类当中,添加认证匹配器来定义认证策略的参数—算法和次数

    将匹配器加入到认证授权类当中

    myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
    

    在验证类当中,返回的用户信息当中,要将盐值加入

    new SimpleAuthenticationInfo(name, user.getPwd(), ByteSource.Util.bytes(user.getSalt()), getName());
    

应用实例

环境配置

注入Shiro依赖
<!--引入shrio-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-starter</artifactId>
    <version>1.5.3</version>
</dependency>
Shiro标签依赖
<!--        shiro标签依赖-->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>
页面解析
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">

shiro常用标签

<!--验证当前用户是否拥有指定权限。  -->
<a shiro:hasPermission="user:add" href="#" >add用户</a><!-- 拥有权限 -->
 
 
<!--与hasPermission标签逻辑相反,当前用户没有制定权限时,验证通过。-->
<p shiro:lacksPermission="user:del"> 没有权限 </p>
 
 
<!--验证当前用户是否拥有以下所有权限。-->
<p shiro:hasAllPermissions="user:view, user:add"> 权限与判断 </p>
 
 
<!--验证当前用户是否拥有以下任意一个权限。-->
<p shiro:hasAnyPermissions="user:view, user:del"> 权限或判断 </p>
 
 
<!--验证当前用户是否属于该角色。-->
<a shiro:hasRole="admin" href="#">拥有该角色</a>
 
 
<!--与hasRole标签逻辑相反,当用户不属于该角色时验证通过。-->
<p shiro:lacksRole="developer"> 没有该角色 </p>
 
 
<!--验证当前用户是否属于以下所有角色。-->
<p shiro:hasAllRoles="developer, admin"> 角色与判断 </p>
 
 
<!--验证当前用户是否属于以下任意一个角色。-->
<p shiro:hasAnyRoles="admin, vip, developer"> 角色或判断 </p>
 
 
<!--验证当前用户是否为“访客”,即未认证(包含未记住)的用户。-->
<p shiro:guest="">访客 未认证</a></p>
 
 
<!--认证通过或已记住的用户-->
<p shiro:user=""> 认证通过或已记住的用户 </p>
    
 
<!--已认证通过的用户。不包含已记住的用户,这是与user标签的区别所在。-->
<p shiro:authenticated=""> <span shiro:principal=""></span> </p>
    
 
<!--输出当前用户信息,通常为登录帐号信息-->
<p> <shiro:principal/> </p>
 
 
<!--未认证通过用户,与authenticated标签相对应。-->
<!--与guest标签的区别是,该标签包含已记住用户。-->
<p shiro:notAuthenticated=""> 未认证通过用户 </p>

使用方式

ShiroConfig中加入设置开启标签解析

@Bean
public ShiroDialect shiroDialect() {
    return new ShiroDialect();
}

认证与注册

Shiro的认证步骤:

  1. subject接受用户信息token
  2. 根据token信息发送到securityManager
  3. 安全管理者去调用验证器,去遍历用户实体
  4. 根据匹配器的规则,用户实体的信息和输入的信息token进行匹配
  5. 返回一个用户样本信息
  6. 将样本信息继续输入到授权管理器中

授权

  1. 授权管理器根据认证后的主体信息,去查找对应的角色和权限信息
    • 角色和权限信息都是以字符串的形式保存

授权定义

在验证和授权类当中,实现doGetAuthorizationInfo

@Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取认证了的主体信息
        String principal = (String) principalCollection.getPrimaryPrincipal();
        if ("zhangsan".equals(principal)) {
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            info.addRole("admin");
            info.addRole("user");
            info.addStringPermission("user:find:*");
            info.addStringPermission("admin:*");
            return info;
        }
        return null;
    }
  • 权限的格式----》 role : permissions

    *可以进行层级权限设置,号表示具有所有权限

授权的使用

对于权限的使用,从两个角度进行限制考虑

1.页面元素的访问

在实际的使用当中,用过添加标签的方式,为标签的访问设置权限,从界面上对授权进行控制

比如有一些元素是只有管理员登陆后才会显示的,在页面的渲染时,这些加了标签的元素就会在系统中进行判断决定是否在页面上进行显示

vip用户的页面当中存在一些专属内容,比如vip的图标,vip的等级显示,特别的装饰元素等等

<shiro:hasRole name="user">
        <li><a href="#">用户管理</a></li>
        <ul>
            <shiro:hasPermission name="user:save:*">
                <li><a href="#">增加</a></li>
            </shiro:hasPermission>
            <shiro:hasPermission name="user:delete:*">
            <li><a href="#">删除</a></li>
            </shiro:hasPermission>
            <shiro:hasPermission name="user:update:*">
            <li><a href="#">修改</a></li>
            </shiro:hasPermission>
            <shiro:hasPermission name="user:find:*">
            <li><a href="#">查询</a></li>
            </shiro:hasPermission>
        </ul>
    </shiro:hasRole>
    <shiro:hasRole name="admin">
        <li><a href="#">商品管理</a></li>
        <li><a href="#">订单管理</a></li>
        <li><a href="#">物流管理</a></li>
    </shiro:hasRole>
2.subject判断

subjectUtil可以获得当前的认证主体信息,通过主体信息判断,进行if-else的使用来限制

    @RequestMapping("save")
    public String save() {
        //基于角色
        //获取主体对象
        Subject subject = SecurityUtils.getSubject();
        //代码方式
        if (subject.hasRole("admin")) {
            System.out.println("保存订单!");
        }else{
            System.out.println("无权访问!");
        }
        System.out.println("进入save方法============");
        return "redircet:/index.jsp";
    }
3.服务接口的权限使用

在现实的业务实现当中,作为用户的区分,有些服务是只对特定的角色和权限进行开放,因为尽管通过标签上的设置,某些用户依旧可以直接通过接口的服务进行调用,因此在服务接口的执行前就加上一些限制

  • 发挥注解和AOP的作用,对方法进行代理和增强

  • 如果需要使用服务接口的限制注解功能,需要在配置类中,注入注解扫描。

        @RequiresRoles(value={"admin","user"})//用来判断角色  同时具有 admin user
        @RequiresPermissions("user:update:01") //用来判断权限字符串
        @RequestMapping("save")
        public String save(){
            System.out.println("进入方法");
            return "redirect:/index.jsp";
        }
    

使用缓存

如果每次认证Realm都从数据库当中查询数据进行验证,这无疑是会对服务器造成负担。通过缓存的方式,将用户的信息放入缓存当中,降低负担。

在这里插入图片描述

使用方式

  1. 引入依赖

    shiro的缓存功能是需要开启的,因此进行依赖的引入

  2. 开启缓存

    在认证授权器中开启缓存功能

    将认证信息和授权信息连接缓存器中

配合Redis使用

  1. 引入依赖

    <!--redis整合springboot-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  2. 设置Redis连接

    在application中设置Redis连接参数

    spring.redis.port=6379
    spring.redis.host=localhost
    spring.redis.database=0
    
  3. 启动Redis服务

  4. 定义shiro缓存管理器

    在认证授权器当中打开了缓存功能后,shiro通过缓存管理器与缓存进行交互

    因此自定义一个缓存管理器与Redis进行交互

    //自定义shiro缓存管理器
    public class RedisCacheManager implements CacheManager {
        //参数1:认证或者是授权缓存的统一名称
        @Override
        public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
            System.out.println(cacheName);
            return new RedisCache<K,V>(cacheName);
        }
    }
    
  5. 实现Redis缓存使用

    对缓存的认证和授权信息进行操作–增删改查

    //自定义redis缓存的实现
    public class RedisCache<k,v> implements Cache<k,v> {
    
        private String cacheName;
    
        public RedisCache() {
        }
    
        public RedisCache(String cacheName) {
            this.cacheName = cacheName;
        }
    
        @Override
        public v get(k k) throws CacheException {
            return (v) getRedisTemplate().opsForHash().get(this.cacheName, k.toString());
        }
    
        @Override
        public v put(k k, v v) throws CacheException {
            System.out.println("put key: "+k);
            System.out.println("put value:"+v);
            getRedisTemplate().opsForHash().put(this.cacheName,k.toString(), v);
            return null;
        }
    
        @Override
        public v remove(k k) throws CacheException {
            System.out.println("=============remove=============");
            return (v) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
        }
    
        @Override
        public void clear() throws CacheException {
            System.out.println("=============clear==============");
            getRedisTemplate().delete(this.cacheName);
        }
    
        @Override
        public int size() {
            return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
        }
    
        @Override
        public Set<k> keys() {
            return getRedisTemplate().opsForHash().keys(this.cacheName);
        }
    
        @Override
        public Collection<v> values() {
            return getRedisTemplate().opsForHash().values(this.cacheName);
        }
    
        private RedisTemplate getRedisTemplate(){
            RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashKeySerializer(new StringRedisSerializer());
            return redisTemplate;
        }
    }
    

    要缓存的信息需要进行序列化,不然在输入遍历信息的时候将会无须进行,这会导致认证错误

    但是shiro中提供的simpleByteSource实现没有实现序列化

    使用的认证信息当中的salt无法进行序列化编译,需要自定义一个可以序列化的ByteSource

    //自定义salt实现 实现序列化接口
    public class MyByteSource implements ByteSource, Serializable {
        private  byte[] bytes;
        private String cachedHex;
        private String cachedBase64;
    
        public MyByteSource(byte[] bytes) {
            this.bytes = bytes;
        }
    
        public MyByteSource(char[] chars) {
            this.bytes = CodecSupport.toBytes(chars);
        }
    
        public MyByteSource(String string) {
            this.bytes = CodecSupport.toBytes(string);
        }
    
        public MyByteSource(ByteSource source) {
            this.bytes = source.getBytes();
        }
    
        public MyByteSource(File file) {
            this.bytes = (new MyByteSource.BytesHelper()).getBytes(file);
        }
    
        public MyByteSource(InputStream stream) {
            this.bytes = (new MyByteSource.BytesHelper()).getBytes(stream);
        }
    
        public static boolean isCompatible(Object o) {
            return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
        }
    
        public byte[] getBytes() {
            return this.bytes;
        }
    
        public boolean isEmpty() {
            return this.bytes == null || this.bytes.length == 0;
        }
    
        public String toHex() {
            if (this.cachedHex == null) {
                this.cachedHex = Hex.encodeToString(this.getBytes());
            }
    
            return this.cachedHex;
        }
    
        public String toBase64() {
            if (this.cachedBase64 == null) {
                this.cachedBase64 = Base64.encodeToString(this.getBytes());
            }
    
            return this.cachedBase64;
        }
    
        public String toString() {
            return this.toBase64();
        }
    
        public int hashCode() {
            return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0;
        }
    
        public boolean equals(Object o) {
            if (o == this) {
                return true;
            } else if (o instanceof ByteSource) {
                ByteSource bs = (ByteSource)o;
                return Arrays.equals(this.getBytes(), bs.getBytes());
            } else {
                return false;
            }
        }
    
        private static final class BytesHelper extends CodecSupport {
            private BytesHelper() {
            }
    
            public byte[] getBytes(File file) {
                return this.toBytes(file);
            }
    
            public byte[] getBytes(InputStream stream) {
                return this.toBytes(stream);
            }
        }
    }
    

    该序列化后的ByteSource在认证授权中代替原先的,对获取的盐进行序列化操作

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String principal = (String) authenticationToken.getPrincipal();
        //获取UserService对象
        UserService userService = (UserService) ApplicationContextUtil.getBean("userService");
        //System.out.println(userService);
        User user = userService.findByUsername(principal);
        if (!ObjectUtils.isEmpty(user)) {
            return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), new MyByteSource(user.getSalt()), this.getName());
        }
        return null;
    }
    

Shiro实现验证功能

验证码工具类

public class VerifyCodeUtils{

    //使用到Algerian字体,系统里没有的话需要安装字体,字体只显示大写,去掉了1,0,i,o几个容易混淆的字符
    public static final String VERIFY_CODES = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
    private static Random random = new Random();


    /**
     * 使用系统默认字符源生成验证码
     * @param verifySize    验证码长度
     * @return
     */
    public static String generateVerifyCode(int verifySize){
        return generateVerifyCode(verifySize, VERIFY_CODES);
    }
    /**
     * 使用指定源生成验证码
     * @param verifySize    验证码长度
     * @param sources   验证码字符源
     * @return
     */
    public static String generateVerifyCode(int verifySize, String sources){
        if(sources == null || sources.length() == 0){
            sources = VERIFY_CODES;
        }
        int codesLen = sources.length();
        Random rand = new Random(System.currentTimeMillis());
        StringBuilder verifyCode = new StringBuilder(verifySize);
        for(int i = 0; i < verifySize; i++){
            verifyCode.append(sources.charAt(rand.nextInt(codesLen-1)));
        }
        return verifyCode.toString();
    }

    /**
     * 生成随机验证码文件,并返回验证码值
     * @param w
     * @param h
     * @param outputFile
     * @param verifySize
     * @return
     * @throws IOException
     */
    public static String outputVerifyImage(int w, int h, File outputFile, int verifySize) throws IOException{
        String verifyCode = generateVerifyCode(verifySize);
        outputImage(w, h, outputFile, verifyCode);
        return verifyCode;
    }

    /**
     * 输出随机验证码图片流,并返回验证码值
     * @param w
     * @param h
     * @param os
     * @param verifySize
     * @return
     * @throws IOException
     */
    public static String outputVerifyImage(int w, int h, OutputStream os, int verifySize) throws IOException{
        String verifyCode = generateVerifyCode(verifySize);
        outputImage(w, h, os, verifyCode);
        return verifyCode;
    }

    /**
     * 生成指定验证码图像文件
     * @param w
     * @param h
     * @param outputFile
     * @param code
     * @throws IOException
     */
    public static void outputImage(int w, int h, File outputFile, String code) throws IOException{
        if(outputFile == null){
            return;
        }
        File dir = outputFile.getParentFile();
        if(!dir.exists()){
            dir.mkdirs();
        }
        try{
            outputFile.createNewFile();
            FileOutputStream fos = new FileOutputStream(outputFile);
            outputImage(w, h, fos, code);
            fos.close();
        } catch(IOException e){
            throw e;
        }
    }

    /**
     * 输出指定验证码图片流
     * @param w
     * @param h
     * @param os
     * @param code
     * @throws IOException
     */
    public static void outputImage(int w, int h, OutputStream os, String code) throws IOException{
        int verifySize = code.length();
        BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        Random rand = new Random();
        Graphics2D g2 = image.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
        Color[] colors = new Color[5];
        Color[] colorSpaces = new Color[] { Color.WHITE, Color.CYAN,
                Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
                Color.PINK, Color.YELLOW };
        float[] fractions = new float[colors.length];
        for(int i = 0; i < colors.length; i++){
            colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)];
            fractions[i] = rand.nextFloat();
        }
        Arrays.sort(fractions);

        g2.setColor(Color.GRAY);// 设置边框色
        g2.fillRect(0, 0, w, h);

        Color c = getRandColor(200, 250);
        g2.setColor(c);// 设置背景色
        g2.fillRect(0, 2, w, h-4);

        //绘制干扰线
        Random random = new Random();
        g2.setColor(getRandColor(160, 200));// 设置线条的颜色
        for (int i = 0; i < 20; i++) {
            int x = random.nextInt(w - 1);
            int y = random.nextInt(h - 1);
            int xl = random.nextInt(6) + 1;
            int yl = random.nextInt(12) + 1;
            g2.drawLine(x, y, x + xl + 40, y + yl + 20);
        }

        // 添加噪点
        float yawpRate = 0.05f;// 噪声率
        int area = (int) (yawpRate * w * h);
        for (int i = 0; i < area; i++) {
            int x = random.nextInt(w);
            int y = random.nextInt(h);
            int rgb = getRandomIntColor();
            image.setRGB(x, y, rgb);
        }

        shear(g2, w, h, c);// 使图片扭曲

        g2.setColor(getRandColor(100, 160));
        int fontSize = h-4;
        Font font = new Font("Algerian", Font.ITALIC, fontSize);
        g2.setFont(font);
        char[] chars = code.toCharArray();
        for(int i = 0; i < verifySize; i++){
            AffineTransform affine = new AffineTransform();
            affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), (w / verifySize) * i + fontSize/2, h/2);
            g2.setTransform(affine);
            g2.drawChars(chars, i, 1, ((w-10) / verifySize) * i + 5, h/2 + fontSize/2 - 10);
        }

        g2.dispose();
        ImageIO.write(image, "jpg", os);
    }

    private static Color getRandColor(int fc, int bc) {
        if (fc > 255)
            fc = 255;
        if (bc > 255)
            bc = 255;
        int r = fc + random.nextInt(bc - fc);
        int g = fc + random.nextInt(bc - fc);
        int b = fc + random.nextInt(bc - fc);
        return new Color(r, g, b);
    }

    private static int getRandomIntColor() {
        int[] rgb = getRandomRgb();
        int color = 0;
        for (int c : rgb) {
            color = color << 8;
            color = color | c;
        }
        return color;
    }

    private static int[] getRandomRgb() {
        int[] rgb = new int[3];
        for (int i = 0; i < 3; i++) {
            rgb[i] = random.nextInt(255);
        }
        return rgb;
    }

    private static void shear(Graphics g, int w1, int h1, Color color) {
        shearX(g, w1, h1, color);
        shearY(g, w1, h1, color);
    }

    private static void shearX(Graphics g, int w1, int h1, Color color) {

        int period = random.nextInt(2);

        boolean borderGap = true;
        int frames = 1;
        int phase = random.nextInt(2);

        for (int i = 0; i < h1; i++) {
            double d = (double) (period >> 1)
                    * Math.sin((double) i / (double) period
                    + (6.2831853071795862D * (double) phase)
                    / (double) frames);
            g.copyArea(0, i, w1, 1, (int) d, 0);
            if (borderGap) {
                g.setColor(color);
                g.drawLine((int) d, i, 0, i);
                g.drawLine((int) d + w1, i, w1, i);
            }
        }

    }

    private static void shearY(Graphics g, int w1, int h1, Color color) {

        int period = random.nextInt(40) + 10; // 50;

        boolean borderGap = true;
        int frames = 20;
        int phase = 7;
        for (int i = 0; i < w1; i++) {
            double d = (double) (period >> 1)
                    * Math.sin((double) i / (double) period
                    + (6.2831853071795862D * (double) phase)
                    / (double) frames);
            g.copyArea(i, 0, 1, h1, 0, (int) d);
            if (borderGap) {
                g.setColor(color);
                g.drawLine(i, (int) d, i, 0);
                g.drawLine(i, (int) d + h1, i, h1);
            }

        }

    }
    public static void main(String[] args) throws IOException {
        //获取验证码
        String s = generateVerifyCode(4);
        //将验证码放入图片中
        outputImage(260,60,new File("/Users/chenyannan/Desktop/安工资料/aa.jpg"),s);
        System.out.println(s);
    }
}
  1. 在接口服务当中将图片进行输出

    @RequestMapping("getImage")
    public void getImage(HttpSession session, HttpServletResponse response) throws IOException {
      //生成验证码
      String code = VerifyCodeUtils.generateVerifyCode(4);
      //验证码放入session
      session.setAttribute("code",code);
      //验证码存入图片
      ServletOutputStream os = response.getOutputStream();
      response.setContentType("image/png");
      VerifyCodeUtils.outputImage(220,60,os,code);
    }
    
  2. 认证流程中,配合存在session进行验证码的设置

    public String login(String username, String password,String code,HttpSession session) {
        //比较验证码
        String codes = (String) session.getAttribute("code");
        try {
            if (codes.equalsIgnoreCase(code)){
                //获取主体对象
                Subject subject = SecurityUtils.getSubject();
                    subject.login(new UsernamePasswordToken(username, password));
                    return "redirect:/index.jsp";
            }else{
                throw new RuntimeException("验证码错误!");
            }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Little BOY.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值