当前位置: 首页 > 工具软件 > shiro-redis > 使用案例 >

springboot_shiro_redis整合(一)-shiro-权限控制

柴昆杰
2023-12-01

springboot_shiro_redis整合(一)-shiro-权限控制


目录




内容

一、前言

  目前常用的安全框架为spring security、shiro等,关于二者之间的比较,有兴趣的小伙伴自行查阅相关文档这里不再讲述。这里,我们选择相对轻巧且功能能满足我们项目需要的shiro框架。

  shiro框架默认基于cookie-session来管理安全信息,而当前前后端分离、微服务、分布式广泛应用的情况下,显然不太适用。通过参考查询相关文档,其中之一的解决方案是用redis来替换。下面我们就来讲述shiro配置和shiro整合redis。

二、shiro整合redis

1、分析

  之前基于cookie-session,用户(subject)通过认证后,信息存入session,身份标志sessionID存入cookie,返回客户端。每次客户端访问服务端,需要通过cookie携带该身份标志,shiro验证通过,然后执行后续操作。

  • 改造
    • 封装shiro工具类
    • realm
    • 整合redis配置
    • 身份标志获取

2、pom.xml 导入依赖

<properties>
	<shiro.version>1.4.0</shiro.version>
	<kaptcha.version>0.0.9</kaptcha.version>
</properties>
<dependencies>
	<dependency>
		<groupId>com.ihrm</groupId>
		<artifactId>ihrm-common</artifactId>
		<version>1.0.0-SNAPSHOT</version>
	</dependency>
	<dependency>
		<groupId>org.apache.shiro</groupId>
		<artifactId>shiro-core</artifactId>
		<version>${shiro.version}</version>
	</dependency>
	<dependency>
		<groupId>org.apache.shiro</groupId>
		<artifactId>shiro-spring</artifactId>
		<version>${shiro.version}</version>
	</dependency>
<!--        <dependency>-->
<!--            <groupId>org.apache.shiro</groupId>-->
<!--            <artifactId>shiro-ehcache</artifactId>-->
<!--            <version>${shiro.version}</version>-->
<!--        </dependency>-->
	<dependency>
		<groupId>com.github.axet</groupId>
		<artifactId>kaptcha</artifactId>
		<version>${kaptcha.version}</version>
	</dependency>
	<dependency>
		<groupId>org.crazycake</groupId>
		<artifactId>shiro-redis</artifactId>
		<version>3.1.0</version>
	</dependency>
</dependencies>
  • 解析:
    • shiro-core:shiro核心
    • shiro-spring: 用于spring整合shiro
    • shiro-redis: 第三方shiro整合redis
    • kapcha :第三方验证码生成

2、相关具体类

2.1、封装ShiroUtils 工具类

package com.ihrm.modules.sys.shiro;

import com.ihrm.common.exception.RRException;
import com.ihrm.modules.sys.entity.UserEntity;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;

/**
 * Shiro工具类
 */
public class ShiroUtils {
	/**  加密算法 */
	public final static String hashAlgorithmName = "md5";
	/**  循环次数 */
	public final static int hashIterations = 16;

	public static String md5(String password, String salt) {
		return new SimpleHash(hashAlgorithmName, password, salt, hashIterations).toString();
	}

	public static Session getSession() {
		return SecurityUtils.getSubject().getSession();
	}

	public static Subject getSubject() {
		return SecurityUtils.getSubject();
	}

	public static UserEntity getUserEntity() {
		return (UserEntity) SecurityUtils.getSubject().getPrincipal();
	}

	public static String getId() {
		return getUserEntity().getId();
	}

	public static void setSessionAttribute(Object key, Object value) {
		getSession().setAttribute(key, value);
	}

	public static Object getSessionAttribute(Object key) {
		return getSession().getAttribute(key);
	}

	public static boolean isLogin() {
		return SecurityUtils.getSubject().getPrincipal() != null;
	}

	public static void logout() {
		SecurityUtils.getSubject().logout();
	}

	public static String getKaptcha(String key) {
		Object kaptcha = getSessionAttribute(key);
		if(kaptcha == null){
			throw new RRException("验证码已失效");
		}
		getSession().removeAttribute(key);
		return kaptcha.toString();
	}

}

2.2、UserRealm 自定义realm

package com.ihrm.modules.sys.shiro;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ihrm.common.utils.Constant;
import com.ihrm.modules.sys.dao.PermissionDao;
import com.ihrm.modules.sys.dao.UserDao;
import com.ihrm.modules.sys.entity.PermissionEntity;
import com.ihrm.modules.sys.entity.UserEntity;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.*;

/**
 * 认证
 */
