• SpringBoot整合Shiro


    SpringBoot整合Shiro

    创建springboot项目

    公共资源所有人都可以访问

    受限资源:菜单、按钮、操作

    image-20221015112722503

    引入依赖

        
                org.springframework.boot
                spring-boot-starter-web
            
    
            
                org.springframework.boot
                spring-boot-devtools
                runtime
                true
            
            
                org.projectlombok
                lombok
                true
            
            
                org.springframework.boot
                spring-boot-starter-test
                test
            
            
                org.apache.tomcat.embed
                tomcat-embed-jasper
                9.0.64
            
            
                javax.servlet
                jstl
                1.2
            
            
                javax.servlet
                servlet-api
                2.3
            
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    application.properties文件

    server.port=8080
    server.servlet.context-path=/shiro
    spring.application.name=shiro
    
    spring.mvc.view.prefix=/
    spring.mvc.view.suffix=jsp
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    页面中假设有这些功能

    image-20221015164420741

    引入shiro整合依赖

       
                org.apache.shiro
                shiro-spring-boot-starter
                1.5.3
            
    
    • 1
    • 2
    • 3
    • 4
    • 5

    配置shiro

    ShiroConfig

    package com.bo.springboot_shiro_jsp.config;
    
    import com.bo.springboot_shiro_jsp.shiro.CustomerRealm;
    import org.apache.shiro.realm.Realm;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import java.util.HashMap;
    
    /**
     * @author: bo
     * @date: 2022/10/15
     * @description: 整合Shiro框架相关的配置类
     */
    @Configuration
    public class ShiroConfig {
        //1.创建shiroFilter //负责拦截所有请求
        @Bean
        public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            //给filter设置安全管理器
            shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
            //配置系统受限资源
            HashMap map = new HashMap<>();
            map.put("/index.jsp", "authc");
            //默认的认证界面路径
            shiroFilterFactoryBean.setLoginUrl("/login.jsp");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
            //配置系统公共资源
            return shiroFilterFactoryBean;
        }
    
    
        //2.创建安全管理器
        @Bean
        public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm) {
            DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
            defaultWebSecurityManager.setRealm(realm);
            return defaultWebSecurityManager;
        }
    
        //3.创建自定义realm
        @Bean
        public Realm getRealm() {
            CustomerRealm customerRealm = new CustomerRealm();
            return customerRealm;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    13行不写,默认就是/login.jsp

    自定义Realm

    package com.bo.springboot_shiro_jsp.shiro;
    
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    
    /**
     * @author: bo
     * @date: 2022/10/15
     * @description: 自定义Realm
     */
    public class CustomerRealm extends AuthorizingRealm {
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            return null;
        }
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    直接访问http://localhost:8080/shiro/index.jsp

    会跳到登录界面

    authc代表这个资源需要认证和授权

    常见过滤器

    shiro提供和多个默认的过滤器,我们可以用这些过滤器来配置控制指定url的权限

    image-20221015172003975

    实现认证
    登录页面写个表单

    image-20221015172248845

    在web环境中,我们只要创建了安全管理器,会自动注入到安全工具类

    我们写个UserController

    image-20221015212535676

    由于我们在自定义的realm中什么都没写,所以一定会输出认证失败,用户名不存在

    image-20221015211840181

    然后在自定义的realm中模拟一下

    image-20221015212112240

    输入xiaozhang

    密码123可以进入主页

    退出

    image-20221015212909901

    controller

    image-20221015212759673

    因为受限资源非常多,所以我们可以写通配符

    image-20221015213253046

    不受限的资源要放到上面

    image-20221015213538514

    路径要写全

    下面做md5加盐和hash散列

    注册页面

    image-20221016161118029

    注册也属于公共资源

    image-20221016161252010

    创建数据库和表

    create database shiro_study
    create table `s_user`(
        id int primary key auto_increment not null,
        username varchar(255) not null,
        password varchar(255) not null,
        salt varchar(255)   not null
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    导入mysql、mybatis、druid依赖

    
        org.mybatis.spring.boot
        mybatis-spring-boot-starter
        2.2.2
    
    
        mysql
        mysql-connector-java
        5.1.47
    
    
        com.alibaba
        druid
        1.1.18
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    增加配置

    application.properties

    spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3307/shiro_study
    spring.datasource.username=root
    spring.datasource.password=123456
    
    mybatis.type-aliases-package=com.bo.springboot_shiro_jsp.entity
    mybatis.mapper-locations=classpath:com/bo/mapper/*.xml
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    项目结构

    image-20221016170050913

    实体类

    @Data
    @Accessors(chain = true)
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
        private Integer id;
        private String username;
        private String password;
        private String salt;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    dao层

    package com.bo.springboot_shiro_jsp.dao;
    
    import com.bo.springboot_shiro_jsp.entity.User;
    import org.apache.ibatis.annotations.Mapper;
    
    /**
     * @author: bo
     * @date: 2022/10/16
     * @description:
     */
    @Mapper
    public interface UserDAO {
        void save(User user);
     }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    mapper.xml

    
    
    
    
        insert into s_user (id,username,password,salt)
        values (#{id},#{username},#{password},#{salt});
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    随机盐工具类

    package com.bo.springboot_shiro_jsp.utils;
    
    import java.util.Random;
    
    /**
     * @author: bo
     * @date: 2022/10/16
     * @description:
     */
    public class SaltUtils {
        public static String getSalt(int n) {
            char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()".toCharArray();
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < n; i++) {
                char aChar = chars[new Random().nextInt(chars.length)];
                sb.append(aChar);
            }
            return sb.toString();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    service层

    package com.bo.springboot_shiro_jsp.service;
    
    import com.bo.springboot_shiro_jsp.entity.User;
    
    /**
     * @author: bo
     * @date: 2022/10/16
     * @description:
     */
    public interface UserService {
        //注册用户
        void register(User user);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    package com.bo.springboot_shiro_jsp.service;
    
    import com.bo.springboot_shiro_jsp.dao.UserDAO;
    import com.bo.springboot_shiro_jsp.entity.User;
    import com.bo.springboot_shiro_jsp.utils.SaltUtils;
    import org.apache.shiro.crypto.hash.Md5Hash;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import javax.annotation.Resource;
    
    /**
     * @author: bo
     * @date: 2022/10/16
     * @description:
     */
    @Service
    @Transactional
    public class UserServiceImpl implements UserService {
        @Resource
        private UserDAO userDAO;
        @Override
        public void register(User user) {
            //1.生成随机盐
            String salt = SaltUtils.getSalt(8);
            //2.存到数据库
            user.setSalt(salt);
            //3.明文md5+salt+hash散列
            Md5Hash md5Hash = new Md5Hash(user.getPassword(), salt, 1024);
            user.setPassword(md5Hash.toHex());
            userDAO.save(user);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    controller

      @Resource
        private UserService userService;
    
    
        @RequestMapping("/register")
        public String register(User user) {
    
            try {
                userService.register(user);
            } catch (Exception e) {
                e.printStackTrace();
                return "redirect:/register.jsp";
            }
            return "redirect:/login.jsp";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    注册一下,查看数据库确实有用户

    image-20221016165840905

    认证功能

    dao层

     User getUserByName(String username);
    
    • 1

    mapper.xml

    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    service层

    User getUserByName(String username);
    
    • 1
        @Override
        public User getUserByName(String username) {
            return userDAO.getUserByName(username);
        }
    
    • 1
    • 2
    • 3
    • 4

    在自定义realm中使用spring注入service需要工具类(因为自定义的realm没有交给工厂管理)

    重点工具类

    package com.bo.springboot_shiro_jsp.utils;
    
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.stereotype.Component;
    
    /**
     * @author: bo
     * @date: 2022/10/16
     * @description: 获取springboot中创建好工厂的工具类
     */
    @Component
    public class ApplicationContextUtils implements ApplicationContextAware {
        private static ApplicationContext context;
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            context = applicationContext;
        }
        public static Object getBean(String beanName){
            return context.getBean(beanName);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    自定义realm

    image-20221016200813235

    设置凭证匹配器

    image-20221016200832564

    授权

    在自定义realm中的这个方法image-20221016201452034

    中进行业务的书写,需要配合shiro自带的标签

    image-20221016205949850

    引入标签

    <%@taglib prefix="shiro" uri="http://shiro.apache.org/tags"%>
    
    • 1

    image-20221016210030547

    xiaozhang用户有user权限,只有admin权限的用户才可以看到商品、订单、物理这些模块

    image-20221016210117724

    只想让user和admin看到用户管理

    image-20221016210302614

    基于资源的权限管理

    image-20221016210435328

    image-20221016210732967

    image-20221016210744200

    用户应当只能看到修改和添加的

    image-20221016210901954

    代码中完成权限的控制

    image-20221016211733820

    注解

    image-20221016211755384

    多个角色

    image-20221016211853908

    注解的权限字符串

    image-20221016212223166

    授权替换成数据库

    模式一

    用户->角色->权限->资源

    用户->角色

    用户->权限

    用户->权限

    image-20221017092447846

    建表语句

    create table `s_role`(
                             id int primary key auto_increment not null,
                             name varchar(255) not null
    )
    
    create table `s_perms`(
                             id int primary key auto_increment not null,
                             name varchar(255) not null,
                             url varchar(255) not null
    )
    
    create table `s_user_role`(
                             id int primary key auto_increment not null,
                             userid int not null,
                             roleid int not null,
                             name varchar(255) not null
    )
    
    create table `s_role_perms`(
                                id int primary key auto_increment not null,
                                roleid int not null,
                                permsid int not null
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    user表

    image-20221017104346212

    role表

    image-20221017104402869

    userrole表

    image-20221017104414162

    角色信息的获取

    实体类

    1个用户对应多个权限

    image-20221017103929564

    @Data
    @Accessors(chain = true)
    @AllArgsConstructor
    @NoArgsConstructor
    public class Role {
        private String id;
        private String name;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    复杂查询resultMap进行映射

    
        
        
        
            
            
        
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    dao层返回是一个User对象,里面的list集合放的权限

    User findRolesByUserName(String username);
    
    • 1

    service层

    User findRolesByUserName(String username);
    
    • 1
    @Override
    public User findRolesByUserName(String username) {
        return userDAO.findRolesByUserName(username);
    }
    
    • 1
    • 2
    • 3
    • 4

    测试一下,查询下小张,可以

    image-20221017104319039

    在自定义的realm中的授权方法中

    image-20221017104516237

    用lamda表达式取出对应角色每一个role,然后授权

    权限信息的获取

    image-20221017141052323

    image-20221017141058338

    dao层

    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
     //根据角色id查询权限集合
        List findPermsByRoleId(Integer id);
    
    • 1
    • 2

    service层

    List findPermsByRoleId(Integer id);
    
    • 1
    @Override
    public List findPermsByRoleId(Integer id) {
        return userDAO.findPermsByRoleId(id);
    }
    
    • 1
    • 2
    • 3
    • 4

    image-20221017141210885

    用户的权限数据不会频繁变化

    使用CacheManager

    使用shiro中默认EhCache实现缓存

    引入依赖

      
                org.apache.shiro
                shiro-ehcache
                1.5.3
            
    
    • 1
    • 2
    • 3
    • 4
    • 5

    开启缓存

    //3.创建自定义realm
    @Bean
    public Realm getRealm() {
        CustomerRealm customerRealm = new CustomerRealm();
        //修改凭证匹配器
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //设置加密算法为md5
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
        //设置散列次数
        hashedCredentialsMatcher.setHashIterations(1024);
        //设置
        customerRealm.setCredentialsMatcher(hashedCredentialsMatcher);
        //开启缓存管理
        customerRealm.setCacheManager(new EhCacheManager());
        //开启全局缓存
        customerRealm.setCachingEnabled(true);
        //开启认证缓存
        customerRealm.setAuthenticationCachingEnabled(true);
        customerRealm.setAuthenticationCacheName("authenticationCache");
        //开启授权缓存
        customerRealm.setAuthorizationCachingEnabled(true);
        customerRealm.setAuthorizationCacheName("authorizationCache");
    
        return customerRealm;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    整合Redis

    引入Redis依赖

    
        org.springframework.boot
        spring-boot-starter-data-redis
    
    
    • 1
    • 2
    • 3
    • 4

    配置

    spring.redis.port=6379
    spring.redis.host=localhost
    spring.redis.database=5
    
    • 1
    • 2
    • 3

    自定义shiro的缓存管理器

    public class RedisCacheManager implements CacheManager {
        //cacheName:认证或者授权的统一名称
        @Override
        public  Cache getCache(String cacheName) throws CacheException {
            return new RedisCache(cacheName);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    自定义缓存

    [public class RedisCache implements Cache {
    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 {
        getRedisTemplate().opsForHash().put(this.cacheName,k.toString(),v);
        return null;
    }
     
    @Override
    public v remove(k k) throws CacheException {
        return (v) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
    }
     
    @Override
    public void clear() throws CacheException {
        getRedisTemplate().opsForHash().delete(this.cacheName);
    }
     
    @Override
    public int size() {
        return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
    }
     
    @Override
    public Set keys() {
        return getRedisTemplate().opsForHash().keys(this.cacheName);
    }
     
    @Override
    public Collection values() {
        return getRedisTemplate().opsForHash().values(this.cacheName);
    }
     
    private RedisTemplate getRedisTemplate(){
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean(redisTemplate);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    自定义MySimpleByteSource

    package com.bo.springboot_shiro_jsp.shiro;
    
    import org.apache.shiro.codec.Base64;
    import org.apache.shiro.codec.CodecSupport;
    import org.apache.shiro.codec.Hex;
    import org.apache.shiro.util.ByteSource;
    
    import java.io.File;
    import java.io.InputStream;
    import java.io.Serializable;
    import java.util.Arrays;
    
    /**
     * @author: bo
     * @date: 2022/10/17
     * @description: shiro 使用缓存时出现:java.io.NotSerializableException: org.apache.shiro.util.SimpleByteSource
     * 序列化后,无法反序列化的问题
     */
    
    public class MySimpleByteSource implements ByteSource, Serializable {
        private static final long serialVersionUID = 1L;
    
        private byte[] bytes;
        private String cachedHex;
        private String cachedBase64;
    
        public MySimpleByteSource() {
        }
    
        public MySimpleByteSource(byte[] bytes) {
            this.bytes = bytes;
        }
    
        public MySimpleByteSource(char[] chars) {
            this.bytes = CodecSupport.toBytes(chars);
        }
    
        public MySimpleByteSource(String string) {
            this.bytes = CodecSupport.toBytes(string);
        }
    
        public MySimpleByteSource(ByteSource source) {
            this.bytes = source.getBytes();
        }
    
        public MySimpleByteSource(File file) {
            this.bytes = (new MySimpleByteSource.BytesHelper()).getBytes(file);
        }
    
        public MySimpleByteSource(InputStream stream) {
            this.bytes = (new MySimpleByteSource.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 void setBytes(byte[] bytes) {
            this.bytes = bytes;
        }
    
        @Override
        public byte[] getBytes() {
            return this.bytes;
        }
    
    
        @Override
        public String toHex() {
            if (this.cachedHex == null) {
                this.cachedHex = Hex.encodeToString(this.getBytes());
            }
            return this.cachedHex;
        }
    
        @Override
        public String toBase64() {
            if (this.cachedBase64 == null) {
                this.cachedBase64 = Base64.encodeToString(this.getBytes());
            }
    
            return this.cachedBase64;
        }
    
        @Override
        public boolean isEmpty() {
            return this.bytes == null || this.bytes.length == 0;
        }
    
        @Override
        public String toString() {
            return this.toBase64();
        }
    
        @Override
        public int hashCode() {
            return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0;
        }
    
        @Override
        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);
            }
        }
    
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127

    整合验证码

    用户验证码:
    • 1

    工具类

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248

    userController

    //验证码
    @RequestMapping("getImage")
    public void getImage(HttpSession session, HttpServletResponse response){
         //生成验证码
         String code = VerifyCodeUtils.generateVerifyCode(4);
         //将验证码放入session中
         session.setAttribute("code",code);
         //存入验证码
         ServletOutputStream os = response.getOutputStream();
         response.setContentType("image/png");
         VerifyCodeUtils.outputImage(220,60,os,code);
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    需要放行

    image-20221017163838870

    修改下登录验证的逻辑,把判断验证码加上

        public String login(String username, String password, String code, HttpSession session) {
    
            //比较验证码
            String code1 = (String) session.getAttribute("code");
    
            try {
                if (code1.equalsIgnoreCase(code)) {
                    Subject subject = SecurityUtils.getSubject();
                    subject.login(new UsernamePasswordToken(username, password));
                    return "redirect:/index.jsp";
                } else {
                    throw new RuntimeException("验证码错误");
                }
            } catch (UnknownAccountException e) {
                e.printStackTrace();
                System.out.println("认证失败:用户名不存在");
            } catch (IncorrectCredentialsException e) {
                e.printStackTrace();
                System.out.println("认证失败,密码错误");
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println(e.getMessage());
            }
            return "redirect:/login.jsp";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    整合thymeleaf

    引入依赖

      
                org.springframework.boot
                spring-boot-starter-web
            
    
            
                org.springframework.boot
                spring-boot-devtools
                runtime
                true
            
            
                org.projectlombok
                lombok
                true
            
            
                org.springframework.boot
                spring-boot-starter-test
                test
            
            
                org.springframework.boot
                spring-boot-starter-thymeleaf
            
            
                javax.servlet
                servlet-api
                2.3
            
            
                org.apache.shiro
                shiro-spring-boot-starter
                1.5.3
            
            
                org.mybatis.spring.boot
                mybatis-spring-boot-starter
                2.2.2
            
            
                mysql
                mysql-connector-java
                5.1.47
            
            
                com.alibaba
                druid
                1.1.18
            
            
                org.apache.shiro
                shiro-ehcache
                1.5.3
            
            
                org.springframework.boot
                spring-boot-starter-data-redis
            
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    引入扩展依赖

    
        com.github.theborakompanioni
        thymeleaf-extras-shiro
        2.0.0
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    引入命名空间

    
    
    • 1
    • 2

    常见的权限扩展标签

    
    

    Please login

    Welcome back John! Not John? Click here to login.

    Hello, , how are you today?

    Update your contact information

    Hello, , how are you today?

    Please login in order to update your credit card information.

    Administer the system

    Sorry, you are not allowed to developer the system.

    You are a developer and a admin.

    You are a admin, vip, or developer.

    添加用户

    Sorry, you are not allowed to delete user accounts.

    You can see or add users.

    You can see or delete users.

    Create a new User
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    注意

    标签不起作用可以写方言

    在config配置中

    @Bean(name = "shiroDialect")
    public ShiroDialect shiroDialect(){
      return new ShiroDialect();
    }
    
    • 1
    • 2
    • 3
    • 4

    加入即可

  • 相关阅读:
    Vue3新的状态管理库-Pinia(保姆级别教程)
    Qt 关于mouseTracking鼠标追踪和tabletTracking平板追踪的几点官方说明
    转载--关闭onenote2013 /中点击超链接(指向本地文件夹)后出现的安全声明 / Microsoft onenote2021 安全声明关闭
    【CV秋季划】生成对抗网络GAN有哪些研究和应用,如何循序渐进地学习好(2022年言有三一对一辅导)?...
    C语言小程序-通讯录(动态内存管理)
    Spring STOMP-权限
    学 Go,最常用的技能是什么?打日志
    使用时间序列数据预测《Apex英雄》的玩家活跃数据
    基于php的房屋销售网站
    Java调用C++程序(C++可带返回值)
  • 原文地址:https://blog.csdn.net/qq_46110710/article/details/127376135