SpringBoot整合Shiro和Redis的示例代码

demo源码

此demo用SpringBoot+Shiro简单实现了登陆、注册、认证、授权的功能,并用redis做分布式缓存提高性能。

1.准备工作

导入pom.xml



    4.0.0

    
        org.springframework.boot
        spring-boot-starter-parent
        2.3.2.RELEASE
    
    com.ego
    shirodemo
    1.0-SNAPSHOT
    
        1.8
    
    
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.projectlombok
            lombok
        
        
            mysql
            mysql-connector-java
        
        
            org.springframework.boot
            spring-boot-starter-validation
        
        
            org.apache.httpcomponents
            httpcore
            4.4.13
        

        
        
            org.apache.tomcat.embed
            tomcat-embed-jasper
        

        
            jstl
            jstl
            1.2
        

        
        
            org.apache.shiro
            shiro-spring-boot-starter
            1.5.3
        

        
        
            com.baomidou
            mybatis-plus-boot-starter
            3.4.1
        

        
        
            com.alibaba
            druid-spring-boot-starter
            1.1.10
        

        
        
            mysql
            mysql-connector-java
            runtime
        
        
            io.springfox
            springfox-swagger2
            2.9.2
        
        
            io.springfox
            springfox-swagger-ui
            2.9.2
        
        
            log4j
            log4j
            1.2.17
        
        
            org.apache.shiro
            shiro-ehcache
            1.5.3
        
        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        

    


配置yml文件

spring:
  # 设置视图模板为jsp
  mvc:
    view:
      prefix: /
      suffix: .jsp
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=UTC
      username: root
      password: root
      # 监控统计拦截的filters
      filters: stat,wall,log4j,config
      # 配置初始化大小/最小/最大
      initial-size: 5
      min-idle: 5
      max-active: 20
      # 获取连接等待超时时间
      max-wait: 60000
      # 间隔多久进行一次检测,检测需要关闭的空闲连接
      time-between-eviction-runs-millis: 60000
      # 一个连接在池中最小生存的时间
      min-evictable-idle-time-millis: 300000
      validation-query: SELECT 'x'
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      # 打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
      pool-prepared-statements: false
      max-pool-prepared-statement-per-connection-size: 20
  redis:
    host: 127.0.0.1
    port: 6379
    password: abc123456
    database: 0

mybatis-plus:
  type-aliases-package: com.ego.pojo
  configuration:
    map-underscore-to-camel-case: true

2.编写index,login,register三个JSP

<%--解决页面乱码--%>
<%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>



    
    
    
    index


    
用户名:
密码 :
<%--解决页面乱码--%> <%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %> <%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> index

系统主页

退出用户
    <%-- admin角色的用户能同时拥有用户管理和订单管理的权限,user角色的用户只拥有订单管理的权限 --%>
  • 用户管理
  • <%-- admin角色的用户对订单有增删改查的权限,user角色的用户只能查看订单 --%>
  • 订单管理
<%--解决页面乱码--%> <%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %> register

用户注册

用户名:
密码 :

3.实现User、Role、Permission三个POJO

package com.ego.pojo;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
 * @author 袁梦达 2019012364
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("t_user")
@ApiModel("用户实体类")
public class User implements Serializable {
    /** 数据库中设置该字段自增时该注解不能少 **/
    @TableId(type = IdType.AUTO)
    @ApiModelProperty(name = "id", value = "ID 主键")
    private Integer id;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @ApiModelProperty(name = "username", value = "用户名")
    private String username;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @ApiModelProperty(name = "password", value = "密码")
    private String password;
    @TableField(fill = FieldFill.INSERT)
    @ApiModelProperty(name = "salt", value = "盐")
    private String salt;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @ApiModelProperty(name = "age", value = "年龄")
    private Integer age;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @ApiModelProperty(name = "email", value = "邮箱")
    private String email;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @ApiModelProperty(name = "address", value = "地址")
    private String address;
    @TableField(exist = false)
    private List roles = new ArrayList<>();
}
package com.ego.pojo;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
 * @author 袁梦达 2019012364
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("t_role")
@ApiModel("角色实体类")
public class Role implements Serializable {
    /** 数据库中设置该字段自增时该注解不能少 **/
    @TableId(type = IdType.AUTO)
    @ApiModelProperty(name = "id", value = "ID 主键")
    private Integer id;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @ApiModelProperty(name = "name", value = "角色名称")
    private String name;
    @TableField(exist = false)
    private List permissions = new ArrayList<>();
}
package com.ego.pojo;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
 * @author 袁梦达 2019012364
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("t_permission")
@ApiModel("权限实体类")
public class Permission implements Serializable {
    /** 数据库中设置该字段自增时该注解不能少 **/
    @TableId(type = IdType.AUTO)
    @ApiModelProperty(name = "id", value = "ID主键")
    private Integer id;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @ApiModelProperty(name = "name", value = "权限名称")
    private String name;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @ApiModelProperty(name = "url", value = "权限菜单URL")
    private String url;
}