@Component
public class UserRealm extends AuthorizingRealm {
	@Autowired
	private UserDao sysUserDao;
	@Autowired
	private PermissionDao permissionDao;

	/**
	 * 授权(验证权限时调用)
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		UserEntity user = (UserEntity)principals.getPrimaryPrincipal();
		String userId = user.getId();

		List<String> permsList;

		//系统管理员,拥有最高权限
		if(StringUtils.equals(userId,Constant.SUPER_ADMIN)){
			List<PermissionEntity> menuList = permissionDao.selectList(null);
			permsList = new ArrayList<>(menuList.size());
			for(PermissionEntity menu : menuList){
				permsList.add(menu.getPerms());
			}
		}else{
			permsList = permissionDao.queryBtnsByUserId(userId);
		}

		//用户权限列表
		Set<String> permsSet = new HashSet<>();
		for(String perms : permsList){
			if(StringUtils.isBlank(perms)){
				continue;
			}
			permsSet.addAll(Arrays.asList(perms.trim().split(",")));
		}

		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		info.setStringPermissions(permsSet);
		return info;
	}

	/**
	 * 认证(登录时调用)
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken authcToken) throws AuthenticationException {
		UsernamePasswordToken token = (UsernamePasswordToken)authcToken;

		//查询用户信息
		UserEntity user = sysUserDao.selectOne(new QueryWrapper<UserEntity>().eq("mobile", token.getUsername()));
		//账号不存在
		if(user == null) {
			throw new UnknownAccountException("账号或密码不正确");
		}

		//账号锁定
		if(user.getEnableState() == 0){
			throw new LockedAccountException("账号已引用,请联系管理员");
		}

		SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getMobile()), getName());
		return info;
	}

	@Override
	public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
		HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();
		shaCredentialsMatcher.setHashAlgorithmName(ShiroUtils.hashAlgorithmName);
		shaCredentialsMatcher.setHashIterations(ShiroUtils.hashIterations);
		super.setCredentialsMatcher(shaCredentialsMatcher);
	}

	/**
	 * 清除当前用户的权限认证缓存
	 *
	 * @param principals 权限信息 身份集合
	 */
	@Override
	public void clearCache(PrincipalCollection principals) {
		super.clearCache(principals);
	}
}

2.3、ShiroConfig 配置类

package com.ihrm.common.config;

import com.ihrm.modules.sys.shiro.CustomSessionManager;
import com.ihrm.modules.sys.shiro.UserRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
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.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.session.mgt.ServletContainerSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;


/**
 * Shiro的配置文件
 */
@Configuration
public class ShiroConfig {

	@Value("${spring.redis.host}")
	private String host;

	@Value("${spring.redis.port}")
	private int port;

	@Value("${spring.redis.password}")
	private String password;

	@Value("${spring.redis.timeout}")
	private int timeout;

//	@Bean(name = "redisSessionDAO")
//	public RedisSessionDAO sessionDAO() {
//		return new RedisSessionDAO();
//	}

	@Bean
	public RedisManager redisManager() {
		RedisManager redisManager = new RedisManager();
//        redisManager.setHost(host);
//        redisManager.setTimeout(timeout);
//        if (StringUtils.isNotBlank(password))
//            redisManager.setPassword(password);

		return redisManager;
	}

	@Bean
	public RedisCacheManager redisCacheManager() {
		RedisCacheManager cacheManager = new RedisCacheManager();
		cacheManager.setRedisManager(redisManager());
		return cacheManager;
	}

	@Bean
	public RedisSessionDAO redisSessionDAO() {
		RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
		redisSessionDAO.setRedisManager(redisManager());
		return redisSessionDAO;
	}

	/**
	 * 单机环境,session交给shiro管理
	 * 会话管理器
	 */
	@Bean("sessionManager")
	@ConditionalOnProperty(prefix = "ihrm", name = "cluster", havingValue = "false")
	public DefaultWebSessionManager sessionManager(
			@Value("${ihrm.globalSessionTimeout:3600}") long globalSessionTimeout) {
		CustomSessionManager sessionManager = new CustomSessionManager();
		// redis session
		sessionManager.setSessionDAO(redisSessionDAO());
		sessionManager.setSessionValidationSchedulerEnabled(true);
		// 禁用重写url
		// sessionManager.setSessionIdUrlRewritingEnabled(false);
		// 禁用session 放在cookie
		// sessionManager.setSessionIdCookieEnabled(false);
		sessionManager.setSessionValidationInterval(globalSessionTimeout * 1000);
		// 会话过期时间,单位:毫秒(在无操作时开始计时)--->一分钟,用于测试
		sessionManager.setGlobalSessionTimeout(globalSessionTimeout * 1000);

		return sessionManager;
	}

