目前常用的安全框架为spring security、shiro等,关于二者之间的比较,有兴趣的小伙伴自行查阅相关文档这里不再讲述。这里,我们选择相对轻巧且功能能满足我们项目需要的shiro框架。
shiro框架默认基于cookie-session来管理安全信息,而当前前后端分离、微服务、分布式广泛应用的情况下,显然不太适用。通过参考查询相关文档,其中之一的解决方案是用redis来替换。下面我们就来讲述shiro配置和shiro整合redis。
之前基于cookie-session,用户(subject)通过认证后,信息存入session,身份标志sessionID存入cookie,返回客户端。每次客户端访问服务端,需要通过cookie携带该身份标志,shiro验证通过,然后执行后续操作。
<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>
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();
}
}
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);
}
}
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;
}
}
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 // 前端后台管理系统