SpringBoot整合Shiro和Redis的示例代码_java

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);