9. SpringCloud之权限校验方案
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了9. SpringCloud之权限校验方案相关的知识,希望对你有一定的参考价值。
参考技术A 1、保存用户信息 到 服务器内存,用于会话保持2、jsessionId存储到浏览器的cookie里,登录后每次访问都要 带上这个,与用户信息绑定,如果jsessionId被人盗取,容易遭受csrf 跨域攻击。
3、如果登录用户过多, 服务内保存的session信息 占用内存较大
由于项目组件的变大,往往会把 应用部署多份,再用nginx 做负载均衡,提高系统的并发量。
这时 由服务器来 存储session 就会出现出现问题:
接下来就引入了 分布式session的 方案:
不讲 session信息存储在 服务器内部,引入第三方中间件如redis,mongodb,将session统一放到 第三方中间件这里,然后 再统一从 这取。
因为在微服务应用中,网关是 系统 所有流量的入口。在网关中 进行权限校验,理论上是可行的。
当请求携带登录返回的token时, 请求网关, 由网关来 向认证服务器 发起 校验token 的请求。
但是这样还有一个很重要的问题, 就是下游服务 不做权限校验的话,那么 下游服务 的接口 就完全相当于裸奔的, 别人知道下游服务的接口地址,就可以直接调用, 非常的危险。
所以,在这种方案中,一般 还会加上ip白名单校验, 下游服务 只允许 网关 发过来的请求,其他 来源不明的请求,直接在防火墙 拦截调。以保证, 下游服务的接口 只能网关转发过来并且 是 网关鉴权通过的。
理论上 网关 服务器是不需要进行权限校验的,因为 zuul 服务器没有接口, 不需要从 网关 调用业务接口,网关 只做简单的路由工作。下游系统在获取到 token 后,通过 过滤器把 token 发到认证服务器校验该 token 是否有效,如果认证服务器校验通过就会携带 这个 token 相关的验证信息传回给下游系统,下游系统根据这个返回结果就知道该 token 具 有的权限是什么了。
这种方式 不会有 接口裸奔的风险, 也不用加ip白名单校验,更可以 进行方法级别的校验,粒度更细。
不管 是只在网关层做权限校验,还是只在下游服务做权限校验,在oauth2.0的权限校验模型中, 所有 校验token的过程,都需要与 认证服务器 进行一次 交互, 这点 不太好,多了一次 网络请求。
权限控制方案之——集成shiro
概述:
上一篇文章中我们介绍的权限控制方案的实现方式是通过URL拦截实现的,这里介绍通过shiro实现权限控制。shiro是apache下的开源的权限管理框架。
Shiro架构:
Subject:主体,是用户和程序的统一抽象。
SecurityManager:安全管理器,认证和授权的核心处理类。
Authenticator:认证器,主体认证的处理类。
Authorizer:授权器,主体授权的处理类。
SessionManager:shiro提供的一套session管理机制。
SessionDao:对于session的存储需要用到sessionDao。
CacheManager;缓存管理器,主要对session和相关数据进行缓存管理。
Realm:通过realm间接连接数据源,认证和授权需要通过realm存取认证和授权数据。
Cryptography:密码管理器,shiro提供的一套加密解密组建。
整合shiro
实现方式:
1、通过shiro默认的IniRealm实现(开发中一般不用该方式),该方式需要将认证和授权信息配置在一个ini文件中,shiro会默认通过IniRealm读取该文件信息,实现认证。
2、通过自定义Realm实现(一般都需要自定义Realm),自定义的Realm可以自定义认证和授权信息获取的方式(例如从数据库中查询),自定义的Realm继承AuthorizingRealm。
这里主要介绍通过自定义Realm实现认证和授权的方式。下面通过将shiro整合到项目中的过程来介绍shiro的基本用法和shiro的工作原理。
整合过程:
1、添加jar包
主要包括:shiro和spring整合的jar包,shiro和web应用整合的jar包和shiro一些基本的包。
2、配置shiro的filter
上篇文章我们介绍通过URL拦截中用到了拦截器,shiro内部也是用到了filter拦截进行实现。
配置信息:
<!-- 将shiroFilter作为spring的Bean,由Spring来管理该Filter,实现和其他Filter的统一 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!-- 设置true表示由servlet容器控制filter的生命周期 -->
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<!-- 设置spring容器filter指定的Bean的id,如果不设置则找与filter-name一致的bean-->
<init-param>
<param-name>targetBeanName</param-name>
<param-value>shiroFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3、applicationContext-shiro.xml
在applicationContext-shiro.xml中配置shiroFilter需要的Bean及其他相关配置。
<!-- web.xml中shiro的filter对应的bean -->
<!-- Shiro 的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 -->
<property name="loginUrl" value="/login.action" />
<!-- 认证成功跳转到first.action-->
<property name="successUrl" value="/first.action"/>
<!-- 指定没有权限操作时跳转页面-->
<property name="unauthorizedUrl" value="/refuse.jsp" />
<!-- 自定义filter配置 -->
<property name="filters">
<map>
<!-- 将自定义 的FormAuthenticationFilter注入shiroFilter中-->
<entry key="authc" value-ref="formAuthenticationFilter" />
</map>
</property>
<!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 -->
<property name="filterChainDefinitions">
<value>
<!-- 对静态资源设置匿名访问 -->
/images/** = anon
/js/** = anon
/styles/** = anon
/validatecode.jsp = anon
<!-- 只需请求logout.action地址即可清除session-->
/logout.action = logout
<!--授权配置,例queryItems.action请求需要item:query权限-->
/items/queryItems.action = perms[item:query]
/items/editItems.action = perms[item:edit]
<!-- /** = authc 所有url都必须认证通过才可以访问-->
/** = authc
</value>
</property>
</bean>
<!-- securityManager属性配置 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="customRealm" />
<!-- 注入缓存管理器 -->
<property name="cacheManager" ref="cacheManager"/>
<!-- 注入session管理器 -->
<property name="sessionManager" ref="sessionManager" />
</bean>
<!-- 自定义的realm -->
<bean id="customRealm" class="cn.itcast.ssm.shiro.CustomRealm">
<!-- 将凭证匹配器设置到realm中,realm按照凭证匹配器的要求进行散列 -->
<property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>
<!-- 凭证匹配器,后边有介绍 -->
<bean id="credentialsMatcher"
class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5" />
<property name="hashIterations" value="1" />
</bean>
<!-- 缓存管理器,后边将会介绍关于引入ehcache缓存 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
</bean>
<!--session管理器,让shiro对session管理-->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- session的失效时长,单位毫秒 -->
<property name="globalSessionTimeout" value="600000"/>
<!-- 删除失效的session -->
<property name="deleteInvalidSessions" value="true"/>
</bean>
<!-- 自定义form认证过虑器 -->
<!-- 基于Form表单的身份验证过滤器,不配置将采用默认值,默认值为username和password -->
<bean id="formAuthenticationFilter"
class="cn.itcast.ssm.shiro.CustomFormAuthenticationFilter ">
<!-- 表单中账号的input名称 -->
<property name="usernameParam" value="username" />
<!-- 表单中密码的input名称 -->
<property name="passwordParam" value="password" />
</bean>
4、认证:
实现原理:由上边的配置可知,当用户进行认证时,会去请求loginurl进行认证,中间FormAuthenticationFilter进行拦截,取出request中携带的username和password参数(注:这里默认为username和password,也可以自行配置),FormAuthenticationFilter会调用realm(自定义的吗?)进行认证,同时传给realm一个token参数,username和password封装在token中,realm会根据username查询用户信息,如果查询到则返回,如果查询不到则会返回null,这时FormAuthenticationFilter会向request中设置异常参数和信息。
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException
//token中封装了用户输入的用户名和密码,从token中取出用户名
String userCode = (String) token.getPrincipal();
//根据用户输入的userCode从数据库查询
SysUser sysUser = null;
try
sysUser = sysService.findSysUserByUserCode(userCode);
catch (Exception e1)
e1.printStackTrace();
// 如果查询不到返回null
if(sysUser==null)
//此处返回null,ModularRealmAuthenticator抛出UnknownAccountException(用户不存在)异常
return null;
//获取用户散列密码
String password = sysUser.getPassword();
//获取盐
String salt = sysUser.getSalt();
ActiveUser activeUser = new ActiveUser();
activeUser.setUserid(sysUser.getId());
activeUser.setUsercode(sysUser.getUsercode());
activeUser.setUsername(sysUser.getUsername());
List<SysPermission> menus = null;
try
//根据用户id取出菜单
menus = sysService.findMenuListByUserId(sysUser.getId());
catch (Exception e)
e.printStackTrace();
//用户菜单设置到activeUser
activeUser.setMenus(menus);
//将activeUser设置到simpleAuthenticationInfo返回
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
activeUser, password,ByteSource.Util.bytes(salt), this.getName());
return simpleAuthenticationInfo;
5、登出
对于登出可以不用去实现具体的退出操作,只需要访问一个指定的登出的url,因为中间会由LogoutFilter拦截,同时清除session数据。配置方式见applicationContext-shiro.xml中的logout配置
6、授权配置
说明:对于shiro的授权配置有三种方法:
1、通过配置文件进行配置。
2、通过注解进行配置。
3、通过jsp标签进行配置。
对于配置文件的配置方式见applicationContext-shiro.xml中的授权配置部分。
实现原理为:在用户请求到/users/deleteUser.action时会被PermissionsAuthorizationFilter拦截,filter根据配置发现需要“item:query”权限,这时PermissionAuthorizationFilter会调用realm从数据库中获取权限信息,如果发现用户拥有该权限会放行,如果发现没有会转到前边配置中指定的refuse.jsp页面。
7、权限判断
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals)
//获取身份信息
ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();
List<SysPermission> permissionList = null;
try
//获取用户拥有的权限信息
permissionList = sysService.findPermissionListByUserId(activeUser.getUserid());
catch (Exception e)
e.printStackTrace();
List<String> permissions = new ArrayList<String>();
if(permissionList!=null)
for(SysPermission sysPermission:permissionList)
permissions.add(sysPermission.getPercode());
//返回授权信息,将查询到的权限信息设置到simpleAuthorizationInfo中
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addStringPermissions(permissions);
return simpleAuthorizationInfo;
8、设置凭证匹配器
由于在数据库中存放的密码是经过加密算法的散列值,所以在shiro也需要对取到的密码进行相同的加密算法才能进行密码比对,这里通过在配置文件中配置的凭证匹配器对加密算法进行配置,对于凭证匹配器的配置见applicationContext-shiro.xml。
上边对于授权的方式我们使用的是通过配置文件进行实现的,但是这样的缺点在,URL和对应的权限成对配置,这样将造成很大的配置量,相对来说比较麻烦;下边我们说明一下如何通过注解的方式进行配置。
整合改进
注解方式实现授权:
上边介绍了对于shiro的授权配置有三种方式,上边的方式是通过配置文件的方式实现的,下边介绍一下如何通过注解方式和jsp标签方式进行授权配置。
开启注解:
由于注解方式内部使用了spring的AOP方式,所以需要首先开启spring的AOP,同时开启shiro的注解支持。配置如下:
<!-- 开启aop,对类代理 -->
<aop:config proxy-target-class="true"></aop:config>
<!-- 开启shiro注解支持 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
注解使用:
注解的使用方式为,在需要进行权限控制的方法上添加相应注解,注解的写法为:
@RequiresPermissions(“users:delete”),表示执行此方法需要“users:delete”权限。
Jsp标签实现授权:
对于通过jsp标签实现授权需要引入shiro的自定义标签
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>
标签使用:shiro提供的标签很多,这里介绍其中几个:
1、认证成功后可以显示的内容放在<shiro:authenticated>内。
2、当用户有指定角色时才显示的内容放在<shiro:hasRole name="角色名"> 内
3、当用户有指定的权限时才显示的内容放在<shiro:hasPermission name="item:query">内
在实际应用当中,通过注解和jsp标签的方式相对多些。
引入缓存:
对于上边的使用,无论是通过何种方式配置的权限,存在一个较大的问题是,对于权限的判断,每次都需要realm从数据库中查询权限信息进行比对,这样对系统的性能消耗是很大的,为了解决该问题,我们为shiro引入缓存处理,这样就只需要在用户第一次访问时去查询数据库,再后边就可以直接从缓存中取,这里使用的是ehcache。
1、添加ehcache的jar包。
2、配置cacheManager,需要将cacheManager注入到securityManager中,对于相关配置见applicationContext-shiro.xml。
3、创建shiro-ehcache.xml,定义ehcache的使用方案,具体配置如下:
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!--diskStore:缓存数据持久化的目录地址 -->
<diskStore path="F:\\develop\\ehcache" />
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
其他
shiro提供了在用户退出后缓存的自动清空处理,然而,在用户登录期间对用户的缓存进行更新处理,需要调用realm的clearCached方法对缓存进行清空。
总结:
对于利用shiro对权限的控制和前边基于URL拦截的控制进行对比来说,shiro的控制功能更加强大一些,但是对于框架的依赖性大些,虽然shiro本身是不依赖与其他框架的,但是这里和项目整合还是依赖了spring框架,在使用shiro时如果使用注解的方式进行权限控制会更加方便和直观。这里主要介绍了shiro和项目整合中的基本用法,其中对于认证和授权的流程在后边进行介绍,这些只是shiro的一些基本用法,对于shiro的其他应用特点还有待继续了解和学习,也希望这篇文章可以对你有所帮助吧。
以上是关于9. SpringCloud之权限校验方案的主要内容,如果未能解决你的问题,请参考以下文章
微服务权限终极解决方案(spring-cloud-gateway-oauth2)