素材巴巴 > 程序开发 >

spring boot shiro redis 整合(完整)

程序开发 2023-09-14 19:06:51

spring boot + shiro + redis 整合(完整)

  • 5.后台代码
  • 6.创建ShiroConfig类
  • 7.Realm(凭证认证、权限获取)
  • 8.限制并发人数登陆
  • 9.Controller层
  • 10.页面
  • 什么是shiro?

    shiro是一个Java平台的开源权限框架,用于认证和访问授权。具体来说,满足对如下元素的支持:

    1.数据库设计

    数据库中有分别有6个表,分别是:权限表(permissions)、用户表(users)、用户权限关系表(user_role)、角色表(roles)、角色权限关系表(role_permission)、shiro过滤器表( resource)

    数据库架构设计图

    数据库下载地址: https://pan.baidu.com/s/1AMKwNfRimPPzqWHY30S1HQ 提取码: r9oh

    2.创建springboot项目并在pom加入依赖

      org.projectlomboklomboktrueorg.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-thymeleaforg.springframework.bootspring-boot-starter-testtestjavax.servletjstlorg.apache.tomcat.embedtomcat-embed-jasperprovidedcom.alibabafastjson1.2.47org.mybatis.spring.bootmybatis-spring-boot-starter1.1.1org.springframework.bootspring-boot-starter-data-redisorg.crazycakeshiro-redis2.4.6mysqlmysql-connector-javacom.alibabadruid1.0.9org.apache.shiroshiro-spring1.4.0redis.clientsjedis2.8.0com.github.theborakompanionithymeleaf-extras-shiro2.0.0org.apache.shiroshiro-core1.2.2com.fasterxml.jackson.corejackson-databind
     

    3. 编辑application.yml

    #springboot
     server:port: 8080spring:datasource:url: jdbc:mysql://localhost:3306/shiro?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8username: rootpassword: 123456driver-class-name: com.mysql.jdbc.Driverredis:host: localhostport: 6379jedis:pool:max-idle: 8min-idle: 0max-active: 8max-wait: -1timeout: 0
     mybatis:mapper-locations: classpath:mapper/*.xml    #*/type-aliases-package: com.jbl.springboot_redis_shiro.entity

    4.建包搭架子(先把包建完)

    在这里插入图片描述

    4.1创建实体类

    /*** @Author: yfj* @Description: 资源* Serializable 要缓存的JavaBean必须实现Serializable接口,因为Spring会将对象先序列化再存入 Redis*/
     @Data
     public class Resources implements Serializable {public Integer id;public String key;public  String val;public Integer order;
     }
     
    /*** @Author: yfj* @Description: 用户信息* Serializable 要缓存的JavaBean必须实现Serializable接口,因为Spring会将对象先序列化再存入 Redis*/
     @Data
     public class User implements Serializable {public Integer u_id;public String u_name;public String u_pwd;
     }
     

    我用的lombok插件,简化get、set、ToString等方法想学习的可以点击下面链接:
    lombok基本使用和安装

    4.2springboot启动类

    @SpringBootApplication
     @MapperScan("com.jbl.springboot_redis_shiro.dao") //这里填的是自己dao层接口的路径
     public class SpringbootRedisShiroApplication {public static void main(String[] args) {SpringApplication.run(SpringbootRedisShiroApplication.class, args);}
     }
     

    4.3启动springboot自动跳转到登陆页面

    /*** 启动项目自动跳转到登陆页面*/
     @SpringBootConfiguration
     public class AutoStartProjectInDefaultBrowser implements CommandLineRunner {@Value("${server.port}")private String port;@Overridepublic void run(String ... args) throws Exception {try {Runtime.getRuntime().exec("cmd /c start http://localhost:" + port + "/shiro/login");} catch (Exception ex) {ex.printStackTrace();}}
     }
     

    4.4全局异常类

    /*** @author yfj* @version 1.0* @date 2020/04/16 下午 03:11* @description: 全局异常类*/
     @ControllerAdvice
     public class CtrlExceptionHandler {private static Logger logger = LoggerFactory.getLogger(CtrlExceptionHandler.class);//拦截未授权页面@ResponseStatus(value = HttpStatus.FORBIDDEN)@ExceptionHandler(UnauthorizedException.class)public String handleException(UnauthorizedException e) {logger.debug(e.getMessage());return "403";}@ResponseStatus(value = HttpStatus.FORBIDDEN)@ExceptionHandler(AuthorizationException.class)public String handleException2(AuthorizationException e) {logger.debug(e.getMessage());return "403";}
     }
     

    5.后台代码

    5.1 Mapper.xml

    
     
     
     

    5.2 Mapper接口

    import com.jbl.springboot_redis_shiro.entity.Resources;
     import com.jbl.springboot_redis_shiro.entity.User;
     import org.apache.ibatis.annotations.Param;
     import org.springframework.stereotype.Repository;import java.util.List;
     import java.util.Set;/*** @author yfj* @version 1.0* @date 2020/04/15 下午 02:40* @description:*/
     @Repository
     public interface ShiroMapper {/*** 登陆* @param name 用户名* @param u_pwd 密码* @return*/User login(@Param("name")String name,@Param("u_pwd")String u_pwd);/*** 查询资源* @return List*/List orders();/*** 根据用户查询角色* @param userName* @return*/Set seRoles(@Param("userName")String userName);/*** 根据名字查询权限* @param userName* @return*/Set quanxian(@Param("userName")String userName);
     }
     

    5.3 server接口

    import com.jbl.springboot_redis_shiro.entity.Resources;
     import com.jbl.springboot_redis_shiro.entity.User;import java.util.List;
     import java.util.Set;/*** @author yfj* @version 1.0* @date 2020/04/15 下午 02:42* @description:*/
     public interface ShiroServer {/*** 登陆* @return*/User login(String name,String u_Pwd);/*** 查询资源* @return List*/List orders();/*** 根据用户查询角色* @param userName* @return*/Set seRoles(String userName);/*** 根据名字查询权限* @param userName* @return*/Set quanxian(String userName);
     }
     

    server实现类

    import com.jbl.springboot_redis_shiro.dao.ShiroMapper;
     import com.jbl.springboot_redis_shiro.entity.Resources;
     import com.jbl.springboot_redis_shiro.entity.User;
     import com.jbl.springboot_redis_shiro.server.ShiroServer;
     import org.springframework.beans.factory.annotation.Autowired;
     import org.springframework.stereotype.Service;import java.util.List;
     import java.util.Set;/*** @author yfj* @version 1.0* @date 2020/04/15 下午 02:43* @description:*/
     @Service("ShiroServerImpl")
     public class ShiroServerImpl implements ShiroServer{@Autowiredprivate ShiroMapper shiroMapper;@Overridepublic User login(String name, String u_Pwd) {return shiroMapper.login(name,u_Pwd);}@Overridepublic List orders() {return shiroMapper.orders();}@Overridepublic Set seRoles(String userName) {return shiroMapper.seRoles(userName);}@Overridepublic Set quanxian(String userName) {return shiroMapper.quanxian(userName);}
     }
     

    6.创建ShiroConfig类

    import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
     import com.jbl.springboot_redis_shiro.server.ShiroServer;
     import org.apache.shiro.mgt.SecurityManager;
     import org.apache.shiro.spring.LifecycleBeanPostProcessor;
     import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
     import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
     import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
     import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
     import org.crazycake.shiro.RedisCacheManager;
     import org.crazycake.shiro.RedisManager;
     import org.crazycake.shiro.RedisSessionDAO;
     import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
     import org.springframework.beans.factory.annotation.Autowired;
     import org.springframework.context.annotation.Bean;
     import org.springframework.context.annotation.Configuration;import javax.servlet.Filter;
     import java.util.LinkedHashMap;
     import java.util.Map;/*** @author yfj* @version 1.0* @date 2020/04/15 下午 07:06* @description: shiro配置*/
     @Configuration
     public class ShiroConfig {@Autowiredprivate ShiroServer shiroServer;@Beanpublic ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();//设置安全管理器shiroFilterFactoryBean.setSecurityManager(securityManager);// 没有登陆的用户只能访问登陆页面shiroFilterFactoryBean.setLoginUrl("/shiro/login");// 登录成功后要跳转的链接shiroFilterFactoryBean.setSuccessUrl("/shiro/index");// 未授权界面; ----这个配置了没卵用,具体原因想深入了解的可以自行百度shiroFilterFactoryBean.setUnauthorizedUrl("/shiro/wuquan");//自定义拦截器Map filtersMap = new LinkedHashMap();//限制同一帐号同时在线的个数。filtersMap.put("kickout", kickoutSessionControlFilter());shiroFilterFactoryBean.setFilters(filtersMap);// 权限控制map.Map filterChainDefinitionMap = new LinkedHashMap();
     /*我加了下面这段代码启动就报错,如果有人知道是为什么请联系我,谢谢List list=shiroServer.orders();for(Resources itms:list){filterChainDefinitionMap.put(itms.getKey(),itms.getVal());}*///添加Shiro内置过滤器/*** Shiro内置过滤器,可以实现权限相关的拦截器*    常用的过滤器:*       anon: 无需认证(登录)可以访问*       authc: 必须认证才可以访问*       user: 如果使用rememberMe的功能可以直接访问*       perms: 该资源必须得到资源权限才可以访问*       role: 该资源必须得到角色权限才可以访问*/filterChainDefinitionMap.put("/shiro/kickout", "anon");filterChainDefinitionMap.put("/shiro/login", "anon");filterChainDefinitionMap.put("/shiro/submitLogin", "anon");filterChainDefinitionMap.put("/shiro/index", "authc");filterChainDefinitionMap.put("/shiro/tianjia", "perms[insert]");filterChainDefinitionMap.put("/shiro/update", "perms[update]");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}@Beanpublic SecurityManager securityManager() {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 设置realm.securityManager.setRealm(myShiroRealm());// 自定义缓存实现 使用redissecurityManager.setCacheManager(cacheManager());// 自定义session管理 使用redissecurityManager.setSessionManager(sessionManager());return securityManager;}/*** 身份认证realm; (这个需要自己写,账号密码校验;权限等)* @return*/@Beanpublic MyShiroRealm myShiroRealm() {MyShiroRealm myShiroRealm = new MyShiroRealm();return myShiroRealm;}/*** cacheManager 缓存 redis实现* 使用的是shiro-redis开源插件** @return*/public RedisCacheManager cacheManager() {RedisCacheManager redisCacheManager = new RedisCacheManager();redisCacheManager.setRedisManager(redisManager());return redisCacheManager;}/*** 配置shiro redisManager* 使用的是shiro-redis开源插件** @return*/public RedisManager redisManager() {RedisManager redisManager = new RedisManager();redisManager.setHost("localhost");redisManager.setPort(6379);redisManager.setExpire(1800);// 配置缓存过期时间redisManager.setTimeout(0);// redisManager.setPassword(password);return redisManager;}/*** Session Manager* 使用的是shiro-redis开源插件*/@Beanpublic DefaultWebSessionManager sessionManager() {DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();sessionManager.setSessionDAO(redisSessionDAO());return sessionManager;}/*** RedisSessionDAO shiro sessionDao层的实现 通过redis* 使用的是shiro-redis开源插件*/@Beanpublic RedisSessionDAO redisSessionDAO() {RedisSessionDAO redisSessionDAO = new RedisSessionDAO();redisSessionDAO.setRedisManager(redisManager());return redisSessionDAO;}/*** 限制同一账号登录同时登录人数控制** @return*/@Beanpublic KickoutSessionControlFilter kickoutSessionControlFilter() {KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();kickoutSessionControlFilter.setCacheManager(cacheManager());kickoutSessionControlFilter.setSessionManager(sessionManager());kickoutSessionControlFilter.setKickoutAfter(false);kickoutSessionControlFilter.setMaxSession(1);kickoutSessionControlFilter.setKickoutUrl("/shiro/kickout");return kickoutSessionControlFilter;}/**** 授权所用配置** @return*/@Beanpublic DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);return defaultAdvisorAutoProxyCreator;}/**** 使授权注解起作用不如不想配置可以在pom文件中加入* *org.springframework.boot*spring-boot-starter-aop** @param securityManager* @return*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}/*** Shiro生命周期处理器**/@Beanpublic LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}/*** 配置ShiroDialect,用于thymeleaf和shiro标签配合使用*/@Beanpublic ShiroDialect getShiroDialect(){return new ShiroDialect();}
     

    7.Realm(凭证认证、权限获取)

    package com.jbl.springboot_redis_shiro.shiroconfig;import com.jbl.springboot_redis_shiro.entity.User;
     import com.jbl.springboot_redis_shiro.server.ShiroServer;
     import org.apache.shiro.authc.*;
     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.slf4j.LoggerFactory;
     import org.springframework.beans.factory.annotation.Autowired;import java.util.*;/*** @author yfj* @version 1.0* @date 2020/04/15 下午 07:16* @description:*/
     public class MyShiroRealm extends AuthorizingRealm {private static org.slf4j.Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);@Autowiredprivate ShiroServer shiroServer;/*** 认证信息.(身份验证) : Authentication 是用来验证用户身份**/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {logger.info("---------------- 执行 Shiro 凭证认证 ----------------------");UsernamePasswordToken token = (UsernamePasswordToken) authcToken;String name = token.getUsername();String password = String.valueOf(token.getPassword());// 从数据库获取对应用户名密码的用户User userList = shiroServer.login(name,password);if (userList == null) { //用户名不存在return null; //  shiro底层会抛出UnKnoAccountException 异常}logger.info("---------------- Shiro 凭证认证成功 ----------------------");//判断密码/*** 第一个参数是否返回给controller里的login数据* 第二个参数是数据库的密码* 第三个参数是名字*/return new SimpleAuthenticationInfo(userList,userList.getU_pwd(),"");}/*** 授权*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {logger.info("---------------- 执行 Shiro 权限获取 ---------------------");Object principal = principals.getPrimaryPrincipal();SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();if (principal instanceof User) {User userLogin = (User) principal;//根据名字查询角色Set roles = shiroServer.seRoles(userLogin.getU_name());authorizationInfo.addRoles(roles);//根据名字查询权限Set permissions = shiroServer.quanxian(userLogin.getU_name());authorizationInfo.addStringPermissions(permissions);}logger.info("---- 获取到以下权限 ----");logger.info(authorizationInfo.getStringPermissions().toString());logger.info("---------------- Shiro 权限获取成功 ----------------------");return authorizationInfo;}}

    8.限制并发人数登陆

    package com.jbl.springboot_redis_shiro.shiroconfig;import com.alibaba.fastjson.JSON;
     import com.jbl.springboot_redis_shiro.entity.User;
     import org.apache.shiro.cache.Cache;
     import org.apache.shiro.cache.CacheManager;
     import org.apache.shiro.session.Session;
     import org.apache.shiro.session.mgt.DefaultSessionKey;
     import org.apache.shiro.session.mgt.SessionManager;
     import org.apache.shiro.subject.Subject;
     import org.apache.shiro.web.filter.AccessControlFilter;
     import org.apache.shiro.web.util.WebUtils;import javax.servlet.ServletRequest;
     import javax.servlet.ServletResponse;
     import javax.servlet.http.HttpServletRequest;
     import java.io.IOException;
     import java.io.PrintWriter;
     import java.io.Serializable;
     import java.util.Deque;
     import java.util.HashMap;
     import java.util.LinkedList;
     import java.util.Map;
     /*** @author yfj* @version 1.0* @date 2020/04/15 下午 07:17* @description: 限制并发人数登陆*/
     public class KickoutSessionControlFilter extends AccessControlFilter {private String kickoutUrl; //踢出后到的地址private boolean kickoutAfter = false; //踢出之前登录的/之后登录的用户 默认踢出之前登录的用户private int maxSession = 1; //同一个帐号最大会话数 默认1private SessionManager sessionManager;private Cache> cache;public void setKickoutUrl(String kickoutUrl) {this.kickoutUrl = kickoutUrl;}public void setKickoutAfter(boolean kickoutAfter) {this.kickoutAfter = kickoutAfter;}public void setMaxSession(int maxSession) {this.maxSession = maxSession;}public void setSessionManager(SessionManager sessionManager) {this.sessionManager = sessionManager;}//设置Cache的key的前缀public void setCacheManager(CacheManager cacheManager) {this.cache = cacheManager.getCache("shiro_redis_cache");}@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {return false;}@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {Subject subject = getSubject(request, response);if(!subject.isAuthenticated() && !subject.isRemembered()) {//如果没有登录,直接进行之后的流程return true;}Session session = subject.getSession();User user = (User) subject.getPrincipal();String username = user.getU_name();Serializable sessionId = session.getId();//读取缓存   没有就存入Deque deque = cache.get(username);//如果此用户没有session队列,也就是还没有登录过,缓存中没有//就new一个空队列,不然deque对象为空,会报空指针if(deque==null){deque = new LinkedList();}//如果队列里没有此sessionId,且用户没有被踢出;放入队列if(!deque.contains(sessionId) && session.getAttribute("kickout") == null) {//将sessionId存入队列deque.push(sessionId);//将用户的sessionId队列缓存cache.put(username, deque);}//如果队列里的sessionId数超出最大会话数,开始踢人while(deque.size() > maxSession) {Serializable kickoutSessionId = null;if(kickoutAfter) { //如果踢出后者kickoutSessionId = deque.removeFirst();//踢出后再更新下缓存队列cache.put(username, deque);} else { //否则踢出前者kickoutSessionId = deque.removeLast();//踢出后再更新下缓存队列cache.put(username, deque);}try {//获取被踢出的sessionId的session对象Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));if(kickoutSession != null) {//设置会话的kickout属性表示踢出了kickoutSession.setAttribute("kickout", true);}} catch (Exception e) {//ignore exception}}//如果被踢出了,直接退出,重定向到踢出后的地址if (session.getAttribute("kickout") != null) {//会话被踢出了try {//退出登录subject.logout();} catch (Exception e) { //ignore}saveRequest(request);Map resultMap = new HashMap();//判断是不是Ajax请求if ("XMLHttpRequest".equalsIgnoreCase(((HttpServletRequest) request).getHeader("X-Requested-With"))) {resultMap.put("user_status", "300");resultMap.put("message", "您已经在其他地方登录,请重新登录!");//输出json串out(response, resultMap);}else{//重定向WebUtils.issueRedirect(request, response, kickoutUrl);}return false;}return true;}private void out(ServletResponse hresponse, Map resultMap)throws IOException {try {hresponse.setCharacterEncoding("UTF-8");PrintWriter out = hresponse.getWriter();out.println(JSON.toJSONString(resultMap));out.flush();out.close();} catch (Exception e) {System.err.println("KickoutSessionFilter.class 输出JSON异常,可以忽略。");}}
     }

    9.Controller层

    import com.jbl.springboot_redis_shiro.server.ShiroServer;
     import org.apache.shiro.SecurityUtils;
     import org.apache.shiro.authc.*;
     import org.apache.shiro.crypto.hash.SimpleHash;
     import org.apache.shiro.subject.Subject;
     import org.apache.shiro.util.ByteSource;
     import org.springframework.beans.factory.annotation.Autowired;
     import org.springframework.stereotype.Controller;
     import org.springframework.ui.Model;
     import org.springframework.web.bind.annotation.RequestMapping;/*** @author yfj* @version 1.0* @date 2020/04/15 下午 02:37* @description: 控制层*/
     @RequestMapping("/shiro")
     @Controller
     public class ControllerDemo {@Autowiredprivate ShiroServer shiroServer;@RequestMapping("/login")public String login(){return "login";}//被踢出后跳转的页面@RequestMapping("/kickout")public String kickout(){return "kickout";}@RequestMapping("/submitLogin")public String submitLogin(String username, String password, Model model) {//加密格式String hashAlgorithmName="MD5";//用户输入的密码String credentials =password;//盐值Object salt = ByteSource.Util.bytes (username) ;//加密次数int hashIterations = 1024;//加密Object result =new SimpleHash(hashAlgorithmName,credentials, salt, hashIterations);System.out.println(result);try {//使用shiro 编写认证操作UsernamePasswordToken token = new UsernamePasswordToken(username, String.valueOf(result));//拿到subjectSubject subject = SecurityUtils.getSubject();// 执行登陆方法subject.login(token);// 执行到这里说明用户已登录成功return "redirect:/shiro/index";}catch (IncorrectCredentialsException e) {model.addAttribute("msg", "密码错误");return "login";}catch (UnknownAccountException e){  //如果有登陆失败就会跳入这里model.addAttribute("msg","用户名不存在");   //UnknownAccountException(用户不存在)return "login";}}@RequestMapping("/tianjia")public String tianjia(){return "user/tianjai";}@RequestMapping("/update")public String update(){return "user/update";}@RequestMapping("/index")public String index(){return "user/index";}@RequestMapping("/wuquan")public String wuquan(){return "yichang";}
     }

    10.页面

    在这里插入图片描述
    1.index.html (主页面)

    
     
     Title
     
     
     
     
     
     
     

    2.tianjii.html(添加页面)

    
     
     Title
     
     
     

    添加

    3.update (修改页面)

    
     
     Title
     
     

    修改

    4.异常页面

    
     
     Title
     
     

    异常页面

    5.多台设备登陆此账号提醒页面

    
     
     Title
     
     
     

    多台设备登陆此账号提醒页面

    6.登陆页面

    
     
     登陆页面
     
     
     
     
    用户名:
    密码:

    7.无权限访问提醒页面

    
     
     Title
     
     
     

    你无权访问这个页面


    标签:

    素材巴巴 Copyright © 2013-2021 http://www.sucaibaba.com/. Some Rights Reserved. 备案号:备案中。