shiro框架是一个权限管理开源框架,也是非常容易整合的。在之前的博文中,我对权限管理,shiro基本知识,spring整合shiro等相关处理都进行了非常详细的讲解。
入门:https://blog.csdn.net/Cs_hnu_scw/article/details/79012284
实践:https://blog.csdn.net/Cs_hnu_scw/article/details/79030687
但是,很很多麻烦的问题,自己也确实遇到,就是很多项目中,都用了这种框架,但是很多时候都要重复进行开发,也挺难受的,而且也有同学说想知道项目中,shiro的完整处理方案是如何,所以,针对这样的问题,自己就花了点时间去阅读了下“Guns开源后台管理系统”的源码,然后自己提取,编写以及优化了一个完整的shiro项目使用级别的源码。讲真,里面还是有很多坑的,但是阅读源码是一个耐性的养成,所以,后续如果在阅读的时候,大家也要耐性点哈。。。
org.apache.shiro
shiro-core
1.4.0
org.apache.shiro
shiro-spring
1.4.0
org.apache.shiro
shiro-ehcache
1.4.0
org.ehcache
ehcache
3.3.1
package com.scw.springboot.shiro.config;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.codec.Base64;
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.Cookie;
import org.apache.shiro.web.servlet.ShiroHttpSession;
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.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* shiro权限管理的配置
* @author scw
*/
@Configuration
public class ShiroConfig {
/**
* 安全管理器
*/
@Bean
public DefaultWebSecurityManager securityManager(CookieRememberMeManager rememberMeManager, CacheManager cacheShiroManager, SessionManager sessionManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(this.shiroDbRealm());
securityManager.setCacheManager(cacheShiroManager);
securityManager.setRememberMeManager(rememberMeManager);
securityManager.setSessionManager(sessionManager);
return securityManager;
}
/**
* spring session管理器(多机环境)
*/
@Bean
@ConditionalOnProperty(prefix = "shiro", name = "spring-session-open", havingValue = "true")
public ServletContainerSessionManager servletContainerSessionManager() {
return new ServletContainerSessionManager();
}
/**
* session管理器(单机环境)
*/
@Bean
@ConditionalOnProperty(prefix = "shiro", name = "spring-session-open", havingValue = "false")
public DefaultWebSessionManager defaultWebSessionManager(CacheManager cacheShiroManager) {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setCacheManager(cacheShiroManager);
//session验证时间可以随便设置
sessionManager.setSessionValidationInterval(15 * 60 * 1000);
//session失效时间
sessionManager.setGlobalSessionTimeout(30 * 60 * 1000);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionValidationSchedulerEnabled(true);
Cookie cookie = new SimpleCookie(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
cookie.setName("shiroCookie");
cookie.setHttpOnly(true);
sessionManager.setSessionIdCookie(cookie);
return sessionManager;
}
/**
* 缓存管理器 使用Ehcache实现
*/
@Bean
public CacheManager getCacheShiroManager(EhCacheManagerFactoryBean ehcache) {
EhCacheManager ehCacheManager = new EhCacheManager();
ehCacheManager.setCacheManager(ehcache.getObject());
return ehCacheManager;
}
/**
* 项目自定义的Realm
*/
@Bean
public ShiroDbRealm shiroDbRealm() {
return new ShiroDbRealm();
}
/**
* rememberMe管理器, cipherKey生成见{@code Base64Test.java}
*/
@Bean
public CookieRememberMeManager rememberMeManager(SimpleCookie rememberMeCookie) {
CookieRememberMeManager manager = new CookieRememberMeManager();
manager.setCipherKey(Base64.decode("Z3VucwAAAAAAAAAAAAAAAA=="));
manager.setCookie(rememberMeCookie);
return manager;
}
/**
* 记住密码Cookie
*/
@Bean
public SimpleCookie rememberMeCookie() {
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
simpleCookie.setHttpOnly(true);
simpleCookie.setMaxAge(7 * 24 * 60 * 60);//7天
return simpleCookie;
}
/**
* Shiro的过滤器链
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
/**
* 默认的登陆访问url
*/
shiroFilter.setLoginUrl("/tologin");
/**
* 登陆成功后跳转的url
*/
shiroFilter.setSuccessUrl("/");
/**
* 没有权限跳转的url
*/
shiroFilter.setUnauthorizedUrl("/global/error");
/**
* 覆盖默认的user拦截器(默认拦截器解决不了ajax请求 session超时的问题,若有更好的办法请及时反馈作者)
*/
HashMap myFilters = new HashMap<>();
myFilters.put("user", new ShiroUserFilter());
shiroFilter.setFilters(myFilters);
/**
* 配置shiro拦截器链
*
* anon 不需要认证
* authc 需要认证
* user 验证通过或RememberMe登录的都可以
*
* 当应用开启了rememberMe时,用户下次访问时可以是一个user,但不会是authc,因为authc是需要重新认证的
* 顺序从上到下,优先级依次降低(也就是匹配程度由小到大)
*/
Map hashMap = new LinkedHashMap<>();
hashMap.put("/static/**", "anon");
hashMap.put("/tologin", "anon");
hashMap.put("/**", "user");
shiroFilter.setFilterChainDefinitionMap(hashMap);
return shiroFilter;
}
/**
* 在方法中 注入 securityManager,进行代理控制
*/
@Bean
public MethodInvokingFactoryBean methodInvokingFactoryBean(DefaultWebSecurityManager securityManager) {
MethodInvokingFactoryBean bean = new MethodInvokingFactoryBean();
bean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
bean.setArguments(new Object[]{securityManager});
return bean;
}
/**
* Shiro生命周期处理器:
* 用于在实现了Initializable接口的Shiro bean初始化时调用Initializable接口回调(例如:UserRealm)
* 在实现了Destroyable接口的Shiro bean销毁时调用 Destroyable接口回调(例如:DefaultSecurityManager)
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 启用shrio授权注解拦截方式,AOP式方法级权限检查
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
package com.scw.springboot.shiro.config;
import cn.stylefeng.roses.core.util.ToolUtil;
import com.scw.springboot.shiro.config.service.UserAuthService;
import com.scw.springboot.shiro.config.service.impl.UserAuthServiceServiceImpl;
import com.scw.springboot.shiro.model.User;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
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 java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ShiroDbRealm extends AuthorizingRealm {
/**
* 登录认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
throws AuthenticationException {
UserAuthService shiroFactory = UserAuthServiceServiceImpl.me();
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
User user = shiroFactory.user(token.getUsername());
ShiroUser shiroUser = shiroFactory.shiroUser(user);
return shiroFactory.info(shiroUser, user, super.getName());
}
/**
* 权限认证
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
UserAuthService shiroFactory = UserAuthServiceServiceImpl.me();
ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
List roleList = shiroUser.getRoleList();
Set permissionSet = new HashSet<>();
Set roleNameSet = new HashSet<>();
for (Integer roleId : roleList) {
List permissions = shiroFactory.findPermissionsByRoleId(roleId);
if (permissions != null) {
for (String permission : permissions) {
if (ToolUtil.isNotEmpty(permission)) {
permissionSet.add(permission);
}
}
}
String roleName = shiroFactory.findRoleNameByRoleId(roleId);
roleNameSet.add(roleName);
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(permissionSet);
info.addRoles(roleNameSet);
return info;
}
/**
* 设置认证加密方式
*/
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
HashedCredentialsMatcher md5CredentialsMatcher = new HashedCredentialsMatcher();
md5CredentialsMatcher.setHashAlgorithmName(ShiroKit.hashAlgorithmName);
md5CredentialsMatcher.setHashIterations(ShiroKit.hashIterations);
super.setCredentialsMatcher(md5CredentialsMatcher);
}
}
package com.scw.springboot.shiro.config.aop;
import java.lang.annotation.*;
/**
* 权限注解 用于检查权限 规定访问权限
*
* @example @Permission({role1,role2})
* @example @Permission
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Permission {
/**
* 角色英文名称
* 使用注解时加上这个值表示限制只有某个角色的才可以访问对应的资源
* 常用在某些资源限制只有超级管理员角色才可访问
*/
String[] value() default {};
}
AOP处理类:
package com.scw.springboot.shiro.config.aop;
import com.scw.springboot.shiro.config.service.PermissionCheckService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.naming.NoPermissionException;
import java.lang.reflect.Method;
/**
* 权限检查的aop
*/
@Aspect
@Component
@Order(200)
public class PermissionAop {
@Autowired
private PermissionCheckService check;
@Pointcut(value = "@annotation(com.scw.springboot.shiro.config.aop.Permission)")
private void cutPermission() {
}
@Around("cutPermission()")
public Object doPermission(ProceedingJoinPoint point) throws Throwable {
MethodSignature ms = (MethodSignature) point.getSignature();
Method method = ms.getMethod();
Permission permission = method.getAnnotation(Permission.class);
Object[] permissions = permission.value();
if (permissions.length == 0) {
//检查全体角色
boolean result = check.checkAll();
if (result) {
return point.proceed();
} else {
throw new NoPermissionException();
}
} else {
//检查指定角色
boolean result = check.check(permissions);
if (result) {
return point.proceed();
} else {
throw new NoPermissionException();
}
}
}
}
package com.scw.springboot.shiro.config;
import cn.stylefeng.roses.core.util.ToolUtil;
import com.scw.springboot.shiro.config.factory.ConstantFactory;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import java.util.List;
/**
* shiro工具类
*/
public class ShiroKit {
private static final String NAMES_DELIMETER = ",";
/**
* 加盐参数
*/
public final static String hashAlgorithmName = "MD5";
/**
* 循环次数
*/
public final static int hashIterations = 1024;
/**
* shiro密码加密工具类
*
* @param credentials 密码
* @param saltSource 密码盐
* @return
*/
public static String md5(String credentials, String saltSource) {
ByteSource salt = new Md5Hash(saltSource);
return new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations).toString();
}
/**
* 获取随机盐值
*
* @param length
* @return
*/
public static String getRandomSalt(int length) {
return ToolUtil.getRandomString(length);
}
/**
* 获取当前 Subject
*
* @return Subject
*/
public static Subject getSubject() {
return SecurityUtils.getSubject();
}
/**
* 获取封装的 ShiroUser
*
* @return ShiroUser
*/
public static ShiroUser getUser() {
if (isGuest()) {
return null;
} else {
return (ShiroUser) getSubject().getPrincipals().getPrimaryPrincipal();
}
}
/**
* 从shiro获取session
*/
public static Session getSession() {
return getSubject().getSession();
}
/**
* 获取shiro指定的sessionKey
*/
@SuppressWarnings("unchecked")
public static T getSessionAttr(String key) {
Session session = getSession();
return session != null ? (T) session.getAttribute(key) : null;
}
/**
* 设置shiro指定的sessionKey
*/
public static void setSessionAttr(String key, Object value) {
Session session = getSession();
session.setAttribute(key, value);
}
/**
* 移除shiro指定的sessionKey
*/
public static void removeSessionAttr(String key) {
Session session = getSession();
if (session != null)
session.removeAttribute(key);
}
/**
* 验证当前用户是否属于该角色?,使用时与lacksRole 搭配使用
*
* @param roleName 角色名
* @return 属于该角色:true,否则false
*/
public static boolean hasRole(String roleName) {
return getSubject() != null && roleName != null
&& roleName.length() > 0 && getSubject().hasRole(roleName);
}
/**
* 与hasRole标签逻辑相反,当用户不属于该角色时验证通过。
*
* @param roleName 角色名
* @return 不属于该角色:true,否则false
*/
public static boolean lacksRole(String roleName) {
return !hasRole(roleName);
}
/**
* 验证当前用户是否属于以下任意一个角色。
*
* @param roleNames 角色列表
* @return 属于:true,否则false
*/
public static boolean hasAnyRoles(String roleNames) {
boolean hasAnyRole = false;
Subject subject = getSubject();
if (subject != null && roleNames != null && roleNames.length() > 0) {
for (String role : roleNames.split(NAMES_DELIMETER)) {
if (subject.hasRole(role.trim())) {
hasAnyRole = true;
break;
}
}
}
return hasAnyRole;
}
/**
* 验证当前用户是否属于以下所有角色。
*
* @param roleNames 角色列表
* @return 属于:true,否则false
*/
public static boolean hasAllRoles(String roleNames) {
boolean hasAllRole = true;
Subject subject = getSubject();
if (subject != null && roleNames != null && roleNames.length() > 0) {
for (String role : roleNames.split(NAMES_DELIMETER)) {
if (!subject.hasRole(role.trim())) {
hasAllRole = false;
break;
}
}
}
return hasAllRole;
}
/**
* 验证当前用户是否拥有指定权限,使用时与lacksPermission 搭配使用
*
* @param permission 权限名
* @return 拥有权限:true,否则false
*/
public static boolean hasPermission(String permission) {
return getSubject() != null && permission != null
&& permission.length() > 0
&& getSubject().isPermitted(permission);
}
/**
* 与hasPermission标签逻辑相反,当前用户没有制定权限时,验证通过。
*
* @param permission 权限名
* @return 拥有权限:true,否则false
*/
public static boolean lacksPermission(String permission) {
return !hasPermission(permission);
}
/**
* 已认证通过的用户。不包含已记住的用户,这是与user标签的区别所在。与notAuthenticated搭配使用
*
* @return 通过身份验证:true,否则false
*/
public static boolean isAuthenticated() {
return getSubject() != null && getSubject().isAuthenticated();
}
/**
* 未认证通过用户,与authenticated标签相对应。与guest标签的区别是,该标签包含已记住用户。。
*
* @return 没有通过身份验证:true,否则false
*/
public static boolean notAuthenticated() {
return !isAuthenticated();
}
/**
* 认证通过或已记住的用户。与guset搭配使用。
*
* @return 用户:true,否则 false
*/
public static boolean isUser() {
return getSubject() != null && getSubject().getPrincipal() != null;
}
/**
* 验证当前用户是否为“访客”,即未认证(包含未记住)的用户。用user搭配使用
*
* @return 访客:true,否则false
*/
public static boolean isGuest() {
return !isUser();
}
/**
* 输出当前用户信息,通常为登录帐号信息。
*
* @return 当前用户信息
*/
public static String principal() {
if (getSubject() != null) {
Object principal = getSubject().getPrincipal();
return principal.toString();
}
return "";
}
/**
* 获取当前用户的部门数据范围的集合
*/
public static List getDeptDataScope() {
Integer deptId = getUser().getDeptId();
List subDeptIds = ConstantFactory.me().getSubDeptId(deptId);
subDeptIds.add(deptId);
return subDeptIds;
}
/**
* 判断当前用户是否是超级管理员
*/
public static boolean isAdmin() {
List roleList = ShiroKit.getUser().getRoleList();
for (Integer integer : roleList) {
String singleRoleTip = ConstantFactory.me().getSingleRoleTip(integer);
if (singleRoleTip.equals("admin")) {
return true;
}
}
return false;
}
}
我将这部分的完整源码放在下面的地址中,方便有需要的人进行使用。
github:https://github.com/qq496616246/SpringBootAndShiro
百度云:https://pan.baidu.com/s/1uQ8Xu143es9bTIQ5HjTjpA
密码:v0zj
上面提到的步骤中,是对主要的步骤的一些讲解。因为,有很多同学都跟反应说,想知道一个能够真正项目级别的shiro,所以,特别写了这一篇文章。对于权限来说,这是个非常重要的模块,而且实现的方式也是有千变万化的,主要还是要根据我们的实际需求来进行,我们要学会的是如何去思考。希望各位阅读者,可以好好的阅读我的源码的内容,好好的理解到底为什么要这样去实现(当然,不是强制一定这样写,只是个参考啦!),好好加油。。