龙空技术网

Shiro实现权限管理

架构即人生 426

前言:

当前同学们对“java权限管理shiro”大约比较看重,各位老铁们都需要学习一些“java权限管理shiro”的相关内容。那么小编同时在网络上搜集了一些关于“java权限管理shiro””的相关知识,希望小伙伴们能喜欢,我们一起来了解一下吧!

前言

Apache Shiro 是 Java 的一个安全框架。功能强大,使用简单的Java安全框架,它为开发人员提供一个直观而全面的认证,授权,加密及会话管理的解决方案。

功能介绍

资源-角色-权限

登录认证,密码加密(Authentication, Authorization, Cryptography)

用户角色和权限放入缓存(Caching)

会话管理(Session Management)功能实现

实现说明

基于Spring开发Shiro的话,我们只需要实现ShiroFilterFactoryBean即可。

ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();filterFactoryBean.setLoginUrl("/login");// 未登录时候跳转URLfilterFactoryBean.setSuccessUrl("/index");// 成功后欢迎页面filterFactoryBean.setUnauthorizedUrl("/unAuthorized");// 未认证页面filterFactoryBean.setSecurityManager(securityManager);filterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

上面我们看到:

SecurityManager是登录认证,缓存管理和会话管理等的具体实现;filterChainDefinitionMap是资源对应的各种Filter的实现;

资源-角色-权限

Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();// /user/下面的需要ROLE_USER角色filterChainDefinitionMap.put("/user/**", "roles[ROLE_USER]");// /admin/下面的所有需要ROLE_ADMIN的角色才能访问filterChainDefinitionMap.put("/admin/**", "roles[ROLE_ADMIN]");//登录注册不需要认证filterChainDefinitionMap.put("/login", "anon");filterChainDefinitionMap.put("/register", "anon");// 其他资源地址全部需要用户认证才能访问filterChainDefinitionMap.put("/**", "authc");

上面是设置当用户请求某个api的时候,使用对应的filter进行拦截处理,从而判断是否有对应的权限;默认的filter有以下几种:

