这几天一直在开发前后端权限管理,就想到刚好用用shiro吧,就在网上找了找,然后根据业务需求做了一下合并,如下。
1、ShiroRealm.java 主要用来验证用户和角色权限。
package shiro;
import utils.ApplicationContextRegister;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @Author:Jerry
* @Created: 2018/7/23
* @Modifier:Jerry
* @Updated:2018/7/23
* @Description: 自定义Realm,主要是用来验证用户权限和验证用户登录信息
* @Version:BUILD1001
*/
public class MyShiroRealm extends AuthorizingRealm {
/**
* 验证用户权限的位置
* 进入方法的场景:
* 1、@RequiresRoles("admin") ,@RequiresPermissions("admin") 在方法上加注解的时候;
* 2、subject.hasRole(“admin”) 或 subject.isPermitted(“admin”) 自己去调用这个方法
* 3、[@shiro.hasPermission name = "admin"][/@shiro.hasPermission]: 在页面加这个标签的时候,这个项目是前后端分离,所以这个方法无效
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
ManageInfoVo manageInfoVo = (ManageInfoVo)principals.getPrimaryPrincipal();
//给予admin超级权限
if(manageInfoVo.getUserName().equals("admin")){
SysMenuDoMapperExt sysMenuDoMapperExt = ApplicationContextRegister.getBean(SysMenuDoMapperExt.class);
List<SysMenuVo> listSysMenuVo = sysMenuDoMapperExt.getList(new SysMenuDto());
Set<String> permsSet = new HashSet<>();
for(SysMenuVo sysMenuVo : listSysMenuVo){
if(!StringUtil.isEmpty(sysMenuVo.getPerms())){
permsSet.add(sysMenuVo.getPerms());
}
}
authorizationInfo.addStringPermissions(permsSet);
}else{
SysMenuService sysMenuService = ApplicationContextRegister.getBean(SysMenuService.class);
List<SysMenuDo > listSysMenuDo = sysMenuService.getListByUserName(manageInfoVo.getUserName());
Set<String> permsSet = new HashSet<>();
for(SysMenuDo sysMenuDo : listSysMenuDo){
if(!StringUtil.isEmpty(sysMenuDo.getPerms())){
permsSet.add(sysMenuDo.getPerms());
}
}
authorizationInfo.addStringPermissions(permsSet);
}
return authorizationInfo;
}
/**
* 验证用户名和密码是否正确
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
//获取用户的输入的账号.
String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
try{
ManageInfoDoMapperExt manageInfoDoMapperExt = ApplicationContextRegister.getBean(ManageInfoDoMapperExt.class);
ManageInfoDto manageInfoDto = new ManageInfoDto();
manageInfoDto.setUserName(username);
manageInfoDto.setPassword(password);
ManageInfoVo manageInfoVo = manageInfoDoMapperExt.login(manageInfoDto);
if(null == manageInfoVo || null == manageInfoVo.getId()){
throw new ValidateException("用户名或密码错误");
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
manageInfoVo, //用户信息
password, //密码
ByteSource.Util.bytes(manageInfoVo.getUserName()),//salt 加密,未用到,这里就不做介绍了。
getName() //realm name
);
return authenticationInfo;
}catch (Exception e){
throw new ValidateException("用户名或密码错误");
}
}
}
说下上面用到的工具类:
ApplicationCOntextRegister.java
package utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* @Author:jerry
* @Created: 2018/7/30
* @Modifier:jerry
* @Updated:2018/7/30
* @Description: 定义便捷类用于shiro中获取service
* @Version:BUILD1001
*/
@Component
public class ApplicationContextRegister implements ApplicationContextAware {
private static Logger logger = LoggerFactory.getLogger(ApplicationContextRegister.class);
private static ApplicationContext APPLICATION_CONTEXT;
/**
* 设置spring上下文
*
* @param applicationContext spring上下文
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
logger.debug("ApplicationContext registed-->{}", applicationContext);
APPLICATION_CONTEXT = applicationContext;
}
/**
* 获取容器
*
* @return
*/
public static ApplicationContext getApplicationContext() {
return APPLICATION_CONTEXT;
}
/**
* 获取容器对象
*
* @param type
* @param <T>
* @return
*/
public static <T> T getBean(Class<T> type) {
return APPLICATION_CONTEXT.getBean(type);
}
}
可以参考链接:https://www.cnblogs.com/mrx520/p/7802831.html
2、ShiroConfig.java 主要是关于shiro的配置,如:cacheManager,sessionManager,sessionListerner等。核心配置都在这里。
package shiro;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.SessionDAO;
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.DefaultWebSecurityManager;
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.Configurable;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @Author:jieli.xu
* @Created: 2018/7/23
* @Modifier:jieli.xu
* @Updated:2018/7/23
* @Description: shiro 配置
* @Version:BUILD1001
*/
@Configuration
public class ShiroConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.pool.max-idle}")
private int minIdle;
@Value("${spring.redis.pool.max-idle}")
private int maxIdle;
@Value("${spring.redis.pool.max-wait}")
private long maxWaitMillis;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.database}")
private int database;
@Bean
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
//注意过滤器配置顺序 不能颠倒
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了,登出后跳转配置的loginUrl
filterChainDefinitionMap.put("/loginout", "logout");
//配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据
shiroFilterFactoryBean.setLoginUrl("/user/login");
// 登录成功后要跳转的链接
// shiroFilterFactoryBean.setSuccessUrl("/user/login");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/user/unauth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置realm.
securityManager.setRealm(myShiroRealm());
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());
securityManager.setSessionManager(sessionManager());
return securityManager;
}
@Bean
MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
return myShiroRealm;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 配置shiro redisManager
*
* @return
*/
@Bean
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setExpire(1800);// 配置缓存过期时间
redisManager.setTimeout(timeout);
redisManager.setPassword(password);
redisManager.setDatabase(database);
redisManager.setTimeout(timeout);
return redisManager;
}
/**
* cacheManager 缓存 redis实现
* 使用的是shiro-redis开源插件
*
* @return
*/
public RedisCacheManagerOverride cacheManager() {
RedisCacheManager redisCacheManager1 = new RedisCacheManager();
redisCacheManager1.setRedisManager(redisManager());
RedisCacheManagerOverride redisCacheManager = new RedisCacheManagerOverride();
return redisCacheManager;
}
/**
* RedisSessionDAO shiro sessionDao层的实现 通过redis
* 使用的是shiro-redis开源插件
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
@Bean
public SessionDAO sessionDAO() {
return redisSessionDAO();
}
/**
* shiro session的管理
*//**
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(1800 * 1000);
sessionManager.setSessionDAO(sessionDAO());
Collection<SessionListener> listeners = new ArrayList<SessionListener>();
listeners.add(new BDSessionListener());
sessionManager.setSessionListeners(listeners);
return sessionManager;
}
*/
//自定义sessionManager
@Bean
public SessionManager sessionManager() {
MySessionManager mySessionManager = new MySessionManager();
mySessionManager.setGlobalSessionTimeout(1800 * 1000);//过期时间:
Collection<SessionListener> listeners = new ArrayList<SessionListener>();
listeners.add(new BDSessionListener());
mySessionManager.setSessionListeners(listeners);
mySessionManager.setSessionDAO(redisSessionDAO());
return mySessionManager;
}
}
BDSession.java 监听Session
package shiro;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;
/**
* 用来监听Session,过期或停止后可以进行一些操作。
* 这里只做参考,具体操作根据业务需求
*/
public class BDSessionListener 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();
}
public int getSessionCount() {
return sessionCount.get();
}
}
MySessionManage.java 因为是前后端分离,所以需要他们穿token过来。头部请求把token带上
package shiro;
import com.alibaba.druid.util.StringUtils;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
/**
* @Author:jieli.xu
* @Created: 2018/7/23
* @Modifier:jieli.xu
* @Updated:2018/7/23
* @Description: 重新获取session的方法
* @Version:BUILD1001
*/
public class MySessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "token";
public MySessionManager() {
super();
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
if(StringUtils.isEmpty(sessionId)){
sessionId = super.getSessionId(request, response)+"";
}
return sessionId;
}
}
RedisCache redis缓存,自定义缓存存储数据信息,也是因为redis-cache会把我的信息覆盖掉。
里面redisClient 是redis管理工具,具体操作你们可以根据自己的redisClient 进行修改。我这里不做详细描述
package com.bxm.depthtaskcms.shiro;
import com.bxm.depthtaskcms.utils.ApplicationContextRegister;
import com.mchange.v2.ser.SerializableUtils;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import java.io.NotSerializableException;
import java.util.*;
/**
* @Author:jerry
* @Created:
* @Modifier:jerry
* @Updated:
* @Description: 重写redis缓存,方便管理
* @Version:BUILD1001
*/
public class RedisCache<K,V> implements Cache<K,V> {
@Autowired
private RedisClient redisClient;
private Logger logger = LoggerFactory.getLogger(RedisCache.class);
private String keyPrefix = "shiro_redis_cache:";
public String getKeyPrefix() {
return keyPrefix;
}
public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
/**
* 获得组装后的byte[] key
* @param key
* @return
*/
private byte[] getByteKey(K key) throws NotSerializableException {
if(key instanceof String){
String preKey = this.keyPrefix + key;
return preKey.getBytes();
}else{
if(key instanceof SimplePrincipalCollection){
SimplePrincipalCollection spc = (SimplePrincipalCollection)key;
Object object = spc.getPrimaryPrincipal();
if(object instanceof ManageInfoVo){
ManageInfoVo manageInfoVo = (ManageInfoVo)object;
String preKey = this.keyPrefix+(manageInfoVo.getId());
return preKey.getBytes();
}
}
if(key instanceof ManageInfoVo){
String preKey = this.keyPrefix+((ManageInfoVo) key).getId();
return preKey.getBytes();
}
return SerializableUtils.toByteArray(key);
}
}
@Override
public V get(K key) throws CacheException {
try{
redisClient = ApplicationContextRegister.getBean(RedisClient.class);
byte[] groupKey = getByteKey(key);
byte[] value = redisClient.get(Constant.REDIS_DEFAULT_DB,groupKey);
if(value == null){
return null;
}
return (V)SerializableUtils.fromByteArray(value);
}catch (Exception e){
logger.error(e.getMessage());
}
return null;
}
@Override
public V put(K key, V value) throws CacheException {
try{
redisClient = ApplicationContextRegister.getBean(RedisClient.class);
redisClient.setex(Constant.REDIS_DEFAULT_DB,getByteKey(key),Constant.SHIRO_REDIS_CACHE_TIME ,SerializableUtils.toByteArray(value));
byte[] bytes = redisClient.get(Constant.REDIS_DEFAULT_DB,getByteKey(key));
return (V)SerializableUtils.fromByteArray(bytes);
}catch (Exception e){
logger.error("反序列化失败:"+e.getMessage());
}
return null;
}
@Override
public V remove(K key) throws CacheException {
try{
redisClient = ApplicationContextRegister.getBean(RedisClient.class);
byte[] bytes = redisClient.get(Constant.REDIS_DEFAULT_DB,getByteKey(key));
Set<K> hashSet = keys();
for(K keyEntity : hashSet){
redisClient.del(Constant.REDIS_DEFAULT_DB,((byte[])keyEntity));
}
// redisClient.del(Constant.REDIS_DEFAULT_DB,getByteKey(key));
return null;
}catch (Exception e){
logger.error("删除异常"+e.getMessage());
}
return null;
}
@Override
public void clear() throws CacheException {
}
@Override
public int size() {
return 0;
}
@Override
public Set<K> keys() {
try{
redisClient = ApplicationContextRegister.getBean(RedisClient.class);
K key = (K)"*";
Set<K> setKey = new HashSet<>();
setKey = (Set<K>)redisClient.hkeys(Constant.REDIS_DEFAULT_DB,getByteKey(key));
return setKey;
}catch (Exception e){
logger.error("获取key失败");
}
return null;
}
@Override
public Collection<V> values() {
return null;
}
}
RedisCacheManager.java 管理redisCache
package shiro;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* @Author:
* @Modifier:jerry
* @Description:
* @Version:BUILD1001
*/
public class RedisCacheManagerOverride implements CacheManager {
private final ConcurrentMap<String, RedisCache> caches = new ConcurrentHashMap();
//
@Override
public <K, V> Cache<K, V> getCache(String name) throws CacheException {
RedisCache cache = (RedisCache)this.caches.get(name);
if (cache == null) {
cache = new RedisCache<K,V>();
this.caches.put(name, cache);
}
return (Cache)cache;
}
}
在定义一个ShiroUtil,用来便捷获取登录人员的信息
package shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.mgt.RealmSecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import sun.security.util.Cache;
import java.security.Principal;
import java.util.Collection;
import java.util.List;
public class ShiroUtils {
public static Subject getSubjct() {
return SecurityUtils.getSubject();
}
/**
* 获取当前用户信息
* @return
*/
public static ManageInfoVo getUser() {
try{
Object object = getSubjct().getPrincipal();
if(null == object)
throw new UnauthenticatedException();
return (ManageInfoVo)object;
}catch (Exception e){
throw new UnauthenticatedException("");
}
}
/**
* 获取用户id
* @return
*/
public static Long getUserId() {
return getUser().getId();
}
/**
* 退出登录
*/
public static void logout() {
getSubjct().logout();
}
/**
* 刷新用户角色权限
*/
public static void cleanCash(){
RealmSecurityManager rsm = (RealmSecurityManager) SecurityUtils.getSecurityManager();
//MyShiroRealm为在项目中定义的realm类
MyShiroRealm shiroRealm = (MyShiroRealm)rsm.getRealms().iterator().next();
Subject subject = SecurityUtils.getSubject();
// String realmName = subject.getPrincipals().getRealmNames().iterator().next();
// SimplePrincipalCollection principals = new SimplePrincipalCollection(subject.getPrincipals(),realmName);
// subject.runAs(principals);
//用realm删除principle
shiroRealm.getAuthorizationCache().remove(subject.getPrincipals());
}
}
定义全局异常,当用户没有权限或者登录失败的时候,自动给前段返回信息。
@ExceptionHandler(value=UnauthorizedException.class)
@ResponseBody
public ResultModel<String> unauthorizedHandler( UnauthorizedException exception) throws Exception{
ResultModel<String> result = new ResultModel<>();
result.setSuccessed(false);
result.setErrorCode(ErrorCode.SERVER_ERROR.getErrorCode());
result.setErrorDesc("用户没有权限");
return result;
}
@ExceptionHandler(value=UnauthenticatedException.class)
@ResponseBody
public ResultModel<String> unauthenticatedHandler( UnauthenticatedException exception) throws Exception{
ResultModel<String> result = new ResultModel<>();
result.setSuccessed(false);
result.setErrorCode(ErrorCode.ILLEGAL_USER.getErrorCode());
result.setErrorDesc(ErrorCode.ILLEGAL_USER.getErrorMessage());
return result;
}
最后附上登录:
///登录开始
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try{
subject.login(token);
}catch (Exception e){
throw new ValidateException("用户名或密码错误");
}
if(subject.isAuthenticated()){
ManageInfoVo manageInfoVo = (ManageInfoVo) subject.getPrincipal();
Subject subjectToken = SecurityUtils.getSubject();
manageInfoVo.setToken((String)subjectToken.getSession().getId());
return manageInfoVo;
}else {
throw new ValidateException("用户名或密码错误,登录失败。");
}