shiro-spring-boot-web-starter 1.6.0版本
[SpringBoot微服务框架]
提前说好,说我缺文件的,一般那是不中要的
ShiroFreshService
/**
* 初始化权限 -> 拿全部权限
*
* @param :
* @return: java.util.Map<java.lang.String, java.lang.String>
*/
Map<String, String> loadFilterChainDefinitionMap();
/**
* 重新构建权限过滤器
* 一般在修改了用户角色、用户等信息时,需要再次调用该方法
*/
void reCreateFilterChains(ShiroFilterFactoryBean shiroFilterFactoryBean);
ShiroFreshServiceImpl
@Slf4j
@Service
public class ShiroFreshServiceImpl implements ShiroFreshService {
@Autowired
private RoleMenuService roleMenuService;
@Autowired
private ConfigProperties configProperties;
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static Lock writeLock = readWriteLock.writeLock();
@Override
public Map<String, String> loadFilterChainDefinitionMap() {
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/validcaptcha", "anon");//验证验证码
filterChainDefinitionMap.put("/validate", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/logout", "anon");
filterChainDefinitionMap.put("/css/** ", "anon");
filterChainDefinitionMap.put("/image/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/error/**", "anon");
filterChainDefinitionMap.put("/plugins/**", "anon");
filterChainDefinitionMap.put("/favicon.ico", "anon");
filterChainDefinitionMap.put("/401", "anon");
filterChainDefinitionMap.put("/403", "anon");
filterChainDefinitionMap.put("/404", "anon");
filterChainDefinitionMap.put("/500", "anon");
List<UrlRoleVo> urlRoleVoList = roleMenuService.findAllUrlRolesList();
String roleCode = configProperties.getAdminRoleCode();
if (CollectionUtils.isNotEmpty(urlRoleVoList)) {
urlRoleVoList.forEach(ur -> filterChainDefinitionMap.put(ur.url(), "roles[" + ur.role().toUpperCase() + "," + roleCode + "]"));
}
//对所有用户认证
filterChainDefinitionMap.put("/**", "authc");
return filterChainDefinitionMap;
}
@Override
public void reCreateFilterChains(ShiroFilterFactoryBean shiroFilterFactoryBean) {
writeLock.lock();
try {
AbstractShiroFilter shiroFilter = null;
try {
shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean.getObject();
} catch (Exception e) {
log.error("ShiroFreshServiceImpl刷新权限异常", e);
throw new HdapException(GET_SHIRO_FILTER);
}
PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter.getFilterChainResolver();
DefaultFilterChainManager filterChainManager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();
//加载数据
Map<String, String> filterChainDefinitionMap = loadFilterChainDefinitionMap();
// 清空老的权限控制
filterChainManager.getFilterChains().clear();
shiroFilterFactoryBean.getFilterChainDefinitionMap().clear();
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
// 重新构建生成
for (Map.Entry<String, String> filterChainDefinition : filterChainDefinitionMap.entrySet()) {
filterChainManager.createChain(filterChainDefinition.getKey(), filterChainDefinition.getValue());
}
} finally {
writeLock.unlock();
}
}
}
Config
/**
* 参考【仅作参考】
* 1. https://blog.youkuaiyun.com/New_Yao/article/details/100769385
* 2. https://www.cnblogs.com/zhengqing/p/11603824.html
* *.https://shiro.apache.org/spring-boot.html
*/
@Slf4j
@Configuration
public class SysUserSecurityConfig {
@Value("${server.servlet.session.timeout}")
@DurationUnit(ChronoUnit.SECONDS)
private Duration timeout = Duration.ofMinutes(30L);
@Autowired
private ShiroFreshService shiroFreshService;
/**
* 设置用于匹配密码的CredentialsMatcher
* SHA-256
*
* @return
*/
@Bean
public HashedCredentialsMatcher credentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
// 散列算法,这里使用更安全的sha256算法
credentialsMatcher.setHashAlgorithmName(Sha256Hash.ALGORITHM_NAME);
// 散列迭代次数
credentialsMatcher.setHashIterations(1024);
return credentialsMatcher;
}
/**
* 配置自定义Realm
*
* @return
*/
@Bean
public SysUserRealm sysUserRealm() {
SysUserRealm sysUserRealm = new SysUserRealm();
// 配置使用哈希密码匹配
sysUserRealm.setCredentialsMatcher(credentialsMatcher());
// 启用身份验证缓存,即缓存AuthenticationInfo信息,默认false
sysUserRealm.setAuthenticationCachingEnabled(true);
// 启用授权缓存,即缓存AuthorizationInfo信息,默认false,一旦配置了缓存管理器,授权缓存默认开启
sysUserRealm.setAuthorizationCachingEnabled(true);
return sysUserRealm;
}
/**
* 内存中的缓存管理器
*
* @return
*/
@Bean
protected CacheManager cacheManager() {
return new MemoryConstrainedCacheManager();
}
/**
* 配置会话管理器,设定会话超时及保存
*
* @return
*/
@Bean("sessionManager")
public SessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
//全局会话超时时间(单位毫秒),默认30分钟
sessionManager.setGlobalSessionTimeout(timeout.getSeconds() * 1000);
//是否开启删除无效的session对象 默认为true
sessionManager.setDeleteInvalidSessions(true);
//是否开启定时调度器进行检测过期session 默认为true
sessionManager.setSessionValidationSchedulerEnabled(true);
//设置session失效的扫描时间, 清理用户直接关闭浏览器造成的孤立会话 默认为 1个小时
//设置该属性 就不需要设置 ExecutorServiceSessionValidationScheduler 底层也是默认自动调用ExecutorServiceSessionValidationScheduler
//暂时设置为 5秒 用来测试
// sessionManager.setSessionValidationInterval(5000);
//禁用URL会话重写
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
/**
* 安全控制中心
*
* @return
*/
@Bean("securityManager")
public SessionsSecurityManager securityManager() {
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
//配置自定义Realm
defaultSecurityManager.setRealm(sysUserRealm());
//内存中的缓存管理器
defaultSecurityManager.setCacheManager(cacheManager());
//配置会话管理器,设定会话超时及保存
defaultSecurityManager.setSessionManager(sessionManager());
return defaultSecurityManager;
}
/**
* 配置Filter过滤器
*
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置SecuritManager
filterFactoryBean.setSecurityManager(securityManager());
//用户未登录时跳转的请求路径
filterFactoryBean.setLoginUrl(SysRoute.REDIRECT_LOGIN);
//用户未登录成功跳转的请求路径
filterFactoryBean.setSuccessUrl("/");
//用户没有访问权限时跳转的请求路径
filterFactoryBean.setUnauthorizedUrl("/401");
Map<String, Filter> filters = new LinkedHashMap();
//配置拦截器,实现无权限返回(过滤器制定)
filters.put("authc", new MyFormAuthenticationFilter());
filters.put("roles", new MyRolesAuthorizationFilter());
filterFactoryBean.setFilters(filters);
Map<String, String> filterChainDefinitionMap = shiroFreshService.loadFilterChainDefinitionMap();
filterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return filterFactoryBean;
}
}
realm
@Slf4j
public class SysUserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private EmpService empService;
@Autowired
private ConfigProperties configProperties;
/**
* 认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {
String username = (String) authenticationToken.getPrincipal();
log.info("#SHIRO#认证" + username);
SysUser sysUser = userService.getSysUserOneByUsername(username);
if (ObjectUtils.isNotEmpty(sysUser)) {
//status 用户状态,0,离职,1:正常,2:禁用
switch (sysUser.status()) {
case 0:
throw new UserStatusException(new ErrCode(10001, "用户离职状态,禁止登录"));
case 2:
throw new UserStatusException(new ErrCode(10002, "用户锁定状态,禁止登录"));
default:
break;
}
return new SimpleAuthenticationInfo(username, sysUser.password(), ByteSource.Util.bytes(username), getName());
}
throw new UnknownAccountException("用户名或密码不存在");
}
/**
* 授权信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("#SHIRO#授权");
if (principalCollection == null) {
throw new AuthorizationException("用户凭证不能为空");
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
String username = (String) getAvailablePrincipal(principalCollection);
//登录用户角色
Set<String> roles = new HashSet<>();
List<String> roleCodeList = empService.getRoleCodeListByUsername(username);
if (CollectionUtils.isNotEmpty(roleCodeList)) {
roles.addAll(roleCodeList.stream().map(String::toUpperCase).collect(Collectors.toSet()));
}
SysUser sysUser = userService.getSysUserOneByUsername(username);
if (ObjectUtils.isNotEmpty(sysUser) && configProperties.getAdminId().equals(sysUser.id())) {
roles.add(configProperties.getAdminRoleCode());
}
info.setRoles(roles);
return info;
}
/**
* 自定义方法:清除所有 授权缓存
*/
public void clearAllCachedAuthorizationInfo() {
getAuthorizationCache().clear();
}
}
MyFormAuthenticationFilter
/**
* Name: 需要登录认证
* Description:
* User: bambo
* Date: 2020-07-16
* Time: 16:12
*/
@Slf4j
public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
/**
* @param request
* @param response
* @return true-继续往下执行,false-该filter过滤器已经处理,不继续执行其他过滤器
* @throws IOException
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
log.info("#SHIRO#认证处理(1),onAccessDenied:MyFormAuthenticationFilter");
Subject subject = this.getSubject(request, response);
if (subject.getPrincipal() == null) {
log.info("#SHIRO#认证处理(2-1),凭证丢失【重新登录】:MyFormAuthenticationFilter");
if (AjaxBoolUtil.isAjax((HttpServletRequest) request)) {
response.setCharacterEncoding(AjaxBoolUtil.CHARACTER_ENCODING);
response.setContentType(AjaxBoolUtil.CONTENT_TYPE);
response.getWriter().write(GsonUtil.toString(Result.right("请重新登录!")));
} else {
((HttpServletResponse) response).sendRedirect(SysRoute.LOGIN_LOGIN);
}
} else {
log.info("#SHIRO#认证处理(2-2),未经认证【401】:MyFormAuthenticationFilter");
if (AjaxBoolUtil.isAjax((HttpServletRequest) request)) {
response.setCharacterEncoding(AjaxBoolUtil.CHARACTER_ENCODING);
response.setContentType(AjaxBoolUtil.CONTENT_TYPE);
response.getWriter().write(GsonUtil.toString(Result.right("未经认证!")));
} else {
WebUtils.toHttp(response).sendError(401);
}
}
return false;
}
}
MyRolesAuthorizationFilter
@Slf4j
public class MyRolesAuthorizationFilter extends AuthorizationFilter {
@Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
throws IOException {
log.info("#SHIRO#授权效验(1):MyRolesAuthorizationFilter");
final Subject subject = getSubject(request, response);
if (subject.getPrincipal() == null) {
return false;
}
final String[] rolesArray = (String[]) mappedValue;
if (rolesArray == null || rolesArray.length == 0) {
log.info("#SHIRO#(2-1),无指定角色时,无需检查,允许访问:MyRolesAuthorizationFilter");
return true;
}
for (String roleName : rolesArray) {
if (subject.hasRole(roleName)) {
log.info("#SHIRO#(2-2),有匹配角色,允许访问:MyRolesAuthorizationFilter");
return true;
}
}
log.info("#SHIRO#未经授权(3),无授权:MyRolesAuthorizationFilter");
return false;
}
@Override
public boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
log.info("#SHIRO#授权处理(1),onAccessDenied:MyRolesAuthorizationFilter");
Subject subject = this.getSubject(request, response);
if (subject.getPrincipal() == null) {
log.info("#SHIRO#授权处理(2-1),凭证丢失【重新登录】:MyRolesAuthorizationFilter");
if (AjaxBoolUtil.isAjax((HttpServletRequest) request)) {
response.setCharacterEncoding(AjaxBoolUtil.CHARACTER_ENCODING);
response.setContentType(AjaxBoolUtil.CONTENT_TYPE);
response.getWriter().write(GsonUtil.toString(Result.right("请重新登录!")));
} else {
((HttpServletResponse) response).sendRedirect(SysRoute.LOGIN_LOGIN);
}
} else {
log.info("#SHIRO#授权处理(2-2),未经授权【403】:MyRolesAuthorizationFilter");
if (AjaxBoolUtil.isAjax((HttpServletRequest) request)) {
response.setCharacterEncoding(AjaxBoolUtil.CHARACTER_ENCODING);
response.setContentType(AjaxBoolUtil.CONTENT_TYPE);
response.getWriter().write(GsonUtil.toString(Result.right("未经授权!")));
} else {
WebUtils.toHttp(response).sendError(403);
}
}
return false;
}
}
Sha256PasswordHelper
public class Sha256PasswordHelper {
public static String encryption(String username, String pwd) {
return new Sha256Hash(pwd, username, 1024).toString();
}
}
ShiroBeanLifecycleConfig
@Configuration
public class ShiroBeanLifecycleConfig {
/**
* 安全框架shiro的bean生命周期
* LifecycleBeanPostProcessor将Initializable和Destroyable的实现类统一在其内部自动分别调用了Initializable.init()和Destroyable.destroy()方法,
* 从而达到管理shiro bean生命周期的目的。
*
* @return
*/
@Bean("lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* APC 自动代理创建器
* 扫描上下文,寻找所有的Advistor(通知器),将这些Advisor应用到所有符合切入点的Bean中。所以必须在lifecycleBeanPostProcessor创建之后创建。
*
* @return
* @DependsOn({"lifecycleBeanPostProcessor"}) 保证创建DefaultAdvisorAutoProxyCreator 之前先创建LifecycleBeanPostProcessor 。
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
//使用cglib方式为Action对象创建代理对象[pom.xml如果引入了aop-starter依赖包装,就需要做出更改]
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
}
ToolDateUtil
@Slf4j
public class ToolDateUtil {
//LocalDate -> Date
public static Date asDate(LocalDate localDate) {
return Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
}
//LocalDateTime -> Date
public static Date asDate(LocalDateTime localDateTime) {
return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
}
//String -> Date
public static Date asYMDDate(String date) {
DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd");
try {
return fmt.parse(date);
} catch (ParseException e) {
log.error(e.getMessage(), e);
return null;
}
}
//Date -> LocalDate
public static LocalDate asLocalDate(Date date) {
return Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault()).toLocalDate();
}
//Date -> LocalDateTime
public static LocalDateTime asLocalDateTime(Date date) {
return Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault()).toLocalDateTime();
}
//Date -> String
public static String YmdHmToString(Date date) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm");
return formatter.format(date);
}
//Date -> String
public static String YmdToString(Date date) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
return formatter.format(date);
}
//String -> Date
public static Date asMinDate(String ymd) {
DateTimeFormatter dtfYMD = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate date = LocalDate.parse(ymd, dtfYMD);
return asDate(LocalDateTime.of(date, LocalTime.MIN));
}
//String -> Date
public static Date asMaxDate(String ymd) {
DateTimeFormatter dtfYMD = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate date = LocalDate.parse(ymd, dtfYMD);
return asDate(LocalDateTime.of(date, LocalTime.MAX));
}
}
调用
@Autowired
private ShiroFilterFactoryBean shiroFilterFactoryBean;
shiroFreshService..reCreateFilterChains(shiroFilterFactoryBean)