SpringBoot整合shiro、自定义sessionManager

匿名 (未验证) 提交于 2019-12-03 00:43:02

1.基础数据


首先引入shiro依赖jar包

         <dependency>             <groupId>org.apache.shiro</groupId>             <artifactId>shiro-spring</artifactId>             <version>1.4.0</version>         </dependency>         <!--shiro缓存插件-->         <dependency>             <groupId>org.apache.shiro</groupId>             <artifactId>shiro-ehcache</artifactId>             <version>1.2.2</version>         </dependency>










这里贴三张表的字段设计

 public class SysUser {     private Integer userId;      private String userAccount;//用户账号     private String userPassword;//用户密码 }
 public class SysRole {     private Integer sysRoleId;     private Byte sysRoleAva; //角色是否生效     private String sysRoleDes;//角色描述     private String sysRoleName;//角色名称 }
 public class SysAuth {     private Integer sysAuthId;     private String sysAuthCode; //权限编号     private String sysAuthName; //权限名称     private String sysAuthUrl; //权限请求的url 例如: user/login     private String sysAuthPermission; //权限的的名称例如 user:login     private Byte sysAuthAva; //权限是否有效     private Byte sysAuthType; //权限类型。菜单还是按钮     private String sysAuthDes; //权限描述 }

 import com.lingjiugis.ocr.domain.SysAuth; import com.lingjiugis.ocr.domain.SysRole; import com.lingjiugis.ocr.domain.SysUser; import com.lingjiugis.ocr.service.SysAuthService; import com.lingjiugis.ocr.service.SysRoleService; import com.lingjiugis.ocr.service.UserService; import org.apache.commons.lang.exception.ExceptionUtils; 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.slf4j.Logger; import org.slf4j.LoggerFactory;  import javax.annotation.Resource; import java.util.List; public class ShiroRealm extends AuthorizingRealm {     private static Logger logger = LoggerFactory.getLogger(ShiroRealm.class);     //这里尝试过使用@Autowired 但是发现会报错。这个是spring的注解。如果有知道原因的可以留言。谢谢     @Resource     private UserService userService;     @Resource     private SysRoleService sysRoleService;     @Resource     private SysAuthService authService;     /**      * 配置权限 注入权限      * @param principals      * @return      */     @Override     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){         System.out.println("--------权限配置-------");         SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();         SysUser user = (SysUser) principals.getPrimaryPrincipal();         try {             //注入角色(查询所有的角色注入控制器)             List<SysRole> list = sysRoleService.selectRoleByUser(user.getUserId());             for (SysRole role: list){                 authorizationInfo.addRole(role.getSysRoleName());             }             //注入角色所有权限(查询用户所有的权限注入控制器)             List<SysAuth> sysAuths = authService.queryByUserId(user.getUserId());             for(SysAuth sysAuth:sysAuths){                 authorizationInfo.addStringPermission(sysAuth.getSysAuthPermission());             }         }catch (Exception e){             e.printStackTrace();             logger.error(ExceptionUtils.getFullStackTrace(e));         }         return authorizationInfo;     }      /**      * 用户验证      * @param token 账户数据      * @return      * @throws AuthenticationException 根据账户数据查询账户。根据账户状态抛出对应的异常      */     @Override     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {          //获取用户的输入的账号         String username = (String) token.getPrincipal();         //这里需注意。看别人的教程有人是这样写的String password = (String) token.getCredentials();         //项目运行的时候报错,发现密码不正确。后来进源码查看发现将密码注入后。Shiro会进行转义将字符串转换成字符数组。         //源码:this(username, password != null ? password.toCharArray() : null, false, null);         //不晓得是否是因为版本的原因,建议使用的时候下载源码进行查看         String password = new String((char[]) token.getCredentials());         //通过username从数据库中查找 User对象,如果找到,没找到.         //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法         SysUser user = userService.selectByAccount(username);         if(null == user){             throw new UnknownAccountException();         }else {             if(password.equals(user.getUserPassword())){                 if(0 == user.getUserState()){                     throw new LockedAccountException();                 }else if (2 == user.getUserState()){                     throw new DisabledAccountException();                 }else{                     SimpleAuthenticationInfo authorizationInfo = new SimpleAuthenticationInfo(user,user.getUserPassword().toCharArray(),getName());                     return authorizationInfo;                 }             } else {                 throw new IncorrectCredentialsException();             }         }     } }

1.2 接下来配置Shiro的关键部分

 import com.lingjiugis.ocr.config.GlobalExceptionResolver; import com.lingjiugis.ocr.filter.ShiroSessionManager; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO; 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.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.HandlerExceptionResolver;  import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfig {     @Bean     public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {         System.out.println("--------------------shiro filter-------------------");         ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();         shiroFilterFactoryBean.setSecurityManager(securityManager);         Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();         //注意过滤器配置顺序 不能颠倒         //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了,登出后跳转配置的loginUrl         // 配置不会被拦截的链接 顺序判断         filterChainDefinitionMap.put("/static/**", "anon");         filterChainDefinitionMap.put("/favicon.ico", "anon");         //拦截其他所以接口         filterChainDefinitionMap.put("/**", "authc");         //配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据         shiroFilterFactoryBean.setLoginUrl("/user/unlogin");         // 登录成功后要跳转的链接 自行处理。不用shiro进行跳转         // shiroFilterFactoryBean.setSuccessUrl("user/index");         //未授权界面;          shiroFilterFactoryBean.setUnauthorizedUrl("/user/unauth");         shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);         return shiroFilterFactoryBean;     }      /**      * shiro 用户数据注入      * @return      */     @Bean     public ShiroRealm shiroRealm(){         ShiroRealm shiroRealm = new ShiroRealm();         return shiroRealm;     }      /**      * 配置管理层。即安全控制层      * @return      */     @Bean     public SecurityManager securityManager(){         DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();         securityManager.setRealm(shiroRealm());         return  securityManager;     }     public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){         DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();         advisorAutoProxyCreator.setProxyTargetClass(true);         return advisorAutoProxyCreator;     }     /**      * 开启shiro aop注解支持 使用代理方式所以需要开启代码支持      *  一定要写入上面advisorAutoProxyCreator()自动代理。不然AOP注解不会生效      * @param securityManager      * @return      */     @Bean     public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){         AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();         authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);         return authorizationAttributeSourceAdvisor;     } }

1.3 修改我们的Controller中的登录请求

 // 这里如果不写method参数的话,默认支持所有请求,如果想缩小请求范围,还是要添加method来支持get, post等等某个请求。     @RequestMapping("/login")     public String login(HttpServletRequest request, Map<String, Object> map) throws Exception {         BaseResponse<String> baseResponse = new BaseResponse<>();         Subject subject = SecurityUtils.getSubject();         //数据库的密码我进行了Md5加密。如果没有进行加密的无需这个         user.setUserPassword(MD5Util.getPwd(user.getUserPassword()));         UsernamePasswordToken token = new UsernamePasswordToken(user.getUserAccount(),user.getUserPassword());         try {             subject.login(token);             //System.out.println(getSession().getId());             baseResponse.success(getSession().getId());         } catch (UnknownAccountException e){             baseResponse.setMsg("用户名不存在");         } catch (IncorrectCredentialsException e){             e.printStackTrace();             baseResponse.setMsg("密码错误");         } catch (LockedAccountException e){             baseResponse.setCode(CodeField.ACCOUNT_NOT_ACTIVAT);             baseResponse.setMsg(CodeField.ACCOUNT_NOT_ACTIVAT_MSG);         }catch (DisabledAccountException e){             baseResponse.setCode(CodeField.ACCOUNT_BAN);             baseResponse.setMsg(CodeField.ACCOUNT_BAN_MSG);         } catch (Exception e){             e.printStackTrace();             logger.error(ExceptionUtils.getFullStackTrace(e));         }         return baseResponse;     }


配置完成了就可以运行起来了。

 @RestController @RequestMapping("user") public class UserController(){     /**      * 测试      * @return      */     @RequestMapping("/test")     //拥有此权限的才可以访问     @RequiresPermissions("user:test")     //拥有此角色的才可以访问     @RequiresRoles("admin")     public BaseResponse test() {         BaseResponse baseResponse = new BaseResponse();         baseResponse.setMsg("用户拥有该权限");         return baseResponse;     } }

 shiroFilterFactoryBean.setUnauthorizedUrl("/user/unauth");

2.异常、缓存



这里选择自定义异常处理。处理全局异常。

2.1 自定义全局异常处理

 import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.support.spring.FastJsonJsonView; import com.lingjiugis.ocr.response.base.BaseResponse; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.shiro.authz.UnauthorizedException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView;  import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.HashMap; import java.util.Map;  /**  * Description: 全局异常处理  *  * @author zlp  * @create 2018-05-24 11:13  **/ public class GlobalExceptionResolver implements HandlerExceptionResolver {      private static Logger logger = LoggerFactory.getLogger(GlobalExceptionResolver.class);      @Override     public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {         ModelAndView mv;         //进行异常判断。如果捕获异常请求跳转。         if(ex instanceof UnauthorizedException){             mv = new ModelAndView("/user/unauth");             return mv;         }else {             mv = new ModelAndView();             FastJsonJsonView view = new FastJsonJsonView();             BaseResponse baseResponse = new BaseResponse();             baseResponse.setMsg("服务器异常");             ex.printStackTrace();             logger.error(ExceptionUtils.getFullStackTrace(ex));             Map<String,Object> map = new HashMap<>();             String beanString = JSON.toJSONString(baseResponse);             map = JSON.parseObject(beanString,Map.class);             view.setAttributesMap(map);             mv.setView(view);             return mv;          }      } }

在前面配置的ShiroConfig添加如下代码块

     /**      * 注册全局异常处理      * @return      */     @Bean(name = "exceptionHandler")     public HandlerExceptionResolver handlerExceptionResolver(){         return new GlobalExceptionResolver();     }


首先在项目配置包中写如缓存配置文件ehcache-shiro.xml

 <!--配置文件来源于网络。具体实际配置要参照配置文档。进行合理配置--> <?xml version="1.0" encoding="UTF-8"?> <ehcache name="es">     <diskStore path="java.io.tmpdir"/>     <!--        name:缓存名称。        maxElementsInMemory:缓存最大数目        maxElementsOnDisk:硬盘最大缓存个数。        eternal:对象是否永久有效,一但设置了,timeout将不起作用。        overflowToDisk:是否保存到磁盘,当系统当机时        timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。        timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。        diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.        diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。        diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。        memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。         clearOnFlush:内存数量最大时是否清除。          memoryStoreEvictionPolicy:             Ehcache的三种清空策略;             FIFO,first in first out,这个是大家最熟的,先进先出。             LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。             LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。     -->     <defaultCache             maxElementsInMemory="10000"             eternal="false"             timeToIdleSeconds="120"             timeToLiveSeconds="120"             overflowToDisk="false"             diskPersistent="false"             diskExpiryThreadIntervalSeconds="120"     />     <!-- 登录记录缓存锁定10分钟 -->     <cache name="passwordRetryCache"            maxEntriesLocalHeap="2000"            eternal="false"            timeToIdleSeconds="3600"            timeToLiveSeconds="0"            overflowToDisk="false"            statistics="true">     </cache> </ehcache>

然后修改ShiroConfig

     //添加方法     /**      * 开启缓存      * shiro-ehcache实现      * @return      */     @Bean     public EhCacheManager ehCacheManager() {         System.out.println("ShiroConfiguration.getEhCacheManager()");         EhCacheManager ehCacheManager = new EhCacheManager();         ehCacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");         return ehCacheManager;     }     //修改securityManager方法。     /**      * 配置管理层。即安全控制层      * @return      */     @Bean     public SecurityManager securityManager(){         DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();         securityManager.setRealm(shiroRealm());         //自定义缓存实现         securityManager.setCacheManager(ehCacheManager());         return  securityManager;     }

现在主流的缓存插件为Redis。但是我进行配置的时候总是会报数据源异常。因为网上用的连接池大部分都是阿里的druid。而我的项目使用的是springboot默认的连接池,配置不同。

3.自定义sessionManager

 import org.apache.shiro.web.servlet.ShiroHttpServletRequest; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.apache.shiro.web.util.WebUtils; import org.springframework.util.StringUtils;  import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.Serializable;  /**  * Description:shiro框架 自定义session获取方式  * 可自定义session获取规则。这里采用ajax请求头authToken携带sessionId的方式  *  * @author zlp  * @create 2018-05-24 10:04  **/ public class ShiroSessionManager extends DefaultWebSessionManager {      private static final String AUTHORIZATION = "authToken";      private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";      public ShiroSessionManager(){         super();     }      @Override     protected Serializable getSessionId(ServletRequest request, ServletResponse response){         String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);         System.out.println("id:"+id);         if(StringUtils.isEmpty(id)){             //如果没有携带id参数则按照父类的方式在cookie进行获取             System.out.println("super:"+super.getSessionId(request, response));             return super.getSessionId(request, response);         }else{             //如果请求头中有 authToken 则其值为sessionId    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;         }     } }

然后修改ShiroConfig 类。将自定义的ShiroSessionManager 注入管理器中

     //添加bean     /**      * 自定义sessionManager      * @return      */     @Bean     public SessionManager sessionManager(){         ShiroSessionManager shiroSessionManager = new ShiroSessionManager();         //这里可以不设置。Shiro有默认的session管理。如果缓存为Redis则需改用Redis的管理         shiroSessionManager.setSessionDAO(new EnterpriseCacheSessionDAO());         return shiroSessionManager;     }     //修改securityManager()方法     /**      * 配置管理层。即安全控制层      * @return      */     @Bean     public SecurityManager securityManager(){         DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();         securityManager.setRealm(shiroRealm());         //自定义session管理         securityManager.setSessionManager(sessionManager());         //自定义缓存实现         securityManager.setCacheManager(ehCacheManager());         return  securityManager;     }

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!