参考文章:
https://blog.csdn.net/jin5203344/article/details/53174341
本篇文章我将会从两个方面去讲解,一个是从shiro的应用 第二个是我在项目中遇到的一些问题:
1.shiro的整个登录流程:
2.首先我们来看看shiro的应用
导入maven依赖
接下来shiroconfig配置
@Configuration @Slf4j public class ShiroConfig { @Bean public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * Shiro 权限相关注解 使用 * * @return */ @Bean public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } /** * ShiroFilterFactoryBean 处理拦截资源文件问题。 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在 * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager ** Filter Chain定义说明 1、一个URL可以配置多个Filter,使用逗号分隔 2、当设置多个过滤器时,全部验证通过,才视为通过 * 3、部分过滤器可指定参数,如perms,roles */ @Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { System.out.println("ShiroConfiguration.shirFilter()"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必须设置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setUnauthorizedUrl("/rest/unauth"); try { loadShiroFilterChain(shiroFilterFactoryBean); } catch (IOException e) { log.error("io读取异常"); e.printStackTrace(); } shiroFilterFactoryBean.setLoginUrl("/rest/login"); return shiroFilterFactoryBean; } public OrPermissionsAuthorizationFilter orPermissionsAuthorizationFilter() { return new OrPermissionsAuthorizationFilter(); } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //设置自定realm securityManager.setRealm(getDatabaseRealm()); //自定义缓存实现 使用redis securityManager.setCacheManager(cacheManager()); //自定义sessoin管理,使用redis securityManager.setSessionManager(sessionManager()); //注入记住我管理器 // securityManager.setRememberMeManager(rememberMeManager()); return securityManager; } /** * 配置shiro redisManager 使用的是shiro-redis开源插件 */ @ConfigurationProperties(prefix = "redis.shiro") @Bean public RedisManager redisManager() { return new RedisManager(); } /** * cacheManager 缓存 redis实现 使用的是shiro-redis开源插件 */ @Bean public RedisCacheManager cacheManager() { RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisManager(redisManager()); return redisCacheManager; } //自定义sessionManager @Bean public SessionManager sessionManager() { MySessionManager mySessionManager = new MySessionManager(); mySessionManager.setSessionDAO(redisSessionDAO()); // mySessionManager.setSessionIdCookie(simpleCookie()); return mySessionManager; } /** * RedisSessionDAO shiro sessionDao层的实现 通过redis 使用的是shiro-redis开源插件 */ @Bean public RedisSessionDAO redisSessionDAO() { RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); redisSessionDAO.setRedisManager(redisManager()); return redisSessionDAO; } /** * cookie对象; */ // @Bean // public SimpleCookie simpleCookie() { // //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe // SimpleCookie simpleCookie = new SimpleCookie( // CookieRememberMeManager.DEFAULT_REMEMBER_ME_COOKIE_NAME); // //setcookie的httponly属性如果设为true的话,会增加对xss防护的安全系数。它有以下特点: // //setcookie()的第七个参数 // //设为true后,只能通过http访问,javascript无法访问 // //防止xss读取cookie // // 是否只在https情况下传输 // simpleCookie.setSecure(false); // // // simpleCookie.setMaxAge(2592000); // return simpleCookie; // } /** * cookie管理对象;记住我功能 */ // @Bean // public CookieRememberMeManager rememberMeManager() { // CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); // cookieRememberMeManager.setCookie(simpleCookie()); // //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位) // cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag==")); // return cookieRememberMeManager; // } /** * 凭证匹配器 (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了 所以我们需要修改下doGetAuthenticationInfo中的代码; * ) */ @Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法; hashedCredentialsMatcher.setHashIterations(1024);//散列的次数,比如散列两次,相当于 md5(md5("")); hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true); return hashedCredentialsMatcher; } @Bean public ShiroRealm getDatabaseRealm() { ShiroRealm myShiroRealm = new ShiroRealm(); myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return myShiroRealm; } /** * 开启shiro aop注解支持. 使用代理方式;所以需要开启代码支持; */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor( SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /** * 加载shiroFilter权限控制规则(然后从数据库读取配置) */ private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean) throws IOException { //自定义拦截器 Map
customisedFilter = new HashMap<>(2); customisedFilter.put("orperms", orPermissionsAuthorizationFilter()); customisedFilter.put("corsAuthenticationFilter", corsAuthenticationFilter()); Map filterChainDefinitionMap = initUrl(); shiroFilterFactoryBean.setFilters(customisedFilter); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); } public CORSAuthenticationFilter corsAuthenticationFilter() { return new CORSAuthenticationFilter(); } /** * 初始化url路径 */ private Map initUrl() throws IOException { Map filterChainDefinitionMap = new LinkedHashMap<>(); InputStream in = this.getClass().getClassLoader().getResourceAsStream("url.properties"); InputStreamReader inputStreamReader = new InputStreamReader(in); BufferedReader br = new BufferedReader(inputStreamReader); String str = null; try { while ((str = br.readLine()) != null) { if (StringUtils.isEmpty(str) || str.contains("#")) { continue; } String[] arr = str.split("="); filterChainDefinitionMap.put(arr[0].trim(), arr[1].trim()); } } catch (IOException e) { e.printStackTrace(); } finally { if (br != null) { br.close(); } } return filterChainDefinitionMap; }
initUrl()方法需要解释一下,在filterChainDefinitionMap中需要配置一系列的拦截路径(太长了),因此我将拦截路径配置到了配置文件中,同样我也推荐大家这样去做,这样管理起来也是非常方便.
下面就是开始
ShiroRealm.java
@Component public class ShiroRealm extends AuthorizingRealm { @Autowired private LoginInfoService loginInfoService; @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("欢迎认证"); UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; //TODO token.setRememberMe(true) String username = token.getPrincipal().toString(); LoginInfo loginInfo = loginInfoService.getByUserName(username); if (loginInfo == null) { return null; } else { SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, loginInfo.getPassword(), this.getClass().getSimpleName()); clearCachedAuthorizationInfo(token.getPrincipal()); return info; } } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("开始授权!"); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 能进入到这里,表示账号已经通过验证了 String userName = (String) principalCollection.getPrimaryPrincipal(); LoginInfo loginInfo = loginInfoService.getByUserName(userName); String type = loginInfo.getType(); Setpermissions = new HashSet<>(); switch (type) { case "1": type = ApiConstant.HQ_FINANCE + ":" + "1"; break; case "2": type = ApiConstant.SUB_COMPANY_MANAGER + ":" + "2"; break; case "3": type = ApiConstant.SUB_COMPANY_FINANCE + ":" + "3"; break; default: break; } permissions.add(type); //给用户添加type 代表具有该类型的权限 加入如类型为admin info.setStringPermissions(permissions); return info;
这样就所有的shiro就可以使用了,这里还有一个地方我需要解释,由于shiro的过滤器都是且的关系,一旦权限或者角色出现或的关系时无法解决,那这个时候shiro自带的过滤器就无法满足我们的需求了 这个时候就需要去重写我们的过滤器.
例如这里我重写了权限过滤器.
public class OrPermissionsAuthorizationFilter extends AuthorizationFilter { @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException { if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) { return true; } Subject subject = getSubject(request, response); String[] perms = (String[]) mappedValue; for (String perm : perms) { String[] permArr = perm.split(":"); if (!isAccessAllowedRealization(permArr, subject, request, response)) { return false; } } return true; } /** * 权限过滤实现 * * @return */ private boolean isAccessAllowedRealization(String[] permArr, Subject subject, ServletRequest request, ServletResponse response) throws IOException { if (permArr.length > 1) { // 权限校验不通过返回的url链接 String unauthorizedUrl = getUnauthorizedUrl(); String username = null; try { username = (String) subject.getPrincipals().getPrimaryPrincipal(); } catch (NullPointerException e) { log.error("用户试图不进行登录进入系统!"); e.printStackTrace(); return false; } LoginInfoService loginInfoService = SpringContextUtils.getContext().getBean(LoginInfoService.class); LoginInfo user = loginInfoService.getByUserName(username); if (!isContains(permArr, request, response, unauthorizedUrl, user)) return false; } return true; } private boolean isContains(String[] permArr, ServletRequest request, ServletResponse response, String unauthorizedUrl, LoginInfo user) throws IOException { if (!Arrays.asList(permArr).contains(user.getType())) { return false; } return true; } /** * 会话超时或权限校验未通过的 因为在校验权限的时候已经进行redirect处理所以这边暂时不处理 */ @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException { HttpServletResponse res = (HttpServletResponse) response; res.setHeader("Access-Control-Allow-Origin", "*"); res.setStatus(HttpServletResponse.SC_OK); res.setHeader("content-type", "text/html;charset=UTF-8"); res.setCharacterEncoding("UTF-8"); PrintWriter writer = res.getWriter(); Mapmap = new HashMap<>(); map.put("code", 702); map.put("msg", "未授权"); writer.write(JSON.toJSONString(map)); writer.close(); return false; }
那么角色过滤器呢 同样也是可以的 !