/** * Enum representing all of the default Shiro Filter instances available to web applications. Each filter instance is * typically accessible in configuration the {@link #name() name} of the enum constant. * * @since 1.0 */public enum DefaultFilter { anon(AnonymousFilter.class), authc(FormAuthenticationFilter.class), authcBasic(BasicHttpAuthenticationFilter.class), logout(LogoutFilter.class), noSessionCreation(NoSessionCreationFilter.class), perms(PermissionsAuthorizationFilter.class), port(PortFilter.class), rest(HttpMethodPermissionFilter.class), roles(RolesAuthorizationFilter.class), ssl(SslFilter.class), user(UserFilter.class);

登录认证,密码加密

因为用户信息和对应的角色权限信息,都是由应用方提供,所以Shiro抽象了一个接口,由应用方去实现这个接口(AuthorizingRealm),有两个方法需要应用方自己去实现:

//用户登录认证protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;//用户权限认证protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);

以下是自己抽象了一个类:

@Slf4jpublic abstract class AbstractAuthorizingRealm extends AuthorizingRealm { public AbstractAuthorizingRealm() { HashedCredentialsMatcher credentialsMatcher = new RetryLimitHashedCredentialsMatcher(EhCacheManagerFactory.getCacheManager()); credentialsMatcher.setHashAlgorithmName(ShiroConstant.hashAlgorithmName); credentialsMatcher.setHashIterations(ShiroConstant.hashIterations);//加密次数 credentialsMatcher.setStoredCredentialsHexEncoded(true); setCredentialsMatcher(credentialsMatcher); } /** * 获取当前用户的角色和权限 */ public abstract RoleAndPermissions getRoleAndPermissionsFromUsername(String username); /** * 获取认证信息 * @param username 用户名 */ public abstract UserAuthInfo getAuthInfoFromUsername(String username); @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { log.info("##################执行Shiro权限认证(默认)##################"); // 获取用户名 String loginName = (String) principals.fromRealm(getName()).iterator().next(); // 判断用户名是否存在 if (loginName == null || loginName.length() == 0) { return null; } // 创建一个授权对象 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //角色和权限设置 RoleAndPermissions rps = getRoleAndPermissionsFromUsername(loginName); if (rps.getPermissions() != null) { info.addStringPermissions(rps.getPermissions()); } if (rps.getRoles() != null) { info.addRoles(rps.getRoles()); } return info; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { log.info("##################执行Shiro登陆认证(默认)##################"); UsernamePasswordToken authenticationToken = (UsernamePasswordToken) token; // 用户名 String username = authenticationToken.getUsername(); if (username != null && !"".equals(username)) { UserAuthInfo userAuthInfo = getAuthInfoFromUsername(username); if (userAuthInfo != null) { Object principal = token.getPrincipal(); // shiro的用户认证对象 return new SimpleAuthenticationInfo(principal, userAuthInfo.getPassword(), ByteSource.Util.bytes(userAuthInfo.getSalt()), getName()); } } return null; }}

业务方只需要继承AbstractAuthorizingRealm,并且实现获取用户信息和角色权限的方法即可。而且还对密码进行了加密处理:

public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher { // 这里使用的是Ehcache对密码重试次数进行缓存 private Cache<String, AtomicInteger> passwordRetryCache; public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager) { passwordRetryCache = cacheManager.getCache("passwordRetryCache"); } @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { String username = (String) token.getPrincipal(); AtomicInteger retryCount = passwordRetryCache.get(username); if (retryCount == null) { retryCount = new AtomicInteger(0); passwordRetryCache.put(username, retryCount); } // 当用户连续输入密码错误5次以上禁止用户登录一段时间 if (retryCount.incrementAndGet() > 5) { throw new ExcessiveAttemptsException(); } boolean match = super.doCredentialsMatch(token, info); if (match) { passwordRetryCache.remove(username); } return match; }}

用户角色和权限放入缓存

用户登录成功后,没访问一个资源的时候不可能都去查一次角色和权限信息,可以将这些信息放入缓存中。

//缓存可以有多种实现,可以是EhCacheManager或RedisCacheManagersecurityManager.setCacheManager(cm);

会话管理

如果使用DefaultWebSecurityManager, 默认会有一个SessionManager

public DefaultWebSecurityManager() { super(); ((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator()); this.sessionMode = HTTP_SESSION_MODE; setSubjectFactory(new DefaultWebSubjectFactory()); setRememberMeManager(new CookieRememberMeManager()); setSessionManager(new ServletContainerSessionManager());}

默认是使用ServletContainerSessionManager和我们平时的Session管理是一致的,客户端通过JSESSIONID和后台的session进行匹配,如果后台服务关闭或挂掉,session信息就丢失了,我们可以自己实现SessionManager,自己去管理Session的创建,销毁等。

@Slf4jpublic class ShiroRedisSessionDao extends AbstractSessionDAO { private RedisCache<String, Object> cache; public ShiroRedisSessionDao(RedisCacheManager redisCacheManager) { this.cache = (RedisCache)redisCacheManager.getCache("session-cache-manager"); } @Override protected Serializable doCreate(Session session) { Serializable sessionId = this.generateSessionId(session); this.assignSessionId(session, sessionId); this.saveSession(session); return sessionId; } @Override protected Session doReadSession(Serializable sessionId) { if(sessionId == null){ log.error("session id is null"); return null; } return (Session)cache.get(sessionId.toString()); } @Override public void update(Session session) throws UnknownSessionException { this.saveSession(session); } @Override public void delete(Session session) { if (session == null || session.getId() == null) { log.error("session or session id is null"); return; } cache.remove(session.getId().toString()); } @Override public Collection<Session> getActiveSessions() { Set<Session> sessions = new HashSet<>(); Set<String> keys = cache.keys(); if(keys != null && keys.size()>0){ for(String key : keys){ Session s = (Session)cache.get(key); sessions.add(s); } } return sessions; } private void saveSession(Session session) throws UnknownSessionException{ if (session == null || session.getId() == null) { log.error("session or session id is null"); return; }// //设置过期时间// long expireTime = 1800000l;// session.setTimeout(expireTime); cache.put(session.getId().toString(), session); }}

上面是继承了AbstractSessionDAO, 使用Redis对Session进行缓存,这样即使后台服务重启了,Session依然可以匹配。

封装

把上面的各部分实现封装到一起就构成了shiro权限管理的核心,业务方只需要实现简单的接口即可。

public abstract class AbstractShiroConfig { /** * 此方法由业务方重写 * @return 具体的Realm */ public abstract AbstractAuthorizingRealm getRealm(); /** * 动态获取角色-资源信息 * @return filterChainDefinitions */ public abstract Map<String, String> loadFilterChainDefinitions() throws Exception; /** * 创建ShiroFilterFactoryBean * 默认使用Ehcache缓存 * @return */ public ShiroFilterFactoryBean createShiroFilterBean() { return createShiroFilterBean(defaultWebSecurityManager(CacheType.EHCACHE), defaultFilterChainMap()); } public ShiroFilterFactoryBean createShiroFilterBean(DefaultWebSecurityManager securityManager, Map<String, String> filterChainDefinitionMap) { return ShiroFilterFactory.create(securityManager, filterChainDefinitionMap, this); } public ShiroFilterFactoryBean createShiroFilterBean(Map<String, String> filterChainDefinitionMap) { return ShiroFilterFactory.create(defaultWebSecurityManager(CacheType.EHCACHE), filterChainDefinitionMap, this); } /*** * 默认的安全管理配置 */ public DefaultWebSecurityManager defaultWebSecurityManager(CacheType cacheType) { return defaultWebSecurityManager(cacheType, null); } public DefaultWebSecurityManager defaultWebSecurityManager(CacheManager cacheManager) { return defaultWebSecurityManager(null, cacheManager); } public DefaultWebSecurityManager defaultWebSecurityManager(CacheType cacheType, CacheManager cacheManager) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //设置realm securityManager.setRealm(getRealm()); // 配置securityManager SecurityUtils.setSecurityManager(securityManager); // 根据情况选择缓存器 CacheManager cm = cacheManager == null ? (cacheType == null ? defaultShiroCacheManager() : getCacheManager(cacheType)) : cacheManager; securityManager.setCacheManager(cm); //设置session manager securityManager.setSessionManager(new ShiroSessionManager()); return securityManager; } /** * shiro缓存:ehcache缓存 (用户认证信息和权限信息等) */ public CacheManager defaultShiroCacheManager() { return EhCacheManagerFactory.getCacheManager(); } /** * redis缓存:redis缓存 (用户认证信息和权限信息等) */ public CacheManager defaultShiroRedisCacheManager() { return new RedisCacheManager(new RedisManager(), "auth"); } private Map<String, String> defaultFilterChainMap() { // 配置拦截地址和拦截器, 使用LinkedHashMap,因为拦截有先后顺序 // authc:所有url都必须认证通过才可以访问; // anon:所有url都都可以匿名访问 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // 以下配置同样可以通过注解@RequiresPermissions("user:edit")来配置访问权限和角色注解@RequiresRoles(value={"ROLE_USER"})方式定义// filterChainDefinitionMap.put("/user/**", "roles[ROLE_USER]");// /user/下面的需要ROLE_USER角色或者query权限才能访问// filterChainDefinitionMap.put("/admin/**", "roles[ROLE_ADMIN]");// /admin/下面的所有需要ROLE_ADMIN的角色才能访问 //登录注册不需要认证 filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/register", "anon"); // 其他资源地址全部需要用户认证才能访问 filterChainDefinitionMap.put("/**", "authc"); return filterChainDefinitionMap; } /** * 根据缓存类型选择对应的缓存管理器 * @param cacheType 缓存类型 * @return */ private CacheManager getCacheManager(CacheType cacheType) { CacheManager cacheManager = defaultShiroCacheManager(); if (cacheType == CacheType.REDIS) { cacheManager = defaultShiroRedisCacheManager(); } return cacheManager; }}

业务方只需要继承AbstractShiroConfig即可

@Configurationpublic class ShiroConfig extends AbstractShiroConfig {  @Bean public ShiroFilterFactoryBean filterFactoryBean() throws Exception { return createShiroFilterBean(); } @Override public AbstractAuthorizingRealm getRealm() { return new AbstractAuthorizingRealm() { @Override public RoleAndPermissions getRoleAndPermissionsFromUsername(String s) { User user = null; try { user = userService.findByUsername(s); if (user != null) { List<String> roles = new ArrayList<>(); List<String> permissions = new ArrayList<>(); if (user.getRoleIds()!=null) { user.getRoleIds().forEach(roleId -> { try { Role role = roleService.findByRoleId(roleId); if (role != null) { roles.add(role.getRoleName()); if (role.getResIds() != null) { role.getResIds().forEach(resId -> { try { permissions.add(resMapper.findById(resId).getUrl()); } catch (Exception e) { e.printStackTrace(); } }); } } } catch (Exception e) { e.printStackTrace(); } }); } return new RoleAndPermissions(roles, permissions); } } catch (Exception e) { e.printStackTrace(); } return null; } @Override public UserAuthInfo getAuthInfoFromUsername(String s) { try { User user = userService.findByUsername(s); if (user != null) { return new UserAuthInfo(user.getLoginname(), user.getPassword(), user.getSalt()); } } catch (Exception e) { e.printStackTrace(); } return null; } }; } }

标签: #java权限管理shiro