spring boot shiro redis 整合(完整)
程序开发
2023-09-14 19:06:51
spring boot + shiro + redis 整合(完整)
什么是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.projectlombok lombok true org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-test test javax.servlet jstl org.apache.tomcat.embed tomcat-embed-jasper provided com.alibaba fastjson 1.2.47 org.mybatis.spring.boot mybatis-spring-boot-starter 1.1.1 org.springframework.boot spring-boot-starter-data-redis org.crazycake shiro-redis 2.4.6 mysql mysql-connector-java com.alibaba druid 1.0.9 org.apache.shiro shiro-spring 1.4.0 redis.clients jedis 2.8.0 com.github.theborakompanioni thymeleaf-extras-shiro 2.0.0 org.apache.shiro shiro-core 1.2.2 com.fasterxml.jackson.core jackson-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
你无权访问这个页面
标签:
上一篇:
Google Map API Version3 教程(二):地图的类型
下一篇:
相关文章
-
无相关信息