一、pom
<!--shiro--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.3</version> </dependency>
二、shiro关键名称
Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support:Web支持,可以非常容易的集成到Web环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
Testing:提供测试支持;
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
Subject:主体,可以看到主体可以是任何可以与应用交互的“用户”;
SecurityManager:相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。
Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
Realm:可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;
SessionManager:如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;所有呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据(这就是为什么应用能够判断访问的用户有没有权限进行其他的操作。在用户登录后Realm会将这个用户的权限和这个用的session做好关联,每当用户访问其他接口shiro都能从session中获取这个用户的权限并且与访问的当前接口的权限进行比较);这样的话,比如我们在Web环境用,刚开始是一台Web服务器;接着又上了台EJB服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到Memcached服务器);
SessionDAO:DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;
CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的。
参考:https://jinnianshilongnian.iteye.com/blog/2018936
三、userRealm
** * @program: maque * @description: shiro用户认证,授权 * @author: maque * @create: 2019-08-01 15:24 */ public class UserRealm extends AuthorizingRealm { private Logger loger = LoggerFactory.getLogger(getClass()); @Autowired private SystemAdminService systemAdminService; @Autowired private SystemRoleService systemRoleService; @Autowired private SystemPermissionService systemPermissionService; @Autowired private RedisSevice redisSevice; /** * 给用户授权限 * 此方法是shiro留给开发人员实现给用户授权使用。 * @param principals * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String userName = (String)principals.getPrimaryPrincipal(); loger.info("doGetAuthorizationInfo,username:{"+userName+"}"); //权限交互对象 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); //首先从缓存中查询角色信息,如果不存在从数据库中查询 List<SystemRole> rlist=systemRoleService.findRoleListByUserName(userName); Set roleSet= new HashSet<String>(); StringBuffer roleNames=new StringBuffer(""); if (rlist != null || rlist.size()>0){ //从数据库查找用户的角色 if (rlist!=null && rlist.size()>0){ int i=0; for (SystemRole role:rlist){ roleSet.add(role.getId()); roleNames.append(role.getRoleName()); if (i!=(rlist.size()-1)){ roleNames.append(","); } i++; } } } //将用户角色放置到授权认证对象中 authorizationInfo.setRoles(roleSet); //从缓存中查找用户权限列表 Set permissionSet= new HashSet<String>(); List<SystemPermissionEntity> plist=systemPermissionService.findAllPermissionByRoleIds(new ArrayList<String>(roleSet)); if (plist!=null && plist.size()>0){ for(SystemPermissionEntity per:plist){ permissionSet.add(per.getPermissionCode()); } } //将用户权限放置到授权认证对象 authorizationInfo.setStringPermissions(permissionSet); return authorizationInfo; } /** * 身份验证 * 调用subject.login(token)方法时会调用doGetAuthenticationInfo方法 * @param token * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken loginToken = (UsernamePasswordToken) token; String userName = loginToken.getUsername(); String password = String.copyValueOf(loginToken.getPassword()); SystemAdminEntity admin = systemAdminService.selectAdminByUserPassword(userName, Md5Encrypt.md5(password)); if (admin == null){ loger.info("=======用户名及密码错误{"+userName+"}========"); //位置异常 throw new UnknownAccountException("用户名及密码错误"); } if (Contants.is_OPEN_N==admin.getIsOpen()){ loger.info("=======用户被锁定{"+userName+"}========"); //锁定异常 throw new LockedAccountException(); } SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(admin.getUserName(),admin.getPassword(),getName()); //redisSevice.setCacheData(Contants.CACHE_ADMIN_USERNAME_KEY+admin.getUserName(),admin,24); //redisSevice.setCacheData(Contants.CACHE_ADMINUSERNAME_KEY+admin.getId(),admin,24); return authenticationInfo; } }
四、MyShiroConfig
/** * @program: maque * @description: shiro过滤器 * @author: maque * @create: 2019-04-29 21:14 */ @Configuration public class MyShiroConfig { /** * 注册shiroFilter * @return */ @Bean public FilterRegistrationBean filterRegistrationBean() { FilterRegistrationBean filterRegistration = new FilterRegistrationBean(); DelegatingFilterProxy delegatingFilterProxy = new DelegatingFilterProxy("shiroFilterFactoryBean"); delegatingFilterProxy.setTargetFilterLifecycle(true); filterRegistration.setFilter(delegatingFilterProxy); filterRegistration.setEnabled(true); filterRegistration.addUrlPatterns("/*"); filterRegistration.setDispatcherTypes(DispatcherType.REQUEST); return filterRegistration; } /**定义shiroFilter过滤器并注入securityManager * 创建ShiroFilterFactoryBean * @param defaultWebSecurityManager * @return */ @Bean(name="shiroFilterFactoryBean") public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager")DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //设置安全管理器 shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); /* * anon:表示可以匿名使用。 authc:表示需要认证(登录)才能使用,没有参数 */ Map<String, String> sMap = new HashMap<String, String>(); //拦截页面 sMap .put("/test/authc.action", "authc"); //匿名访问,即所有人都可以访问 sMap .put("/test/login.action", "anon"); sMap .put("/test/goLogin.action", "anon"); sMap .put("/test/unAuthorized.action","anon"); //被拦截返回登录页面 shiroFilterFactoryBean.setLoginUrl("/test/goLogin.action"); //授权拦截返回页面 权限不足跳转页面,此处设置不起作用,当权限布够的时候会抛出异常,并没有返回到想要 //的页面。后面会有响应的解决方法 shiroFilterFactoryBean.setUnauthorizedUrl("/test/unAuthorized.action"); shiroFilterFactoryBean.setFilterChainDefinitionMap(sMap); return shiroFilterFactoryBean; } /**定义安全管理器securityManager,注入自定义的realm * 创建DefaultWebSecurityManager * @param userRealm * @return */ @Bean(name="defaultWebSecurityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm")UserRealm userRealm){ DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); defaultWebSecurityManager.setRealm(userRealm); return defaultWebSecurityManager; } /** * 密码校验规则HashedCredentialsMatcher * 这个类是为了对密码进行编码的 , * 防止密码在数据库里明码保存 , 当然在登陆认证的时候 , * 这个类也负责对form里输入的密码进行编码 * 处理认证匹配处理器:如果自定义需要实现继承HashedCredentialsMatcher */ @Bean("hashedCredentialsMatcher") public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); //指定加密方式为MD5 credentialsMatcher.setHashAlgorithmName("MD5"); //加密次数,要和数据库中密码加密次数保持一致。 credentialsMatcher.setHashIterations(1); credentialsMatcher.setStoredCredentialsHexEncoded(true); return credentialsMatcher; } /**生成用户认证,授权 现实对象,并注入注入自定义的加密方式 * 创建Realm * @return */ @Bean(name="userRealm") public UserRealm getUserRealm(HashedCredentialsMatcher hashedCredentialsMatcher){ UserRealm userRealm = new UserRealm(); userRealm.setAuthorizationCachingEnabled(true); userRealm.setCredentialsMatcher(hashedCredentialsMatcher); return userRealm; } @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * * * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 * * * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能 * * @return */ @Bean @DependsOn({"lifecycleBeanPostProcessor"}) public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } /** * 开启shiro注解 * @param defaultWebSecurityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("defaultWebSecurityManager")DefaultWebSecurityManager defaultWebSecurityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager); return authorizationAttributeSourceAdvisor; } //参考 https://www.cnblogs.com/bbthome/p/8688849.html /** * 解决 没有权限时并没有跳转shiroFilterFactoryBean.setUnauthorizedUrl("/test/unAuthorized.action"); * 而是抛出org.apache.shiro.authz.AuthorizationException异常问题 * 自己来处理UnauthorizedException异常 * @return */ @Bean public SimpleMappingExceptionResolver shiroUnAuthorizedresolver() { SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver(); Properties properties = new Properties(); //unAuthorized是页面不是请求接口 properties.setProperty("org.apache.shiro.authz.AuthorizationException", "unAuthorized"); resolver.setExceptionMappings(properties); return resolver; } }
五、测试
/** * 测试shiroFilterFactoryBean.setLoginUrl("/test/goLogin.action"); */ @RequestMapping("/goLogin") public String goLogin(){ return "index"; } @ResponseBody @RequestMapping("/login") public ResultResponBody login(String userName,String password){ ResultResponBody result = new ResultResponBody(); UsernamePasswordToken token=null; String msg = ""; try { Subject subject = SecurityUtils.getSubject(); token = new UsernamePasswordToken(userName,password); token.setRememberMe(true); //用户认证,通过自定义的 UserRealm 来实现用户认证即未用户授权,shiro会将用户的权限信息将会和 //session做关联, subject.login(token); if (subject.isAuthenticated()){ result.setResult(true); return result; }else { result.setResult(false); result.setMessage("登录失败"); return result; } } catch (IncorrectCredentialsException e) { e.printStackTrace(); result.setResult(false); msg = "登录密码错误. Password for account " + token.getPrincipal() + " was incorrect."; }catch (ExcessiveAttemptsException e) { result.setResult(false); msg = "登录失败次数过多"; } catch (LockedAccountException e) { result.setResult(false); msg = "帐号已被锁定. The account for username " + token.getPrincipal() + " was locked."; } catch (DisabledAccountException e) { result.setResult(false); msg = "帐号已被禁用. The account for username " + token.getPrincipal() + " was disabled."; } catch (ExpiredCredentialsException e) { result.setResult(false); msg = "帐号已过期. the account for username " + token.getPrincipal() + " was expired."; } catch (UnknownAccountException e) { result.setResult(false); if (!StringUtils.isEmpty(userName)) { msg = "帐号不存在. There is no user with username of " + token.getPrincipal(); } } catch (UnauthorizedException e) { msg = "您没有得到相应的授权!" + e.getMessage(); } result.setMessage(msg); return result; } /** * 测试权限 */ @RequiresPermissions(value = {"system.oc.c.j.q"}) @RequestMapping("/authc") public String gouserManage(){ return "user/userManage"; } /** * 权限布够要跳转的接口 * shiroFilterFactoryBean.setUnauthorizedUrl("/test/unAuthorized.action");布跳转这个问题有针对的解决方法 * @return */ @RequestMapping("/unAuthorized") public String unAuthorizedUrl(){ return "unAuthorized"; }