4.实现Controller、Service、Dao

这里dao采用了mybatis-plus

package com.ego.controller;
import com.ego.pojo.User;
import com.ego.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
 * @author 袁梦达 2019012364
 */
@Controller
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    /**
     * 用户登录
     * @param username
     * @param password
     * @return
     */
    @RequestMapping("/login")
    public String login(String username,String password){
        // 获取当前登录用户
        Subject subject = SecurityUtils.getSubject();
        try {
            // 执行登录操作
            subject.login(new UsernamePasswordToken(username,password));
            // 认证通过后直接跳转到index.jsp
            return "redirect:/index.jsp";
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("用户名错误!");
        } catch (IncorrectCredentialsException e) {
            System.out.println("密码错误!");
        } catch (Exception e) {
        }
        // 如果认证失败仍然回到登录页面
        return "redirect:/login.jsp";
    }
    @RequestMapping("/logout")
    public String logout(){
        subject.logout();
        // 退出后仍然会到登录页面
     * 用户注册
     * @param user
    @RequestMapping("/register")
    public String register(User user){
            userService.register(user);
            return "redirect:/login.jsp";
        return "redirect:/register.jsp";
}
package com.ego.service.impl;
import com.ego.dao.mapper.UserMapper;
import com.ego.shiro.ShiroConstant;
import com.ego.utils.SaltUtil;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserServiceImpl implements UserService {
    private UserMapper userMapper;
    @Override
    public void register(User user) {
        //生成随机盐
        String salt = SaltUtil.getSalt(ShiroConstant.SALT_LENGTH);
        //保存随机盐
        user.setSalt(salt);
        //生成密码
        Md5Hash password = new Md5Hash(user.getPassword(), salt, ShiroConstant.HASH_ITERATORS);
        //保存密码
        user.setPassword(password.toHex());
        userMapper.insert(user);
    public User findUserByUserName(String userName) {
        return userMapper.findUserByUserName(userName);
import com.ego.dao.mapper.RoleMapper;
import com.ego.pojo.Role;
import com.ego.service.RoleService;
import java.util.List;
@Service("roleService")
public class RoleServiceImpl implements RoleService {
    private RoleMapper roleMapper;
    public List getRolesByUserId(Integer userId) {
        return roleMapper.getRolesByUserId(userId);
import com.ego.dao.mapper.PermissionMapper;
import com.ego.pojo.Permission;
import com.ego.service.PermissionService;
@Service("permissionService")
public class PermissionServiceImpl implements PermissionService {
    private PermissionMapper permissionMapper;
    public List getPermissionsByRoleId(Integer roleId) {
        return permissionMapper.getPermissionsByRoleId(roleId);
package com.ego.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper extends BaseMapper {
    @Select("SELECT u.id,u.username,u.password,u.salt,u.age,u.email,u.address FROM t_user u WHERE u.username = #{username}")
    User findUserByUserName(String username);
public interface RoleMapper extends BaseMapper {
    @Select("select r.id,r.name from t_role r left join t_user_role ur on ur.role_id = r.id where ur.user_id = #{userId}")
    List getRolesByUserId(Integer userId);
public interface PermissionMapper extends BaseMapper {
    @Select("select p.id,p.name,p.url from t_permission p left join t_role_permission rp on rp.permission_id = p.id where rp.role_id = #{roleId}")
    List getPermissionsByRoleId(Integer roleId);

5.实现SaltUtil和ApplicationContextUtil两个工具类

package com.ego.utils;
import java.util.Random;
/**
 * @author 袁梦达 2019012364
 */
public class SaltUtil {
    public static String getSalt(int n){
        char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890!@#$%^&*()".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();
    }
}
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class ApplicationContextUtil implements ApplicationContextAware {
    public static ApplicationContext context;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    /**
     * 根据工厂中的类名获取类实例
     */
    public static Object getBean(String beanName){
        return context.getBean(beanName);

6.实现核心Shiro

package com.ego.utils;
import java.util.Random;
/**
 * @author 袁梦达 2019012364
 */
public class SaltUtil {
    public static String getSalt(int n){
        char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890!@#$%^&*()".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();
    }
}
package com.ego.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
 * @author 袁梦达 2019012364
 */
@Component
public class ApplicationContextUtil implements ApplicationContextAware {
    public static ApplicationContext context;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }
    /**
     * 根据工厂中的类名获取类实例
     */
    public static Object getBean(String beanName){
        return context.getBean(beanName);
    }
}

7.实现Redis分布式缓存

package com.ego.shiro;
import com.ego.shiro.cache.RedisCacheManager;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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;
import java.util.Map;
/**
 * @author 袁梦达 2019012364
 */
@Configuration
public class ShiroConfiguration {
    //1.创建shiroFilter  //负责拦截所有请求
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //给filter设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        //配置系统受限资源
        //配置系统公共资源
        Map map = new HashMap();
        map.put("/user/login", "anon");
        map.put("/user/register","anon");
        map.put("/register.jsp","anon");
        map.put("/index.jsp","authc");//authc 请求这个资源需要认证和授权
        //默认认证界面路径
        shiroFilterFactoryBean.setLoginUrl("/login.jsp");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }
    //2.创建安全管理器
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //给安全管理器设置
        defaultWebSecurityManager.setRealm(realm);
        return defaultWebSecurityManager;
    //3.创建自定义realm
    public Realm getRealm(){
        CustomerRealm customerRealm = new CustomerRealm();
        // 设置密码匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 设置加密方式
        credentialsMatcher.setHashAlgorithmName(ShiroConstant.HASH_ALGORITHM_NAME.MD5);
        // 设置散列次数
        credentialsMatcher.setHashIterations(ShiroConstant.HASH_ITERATORS);
        customerRealm.setCredentialsMatcher(credentialsMatcher);
        // 设置缓存管理器
        customerRealm.setCacheManager(new RedisCacheManager());
        // 开启全局缓存
        customerRealm.setCachingEnabled(true);
        // 开启认证缓存并指定缓存名称
        customerRealm.setAuthenticationCachingEnabled(true);
        customerRealm.setAuthenticationCacheName("authenticationCache");
        // 开启授权缓存并指定缓存名称
        customerRealm.setAuthorizationCachingEnabled(true);
        customerRealm.setAuthorizationCacheName("authorizationCache");
        return customerRealm;
}
public class ShiroConstant {
    /** 随机盐的位数 **/
    public static final int SALT_LENGTH = 8;
    /** hash的散列次数 **/
    public static final int HASH_ITERATORS = 1024;
    public interface HASH_ALGORITHM_NAME {
        String MD5 = "MD5";
import com.ego.pojo.Permission;
import com.ego.pojo.Role;
import com.ego.pojo.User;
import com.ego.service.PermissionService;
import com.ego.service.RoleService;
import com.ego.service.UserService;
import com.ego.utils.ApplicationContextUtil;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import java.util.List;
public class CustomerRealm extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 获取主身份信息
        String principal = (String) principals.getPrimaryPrincipal();
        // 根据主身份信息获取角色信息
        UserService userService = (UserService) ApplicationContextUtil.getBean("userService");
        User user = userService.findUserByUserName(principal);
        RoleService roleService = (RoleService) ApplicationContextUtil.getBean("roleService");
        List roles = roleService.getRolesByUserId(user.getId());
        if(!CollectionUtils.isEmpty(roles)){
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            roles.forEach(role -> {
                simpleAuthorizationInfo.addRole(role.getName());
                PermissionService permissionService = (PermissionService) ApplicationContextUtil.getBean("permissionService");
                List permissions = permissionService.getPermissionsByRoleId(role.getId());
                if(!CollectionUtils.isEmpty(permissions)){
                    permissions.forEach(permission -> {
                        simpleAuthorizationInfo.addStringPermission(permission.getName());
                    });
                }
            });
            return simpleAuthorizationInfo;
        }
        return null;
    //认证
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String principal = (String) token.getPrincipal();
        if(!ObjectUtils.isEmpty(user)){
            return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), new CustomerByteSource(user.getSalt()),this.getName());
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;
//自定义salt实现  实现序列化接口
public class CustomerByteSource implements ByteSource, Serializable {
    private byte[] bytes;
    private String cachedHex;
    private String cachedBase64;
    public CustomerByteSource() {
    public CustomerByteSource(byte[] bytes) {
        this.bytes = bytes;
    public CustomerByteSource(char[] chars) {
        this.bytes = CodecSupport.toBytes(chars);
    public CustomerByteSource(String string) {
        this.bytes = CodecSupport.toBytes(string);
    public CustomerByteSource(ByteSource source) {
        this.bytes = source.getBytes();
    public CustomerByteSource(File file) {
        this.bytes = (new CustomerByteSource.BytesHelper()).getBytes(file);
    public CustomerByteSource(InputStream stream) {
        this.bytes = (new CustomerByteSource.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);

到此这篇关于SpringBoot整合Shiro和Redis的文章就介绍到这了,更多相关SpringBoot整合Shiro和Redis内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

你可能感兴趣的:(SpringBoot整合Shiro和Redis的示例代码)