注意:RedisCacheManager、RedisManager、RedisSessionDAO都是shiro包中的类
package cc.mrbird.febs.common.authentication;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import cc.mrbird.febs.common.properties.FebsProperties;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.SessionListener;
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.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Base64Utils;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
/**
* Shiro 配置类
*
* @author MrBird
*/
@Configuration
@RequiredArgsConstructor
public class ShiroConfig {
private final FebsProperties febsProperties;
@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;
@Value("${spring.redis.database:0}")
private int database;
/**
* shiro 中配置 redis 缓存
*
* @return RedisManager
*/
private RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host + ":" + port);
if (StringUtils.isNotBlank(password)) {
redisManager.setPassword(password);
}
redisManager.setTimeout(timeout);
redisManager.setDatabase(database);
return redisManager;
}
private RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 设置 securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 登录的 url
shiroFilterFactoryBean.setLoginUrl(febsProperties.getShiro().getLoginUrl());
// 登录成功后跳转的 url
shiroFilterFactoryBean.setSuccessUrl(febsProperties.getShiro().getSuccessUrl());
// 未授权 url
shiroFilterFactoryBean.setUnauthorizedUrl(febsProperties.getShiro().getUnauthorizedUrl());
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 设置免认证 url
String[] anonUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(febsProperties.getShiro().getAnonUrl(), ",");
for (String url : anonUrls) {
filterChainDefinitionMap.put(url, "anon");
}
// 配置退出过滤器,其中具体的退出代码 Shiro已经替我们实现了
filterChainDefinitionMap.put(febsProperties.getShiro().getLogoutUrl(), "logout");
// 除上以外所有 url都必须认证通过才可以访问,未通过认证自动访问 LoginUrl
filterChainDefinitionMap.put("/**", "user");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager(ShiroRealm shiroRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 配置 SecurityManager,并注入 shiroRealm
securityManager.setRealm(shiroRealm);
// 配置 shiro session管理器
securityManager.setSessionManager(sessionManager());
// 配置 缓存管理类 cacheManager
securityManager.setCacheManager(cacheManager());
// 配置 rememberMeCookie
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
/**
* rememberMe cookie 效果是重开浏览器后无需重新登录
*
* @return SimpleCookie
*/
private SimpleCookie rememberMeCookie() {
// 设置 cookie 名称,对应 login.html 页面的 <input type="checkbox" name="rememberMe"/>
SimpleCookie cookie = new SimpleCookie("rememberMe");
// 设置 cookie 的过期时间,单位为秒,这里为一天
cookie.setMaxAge(febsProperties.getShiro().getCookieTimeout());
return cookie;
}
/**
* cookie管理对象
*
* @return CookieRememberMeManager
*/
private CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
// rememberMe cookie 加密的密钥
String encryptKey = "febs_shiro_key";
byte[] encryptKeyBytes = encryptKey.getBytes(StandardCharsets.UTF_8);
String rememberKey = Base64Utils.encodeToString(Arrays.copyOf(encryptKeyBytes, 16));
cookieRememberMeManager.setCipherKey(Base64.decode(rememberKey));
return cookieRememberMeManager;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 用于开启 Thymeleaf 中的 shiro 标签的使用
*
* @return ShiroDialect shiro 方言对象
*/
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
/**
* session 管理对象
*
* @return DefaultWebSessionManager
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
Collection<SessionListener> listeners = new ArrayList<>();
listeners.add(new ShiroSessionListener());
// 设置 session超时时间
sessionManager.setGlobalSessionTimeout(febsProperties.getShiro().getSessionTimeout() * 1000L);
sessionManager.setSessionListeners(listeners);
sessionManager.setSessionDAO(redisSessionDAO());
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
}
直接在这类上用了@Component,就可以直接在ShiroConfig配置中的public SecurityManager securityManager(ShiroRealm shiroRealm)中注入这个Realm,不用在ShiroConfig配置中去@Bean这个ShiroRealm了
package cc.mrbird.febs.common.authentication;
import cc.mrbird.febs.system.entity.Menu;
import cc.mrbird.febs.system.entity.Role;
import cc.mrbird.febs.system.entity.User;
import cc.mrbird.febs.system.service.IMenuService;
import cc.mrbird.febs.system.service.IRoleService;
import cc.mrbird.febs.system.service.IUserDataPermissionService;
import cc.mrbird.febs.system.service.IUserService;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 自定义实现 ShiroRealm,包含认证和授权两大模块
*
* @author MrBird
*/
@Component
public class ShiroRealm extends AuthorizingRealm {
private IUserService userService;
private IRoleService roleService;
private IMenuService menuService;
private IUserDataPermissionService userDataPermissionService;
@Autowired
public void setMenuService(IMenuService menuService) {
this.menuService = menuService;
}
@Autowired
public void setUserService(IUserService userService) {
this.userService = userService;
}
@Autowired
public void setRoleService(IRoleService roleService) {
this.roleService = roleService;
}
@Autowired
public void setUserDataPermissionService(IUserDataPermissionService userDataPermissionService) {
this.userDataPermissionService = userDataPermissionService;
}
/**
* 授权模块,获取用户角色和权限
*
* @param principal principal
* @return AuthorizationInfo 权限信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
User user = (User) SecurityUtils.getSubject().getPrincipal();
String userName = user.getUsername();
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// 获取用户角色集
List<Role> roleList = this.roleService.findUserRole(userName);
Set<String> roleSet = roleList.stream().map(Role::getRoleName).collect(Collectors.toSet());
simpleAuthorizationInfo.setRoles(roleSet);
// 获取用户权限集
List<Menu> permissionList = this.menuService.findUserPermissions(userName);
Set<String> permissionSet = permissionList.stream().map(Menu::getPerms).collect(Collectors.toSet());
simpleAuthorizationInfo.setStringPermissions(permissionSet);
return simpleAuthorizationInfo;
}
/**
* 用户认证
*
* @param token AuthenticationToken 身份认证 token
* @return AuthenticationInfo 身份认证信息
* @throws AuthenticationException 认证相关异常
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 获取用户输入的用户名和密码
String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
// 通过用户名到数据库查询用户信息
User user = this.userService.findByName(username);
if (user == null || !StringUtils.equals(password, user.getPassword())) {
throw new IncorrectCredentialsException("用户名或密码错误!");
}
if (User.STATUS_LOCK.equals(user.getStatus())) {
throw new LockedAccountException("账号已被锁定,请联系管理员!");
}
String deptIds = this.userDataPermissionService.findByUserId(String.valueOf(user.getUserId()));
user.setDeptIds(deptIds);
return new SimpleAuthenticationInfo(user, password, getName());
}
/**
* 清除当前用户权限缓存
* 使用方法:在需要清除用户权限的地方注入 ShiroRealm,
* 然后调用其 clearCache方法。
*/
public void clearCache() {
PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
super.clearCache(principals);
}
}
package cc.mrbird.febs.common.authentication;
import cc.mrbird.febs.common.annotation.Helper;
import org.apache.shiro.authz.AuthorizationInfo;
/**
* @author MrBird
*/
@Helper
public class ShiroHelper extends ShiroRealm {
/**
* 获取当前用户的角色和权限集合
*
* @return AuthorizationInfo
*/
public AuthorizationInfo getCurrentUserAuthorizationInfo() {
return super.doGetAuthorizationInfo(null);
}
}
注意与HttpSessionListener的不同:
# public void sessionCreated(HttpSessionEvent se);
# public void sessionDestroyed(HttpSessionEvent se);
package cc.mrbird.febs.common.authentication;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author MrBird
*/
public class ShiroSessionListener implements SessionListener{
private final AtomicInteger sessionCount = new AtomicInteger(0);
@Override
public void onStart(Session session) {
sessionCount.incrementAndGet();
}
@Override
public void onStop(Session session) {
sessionCount.decrementAndGet();
}
@Override
public void onExpiration(Session session) {
sessionCount.decrementAndGet();
}
}