	/**
	 * 集群环境,session交给spring-session管理
	 */
	@Bean
	@ConditionalOnProperty(prefix = "ihrm", name = "cluster", havingValue = "true")
	public ServletContainerSessionManager servletContainerSessionManager() {
		return new ServletContainerSessionManager();
	}


	private SimpleCookie rememberMeCookie() {
		// 设置 cookie 名称,对应 login.html 页面的 <input type="checkbox" name="rememberMe"/>
		SimpleCookie cookie = new SimpleCookie("rememberMe");
		// 设置 cookie 的过期时间,单位为秒 一个月
		cookie.setMaxAge(3600 * 30);
		return cookie;
	}


	private CookieRememberMeManager rememberMeManager() {
		CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
		cookieRememberMeManager.setCookie(rememberMeCookie());
		// rememberMe cookie 加密的密钥
		cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
		return cookieRememberMeManager;
	}


	@Bean("securityManager")
	public SecurityManager securityManager(UserRealm userRealm, SessionManager sessionManager) {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//		securityManager.setCacheManager(new EhCacheManager());
		securityManager.setCacheManager(redisCacheManager());
		securityManager.setRealm(userRealm);
		securityManager.setSessionManager(sessionManager);
		securityManager.setRememberMeManager(rememberMeManager());

		return securityManager;
	}

	@Bean("shiroFilter")
	public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
		ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
		shiroFilter.setSecurityManager(securityManager);
		shiroFilter.setLoginUrl("/login");
		shiroFilter.setUnauthorizedUrl("/");
		// shiroFilter.setSuccessUrl("/index.html");

		//自定义过滤器
//        Map<String, Filter> filterMap1 = new LinkedHashMap<>();
//
//        filterMap1.put("user",new MyUserFilter());
//        shiroFilter.setFilters(filterMap1);

		Map<String, String> filterMap = new LinkedHashMap<>();
		filterMap.put("/swagger/**", "anon");
//		filterMap.put("/v2/api-docs", "anon");
		filterMap.put("/swagger-ui.html", "anon");
		filterMap.put("/webjars/**", "anon");
		filterMap.put("/swagger-resources/**", "anon");
		filterMap.put("/doc.html", "anon");

		filterMap.put("/statics/**", "anon");
		filterMap.put("/favicon.ico", "anon");
		filterMap.put("/sys/captcha", "anon");
		filterMap.put("/sys/login", "anon");
//        filterMap.put("/sys/getInfo", "anon");
		filterMap.put("/**", "authc");
		shiroFilter.setFilterChainDefinitionMap(filterMap);

		return shiroFilter;
	}

	@Bean("lifecycleBeanPostProcessor")
	public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
		return new LifecycleBeanPostProcessor();
	}

	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
		AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
		advisor.setSecurityManager(securityManager);
		return advisor;
	}

	@Bean
	public HashedCredentialsMatcher hashedCredentialsMatcher() {
		HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
		credentialsMatcher.setHashAlgorithmName("MD5"); //加密方式
		credentialsMatcher.setHashIterations(16);//散列次数
		return credentialsMatcher;

	}
}

2.4、CustomSessionManager 身份标志获取

package com.ihrm.modules.sys.shiro;

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;

public class CustomSessionManager extends DefaultWebSessionManager {
	/**
	 * 获取请求头中key为“Authorization”的value == sessionId
	 */
	private static final Logger log = LoggerFactory.getLogger(CustomSessionManager.class);
	private static final String TOKEN = "Authorization";

	private static final String REFERENCED_SESSION_ID_SOURCE = "header";

	/**
	 * @Description shiro框架 自定义session获取方式<br/>
	 * 可自定义session获取规则。这里采用ajax请求头 Authorization 携带sessionId的方式
	 */
	@Override
	protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
		String id = WebUtils.toHttp(request).getHeader(TOKEN);
		if (StringUtils.isBlank(id)) {
			id = WebUtils.toHttp(request).getParameter(TOKEN);
		}
		// 如果请求头中有 Authorization 则其值为sessionId
		if (StringUtils.isNotBlank(id)) {
			log.info("请求头中获取");
			request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
			request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
			request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
			return id;
		} else {
			log.info("默认方式获取sessionId");
			return super.getSessionId(request, response);
		}

	}
}

后记

本项目为参考某马视频开发,相关视频及配套资料可自行度娘或者联系本人。上面为自己编写的开发文档,持续更新。欢迎交流,本人QQ:806797785

后端JAVA源代码地址:https://gitee.com/gaogzhen/ihrm-parent    // 后端项目
前端项目源代码地址:https://gitee.com/gaogzhen/ihrm-vue    // 前端后台管理系统
 类似资料: