Spring boot 入门(四):集成 Shiro 实现登陆认证和权限管理
2019-03-02 00:09
751 查看
本文是接着上篇博客写的:Spring boot 入门(三):SpringBoot 集成结合 AdminLTE(Freemarker),利用 generate 自动生成代码,利用 DataTable 和 PageHelper 进行分页显示。按照前面的博客,已经可以搭建一个简单的 Spring Boot 系统,本篇博客继续对此系统进行改造,主要集成了 Shiro 权限认证框架,关于 Shiro 部分,在本人之前的博客(认证与Shiro安全框架)有介绍到,这里就不做累赘的介绍。
此系列的博客为实践部分,以代码和搭建系统的过程为主,如遇到专业名词,自行查找其含义。
1.Shiro 配置类
系统搭建到目前为止,主要用到了3个配置类,均与 Shiro 有关,后期随着项目的扩大,配置文件也会随之增多。
- FreeMarkerConfig:主要针对 FreeMarker 页面显示的配置,关于 Shiro 部分,为 Shiro 标签设置了共享变量
,如果不设置此变量,FreeMarker 页面将不能识别 Shiro 的标签
,其主要代码如下:
configuration.setSharedVariable("shiro", new ShiroTags());
- MShiroFilterFactoryBean:设置了过滤器,当然也可以在 Config 文件里面配置过滤器,其缺点是:
在每次请求里面都做了 session 的读取和更新访问时间等操作,这样在集群部署 session 共享的情况下,数量级的加大了处理量负载
。本项目后期将用到分布式,因此这里就直接将过滤器与 Config 配置文件分离,提高效率。
private final class MSpringShiroFilter extends AbstractShiroFilter { protected MSpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) { super(); if (webSecurityManager == null) { throw new IllegalArgumentException("WebSecurityManager property cannot be null."); } setSecurityManager(webSecurityManager); if (resolver != null) { setFilterChainResolver(resolver); } } @Override protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws ServletException, IOException { HttpServletRequest request = (HttpServletRequest) servletRequest; String str = request.getRequestURI().toLowerCase(); boolean flag = true; int idx = 0; if ((idx = str.indexOf(".")) > 0) { str = str.substring(idx); if (ignoreExt.contains(str.toLowerCase())) flag = false; } if (flag) { super.doFilterInternal(servletRequest, servletResponse, chain); } else { chain.doFilter(servletRequest, servletResponse); } } }
- ShiroConfiguration:通用配置文件,此配置文件为 Shiro 的基础通用配置文件,只要是集成 Shiro,必有此文件,主要配置 Shiro 的登录认证相关的信息,其代码如下:
/** * 设置shiro的缓存,缓存参数均配置在xml文件中 * @return */ @Bean public EhCacheManager getEhCacheManager() { EhCacheManager em = new EhCacheManager(); em.setCacheManagerConfigFile("classpath:ehcache/ehcache-shiro.xml"); return em; } /** * 凭证匹配器 * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了 * 所以我们需要修改下doGetAuthenticationInfo中的代码; * ) * @return */ @Bean public HashedCredentialsMatcher hashedCredentialsMatcher(){ HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法; aec hashedCredentialsMatcher.setHashIterations(1);//散列的次数,比如散列两次,相当于 md5(md5("")); return hashedCredentialsMatcher; } /** * * 主文件 */ @Bean(name = "myShiroRealm") public UserRealm myShiroRealm(EhCacheManager cacheManager) { UserRealm realm = new UserRealm(); realm.setCacheManager(cacheManager); realm.setCredentialsMatcher(hashedCredentialsMatcher()); return realm; } //会话ID生成器 @Bean(name = "sessionIdGenerator") public JavaUuidSessionIdGenerator javaUuidSessionIdGenerator(){ JavaUuidSessionIdGenerator javaUuidSessionIdGenerator = new JavaUuidSessionIdGenerator(); return javaUuidSessionIdGenerator; } @Bean(name = "sessionIdCookie") 43 public SimpleCookie getSessionIdCookie(){ SimpleCookie sessionIdCookie = new SimpleCookie("sid"); sessionIdCookie.setHttpOnly(true); sessionIdCookie.setMaxAge(-1); return sessionIdCookie; } /*<!-- 会话DAO -->*/ @Bean(name = "sessionDAO") public EnterpriseCacheSessionDAO enterpriseCacheSessionDAO(){ EnterpriseCacheSessionDAO sessionDao = new EnterpriseCacheSessionDAO(); sessionDao.setSessionIdGenerator(javaUuidSessionIdGenerator()); sessionDao.setActiveSessionsCacheName("shiro-activeSessionCache"); return sessionDao; } @Bean(name = "sessionValidationScheduler") public ExecutorServiceSessionValidationScheduler getExecutorServiceSessionValidationScheduler() { ExecutorServiceSessionValidationScheduler scheduler = new ExecutorServiceSessionValidationScheduler(); scheduler.setInterval(1800000); return scheduler; } @Bean(name = "sessionManager") public DefaultWebSessionManager sessionManager(EnterpriseCacheSessionDAO sessionDAO){ DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setGlobalSessionTimeout(1800000); sessionManager.setDeleteInvalidSessions(true); sessionManager.setSessionValidationSchedulerEnabled(true); sessionManager.setSessionValidationScheduler(getExecutorServiceSessionValidationScheduler()); sessionManager.setSessionDAO(sessionDAO); sessionManager.setSessionIdCookieEnabled(true); sessionManager.setSessionIdCookie(getSessionIdCookie()); return sessionManager; } @Bean(name = "lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator(); daap.setProxyTargetClass(true); return daap; } @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm myShiroRealm, DefaultWebSessionManager sessionManager) { DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager(); dwsm.setRealm(myShiroRealm); // <!-- 用户授权/认证信息Cache, 采用EhCache 缓存 --> dwsm.setCacheManager(getEhCacheManager()); dwsm.setSessionManager(sessionManager); return dwsm; } /** * 开启shiro aop注解支持. * 使用代理方式;所以需要开启代码支持; * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor(); aasa.setSecurityManager(securityManager); return aasa; } /** * ShiroFilter<br/> * 注意这里参数中的 StudentService 和 IScoreDao 只是一个例子,因为我们在这里可以用这样的方式获取到相关访问数据库的对象, < 2632 span style="color: #008080;">110 * 然后读取数据库相关配置,配置到 shiroFilterFactoryBean 的访问规则中。实际项目中,请使用自己的Service来处理业务逻辑。 * */ @Bean(name = "shiroFilter") public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new MShiroFilterFactoryBean(); // 必须设置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setLoginUrl("/login"); // 登录成功后要跳转的连接 shiroFilterFactoryBean.setSuccessUrl("/certification"); //shiroFilterFactoryBean.setSuccessUrl("/index"); shiroFilterFactoryBean.setUnauthorizedUrl("/403"); loadShiroFilterChain(shiroFilterFactoryBean); return shiroFilterFactoryBean; } /** * 加载shiroFilter权限控制规则(从数据库读取然后配置) * */ private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean){ /////////////////////// 下面这些规则配置最好配置到配置文件中 /////////////////////// Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter filterChainDefinitionMap.put("/login", "authc"); filterChainDefinitionMap.put("/logout", "logout"); // anon:它对应的过滤器里面是空的,什么都没做 logger.info("##################从数据库读取权限规则,加载到shiroFilter中##################"); // filterChainDefinitionMap.put("/user/edit/**", "authc,perms[user:edit]");// 这里为了测试,固定写死的值,也可以从数据库或其他配置中读取 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); }
2.登录认证与权限管理
主要重写了 Realm域,完成权限认证和权限管理:
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //如果没有做权限验证,此处只需要return null即可 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); String userName = (String) principals.getPrimaryPrincipal(); Result<TUser> list = userService.getUserByUsername(userName); if(list.isStatus()) { //获取该用户所属的角色 Result<List<TRole>> resultRole = roleService.getRoleByUserId(list.getResultData().getUserId()); if(resultRole.isStatus()) { HashSet<String> role = new HashSet<String>(); for(TRole tRole : resultRole.getResultData()) { role.add(tRole.getRoleId()+""); } //获取该角色拥有的权限 Result<List<TPermission>> resultPermission = permissionService.getPermissionsByRoleId(role); if(resultPermission.isStatus()) { HashSet<String> permissions = new HashSet<String>(); for(TPermission tPermission : resultPermission.getResultData()) { permissions.add(tPermission.getPermissionsValue()); } System.out.println("权限:"+permissions); authorizationInfo.setStringPermissions(permissions); } } } //return null; return authorizationInfo; } 29 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //认证登录 String username = (String) authenticationToken.getPrincipal(); //String password = new String((char[]) authenticationToken.getCredentials()); Result<TUser> result = userService.getUserByUsername(username); if (result.isStatus()) { TUser user = result.getResultData(); return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName()); } //return new SimpleAuthenticationInfo(user., "123456", getName()); return null; } }
2.1.登录认证
首先创建一个前端登录界面,做一个简单的登录 Form 表单
点击登录即想后台发送一个请求,
必须是Post请求,否则Shiro不能识别,认证部分主要在 Ream 中完成,新建一个类,继承 AuthorizingRealm ,然后在重写 doGetAuthenticationInfo 方法: 只需要通过界面上的用户名查找到数据库存储的相关信息即可,具体的认证是 Shiro 内部自己完成的,我们只需要传入数据库中存储的用户名和密码个认证函数即可(
new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName())),我们可以自己重新定义密码比较器,密码比较器的写法较多,在认证与Shiro安全框架中,直接将密码比较器写入到Ream中,耦合度太高,本项目通过配置的方式重写密码比较器,具体代码请参考参考ShiroConfiguration配置类:
在具体的 Login 方法中,写入一些登录失败的异常即可,主要用户将此失败结果存入 Session,并显示在页面上:
@RequestMapping(value = "/login", method = RequestMethod.POST) public String postLogin(RedirectAttributes redirectAttributes, HttpServletRequest request, HttpSession session) { // 登录失败从request中获取shiro处理的异常信息。 // shiroLoginFailure:就是shiro异常类的全类名. String exception = (String) request.getAttribute("shiroLoginFailure"); System.out.println("exception=" + exception); String msg = ""; if (exception != null) { if (UnknownAccountException.class.getName().equals(exception)) { System.out.println("UnknownAccountException -- > 账号不存在:"); msg = "用户不存在!"; } else if (IncorrectCredentialsException.class.getName().equals(exception)) { System.out.println("IncorrectCredentialsException -- > 密码不正确:"); msg = "密码不正确!"; } else if ("kaptchaValidateFailed".equals(exception)) { System.out.println("kaptchaValidateFailed -- > 验证码错误"); msg = "验证码错误!"; } else { //msg = "else >> "+exception; msg = "密码不正确!"; System.out.println("else -- >" + exception); } } redirectAttributes.addFlashAttribute("msg", msg); session.setAttribute("msg", msg); //return redirect("/login"); return "redirect:login"; //return msg; }
此时登录认证部门已经完成:一个页面+后台2个函数(1个认证函数+1个Login函数)
2.2.权限管理
总体来说,权限管理只需要在界面增加 Shiro 的权限标签即可,可以使用角色的标签,也可以使用权限的标签,一般情况下2种标签配合使用,效果最好
<@shiro.hasPermission name="xtgl-yhgl:read">
<@shiro.hasRolen name="xtgl-yhgl:read">
此外,在 Realm 中,需要重写权限认证的业务逻辑,通常情况下通过用户 ID 找到该用户所属的角色,然后通过角色 ID 找到该角色拥有的权限,并将角色或者权限写入的 Shiro 中即可:
authorizationInfo.setStringPermissions(permissions);
authorizationInfo.setRoles(role);
本项目也是通过此逻辑完成权限管理的
上面2张截图表示的是一个函数。
到此,Spring Boot集成Shiro框架的权限认证已经搭建完毕,可以实现简单的权限管理。
3.新增文件
较上一篇博客,Shiro 部分新增加的文件
相关文章推荐
- shiro 权限认证框集成到spring中,实现登陆与权限拦截
- spring boot mybatis 整合shiro简单实现登陆权限管理
- Spring Cloud之路:(七)SpringBoot+Shiro实现登录认证和权限管理
- Spring Boot + Spring Cloud 实现权限管理系统 后端篇(十一):集成 Shiro 框架
- springboot(十四):springboot整合shiro-登录认证和权限管理
- SpringBoot整合mybatis、shiro、redis实现基于数据库的细粒度动态权限管理系统实例
- springmvc+shiro+maven 实现登录认证与权限授权管理
- springboot整合shiro-登录认证和权限管理
- spring boot 整合shiro和redis实现权限管理和登录功能
- springboot(十四):springboot整合shiro-登录认证和权限管理
- 转载:Spring Boot (十四):springboot整合shiro-登录认证和权限管理
- Spring项目集成ShiroFilter简单实现权限管理
- spring boot 1.5.4 集成shiro+cas,实现单点登录和权限控制
- Springboot集成权限管理框架apache shiro
- springboot 整合shiro + cas 实现单点登录权限管理(一)(sso)
- SpringBoot整合mybatis、shiro、redis实现基于数据库的细粒度动态权限管理系统实例
- 如何在 Spring Boot 中用 Shiro 实现权限管理
- springboot整合shiro-登录认证和权限管理
- SpringBoot2.0整合Shiro框架实现用户权限管理的示例
- Spring Boot 整合 Shiro实现认证及授权管理