SpringBoot整合Shiro和Redis的示例代码
此demo用SpringBoot+Shiro简单实现了登陆、注册、认证、授权的功能,并用redis做分布式缓存提高性能。
1.准备工作
导入pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.2.RELEASE</version> </parent> <groupId>com.ego</groupId> <artifactId>shirodemo</artifactId> <version>1.0-SNAPSHOT</version> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpcore</artifactId> <version>4.4.13</version> </dependency> <!-- 引入jsp依赖 --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.5.3</version> </dependency> <!-- mybatis plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> </dependency> <!-- Druid数据源 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <!-- Mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.5.3</version> </dependency> <!-- 引入redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies> </project>
配置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" %> <!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>index</title> </head> <body> <form action="${pageContext.request.contextPath}/user/login" method="post"> 用户名:<input type="text" name="username" > <br/> 密码 : <input type="text" name="password"> <br> <input type="submit" value="登录"> </form> </body> </html> <%--解决页面乱码--%> <%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %> <%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>index</title> </head> <body> <h1>系统主页</h1> <a href="${pageContext.request.contextPath}/user/logout">退出用户</a> <ul> <%-- admin角色的用户能同时拥有用户管理和订单管理的权限,user角色的用户只拥有订单管理的权限 --%> <shiro:hasRole name="admin"> <li> <a href="">用户管理</a> </li> </shiro:hasRole> <%-- admin角色的用户对订单有增删改查的权限,user角色的用户只能查看订单 --%> <shiro:hasAnyRoles name="admin,user"> <li> <a href="">订单管理</a> <ul> <shiro:hasPermission name="order:add:*"> <li><a href="">新增</a></li> </shiro:hasPermission> <shiro:hasPermission name="order:del:*"> <li><a href="">删除</a></li> </shiro:hasPermission> <shiro:hasPermission name="order:upd:*"> <li><a href="">修改</a></li> </shiro:hasPermission> <shiro:hasPermission name="order:find:*"> <li><a href="">查询</a></li> </shiro:hasPermission> </ul> </li> </shiro:hasAnyRoles> </ul> </body> </html> <%--解决页面乱码--%> <%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %> <!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>register</title> </head> <body> <h1>用户注册</h1> <form action="${pageContext.request.contextPath}/user/register" method="post"> 用户名:<input type="text" name="username" > <br/> 密码 : <input type="text" name="password"> <br> <input type="submit" value="立即注册"> </form> </body> </html>
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<Role> 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<Permission> 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<Role> 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<Permission> 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<User> { @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<Role> { @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<Role> getRolesByUserId(Integer userId); public interface PermissionMapper extends BaseMapper<Permission> { @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<Permission> 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<String,String> map = new HashMap<String,String>(); 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<Role> 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<Permission> 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内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!
赞 (0)