版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
微服务架构中的认证权限控制设计
第一节引言:本文系《认证鉴权与API权限控制在微服务架构中的设计与实现》系列的第一篇,本系列预计四篇文章讲解微服务下的认证鉴权与API权限控制的实现。1.背景最近在做权限相关服务的开发,在系统微服务化后,原有的单体应用是基于Session的安全权限方式,不能满足现有的微服务架构的认证与鉴权需求。微服务架构下,一个应用会被拆分成若干个微应用,每个微应用都需要对访问进行鉴权,每个微应用都需要明确当前访问用户以及其权限。尤其当访问来源不只是浏览器,还包括其他服务的调用时,单体应用架构下的鉴权方式就不是特别合适了。在微服务架构下,要考虑外部应用接入的场景、用户–服务的鉴权、服务–服务的鉴权等多种鉴权场景。比如用户A访问UserService,A如果未登录,则首先需要登录,请求获取授权token。获取token之后,A将携带着token去请求访问某个文件,这样就需要对A的身份进行校验,并且A可以访问该文件。为了适应架构的变化、需求的变化,auth权限模块被单独出来作为一个基础的微服务系统,为其他业务service提供服务。2.系统架构的变更单体应用架构到分布式架构,简化的权限部分变化如下面两图所示。(1)单体应用简化版架构图:
(2)分布式应用简化版架构图:
分布式架构,特别是微服务架构的优点是可以清晰的划分出业务逻辑来,让每个微服务承担职责单一的功能,毕竟越简单的东西越稳定。但是,微服务也带来了很多的问题。比如完成一个业务操作,需要跨很多个微服务的调用,那么如何用权限系统去控制用户对不同微服务的调用,对我们来说是个挑战。当业务微服务的调用接入权限系统后,不能拖累它们的吞吐量,当权限系统出现问题后,不能阻塞它们的业务调用进度,当然更不能改变业务逻辑。新的业务微服务快速接入权限系统相对容易把控,那么对于公司已有的微服务,如何能不改动它们的架构方式的前提下,快速接入,对我们来说,也是一大挑战。3.技术方案这主要包括两方面需求:其一是认证与鉴权,对于请求的用户身份的授权以及合法性鉴权;其二是API级别的操作权限控制,这个在第一点之后,当鉴定完用户身份合法之后,对于该用户的某个具体请求是否具有该操作执行权限进行校验。3.1认证与鉴权对于第一个需求,笔者调查了一些实现方案:分布式Session方案分布式会话方案原理主要是将关于用户认证的信息存储在共享存储中,且通常由用户会话作为key来实现的简单分布式哈希映射。当用户访问微服务时,用户数据可以从共享存储中获取。在某些场景下,这种方案很不错,用户登录状态是不透明的。同时也是一个高可用且可扩展的解决方案。这种方案的缺点在于共享存储需要一定保护机制,因此需要通过安全链接来访问,这时解决方案的实现就通常具有相当高的复杂性了。基于OAuth2Token方案随着RestfulAPI、微服务的兴起,基于Token的认证现在已经越来越普遍。Token和SessionID不同,并非只是一个key。Token一般会包含用户的相关信息,通过验证Token就可以完成身份校验。用户输入登录信息,发送到身份认证服务进行认证。AuthorizationServer验证登录信息是否正确,返回用户基础信息、权限范围、有效时间等信息,客户端存储接口。用户将Token放在HTTP请求头中,发起相关API调用。被调用的微服务,验证Token。ResourceServer返回相关资源和数据。这边选用了第二种方案,基于OAuth2Token认证的好处如下:服务端无状态:Token机制在服务端不需要存储Session信息,因为Token自身包含了所有用户的相关信息。性能较好,因为在验证Token时不用再去访问数据库或者远程服务进行权限校验,自然可以提升不少性能。现在很多应用都是同时面向移动端和Web端,OAuth2Token机制可以支持移动设备。最后一点,也是挺重要的,OAuth2与SpringSecurity结合使用,SpringSecurityOAuth2的文档写得较为详细。OAuth2根据使用场景不同,分成了4种模式:授权码模式(authorizationcode)简化模式(implicit)密码模式(resourceownerpasswordcredentials)客户端模式(clientcredentials)对于上述OAuth2四种模式不熟的同学,可以自行百度OAuth2,阮一峰的文章有解释。常使用的是password模式和client模式。3.2操作权限控制对于第二个需求,笔者主要看了SpringSecurity和Shiro。ShiroShiro是一个强大而灵活的开源安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加密。Shiro很容易入手,上手快控制粒度可糙可细。自由度高,Shiro既能配合Spring使用也可以单独使用。SpringSecuritySpring社区生态很强大。除了不能脱离Spring,SpringSecurity具有Shiro所有的功能。而且SpringSecurity对Oauth、OpenID也有支持,Shiro则需要自己手动实现。SpringSecurity的权限细粒度更高。但是SpringSecurity太过复杂。看了下网上的评论,貌似一边倒向Shiro。大部分人提出的SpringSecurity问题就是比较复杂难懂,文档太长。笔者综合评估了下复杂性与所要实现的权限需求,以及上一个需求调研的结果,最终选择了SpringSecurity。4.系统架构4.1组件Auth系统的最终使用组件如下:OAuth2.0
JWT
TokenSpring
SecuritySpring
boot4.2步骤主要步骤为:配置资源服务器和认证服务器配置SpringSecurity上述步骤比较笼统,对于前面小节提到的需求,属于Auth系统的主要内容,笔者后面会另写文章对应讲解。4.3Endpoint提供的Endpoint:/oauth/token?grant_type=password
#请求授权token/oauth/token?grant_type=refresh_token
#刷新token/oauth/check_token
#校验token/logout
#注销token及权限相关信息4.4Maven依赖主要的jar包,pom.xml文件如下:<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
<version>1.2.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>1.2.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
<version>1.5.3.RELEASE</version>
</dependency>4.5AuthorizationServer配置文件AuthorizationServer配置主要是覆写如下的三个方法,分别针对Endpoints、Clients、Security配置。@Overridepublic
void
configure(AuthorizationServerSecurityConfigurer
security)
throws
Exception
{
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");}@Overridepublic
void
configure(ClientDetailsServiceConfigurer
clients)
throws
Exception
{//配置客户端认证
clients.withClientDetails(clientDetailsService(dataSource));}@Overridepublic
void
configure(AuthorizationServerEndpointsConfigurer
endpoints)
throws
Exception
{
//配置token的数据源、自定义的tokenServices等信息
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore(dataSource))
.tokenServices(authorizationServerTokenServices())
.accessTokenConverter(accessTokenConverter())
.exceptionTranslator(webResponseExceptionTranslator);}4.6ResourceServer配置资源服务器的配置,覆写了默认的配置。为了支持logout,这边自定义了一个CustomLogoutHandler并且将logoutSuccessHandler指定为返回http状态的HttpStatusReturningLogoutSuccessHandler。@Overridepublic
void
configure(HttpSecurity
http)
throws
Exception
{
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.requestMatchers().antMatchers("/**")
.and().authorizeRequests()
.antMatchers("/**").permitAll()
.anyRequest().authenticated()
.and().logout()
.logoutUrl("/logout")
.clearAuthentication(true)
.logoutSuccessHandler(new
HttpStatusReturningLogoutSuccessHandler())
.addLogoutHandler(customLogoutHandler());4.7执行Endpoint1、首先执行获取授权的Endpoint。method:
post
url:
http://localhost:12000/oauth/token?grant_type=passwordheader:{Authorization:
Basic
ZnJvbnRlbmQ6ZnJvbnRlbmQ=,Content-Type:
application/x-www-form-urlencoded}body:{username:
keets,password:
***}上述构造了一个post请求,具体请求写得很详细。username和password是客户端提供给服务器进行校验用户身份信息。header里面的Authorization是存放的clientId和clientSecret经过编码的字符串。返回结果如下:{
"access_token":
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsImV4cCI6MTUwODQ0Nzc1NiwidXNlcl9uYW1lIjoia2VldHMiLCJqdGkiOiJiYWQ3MmIxOS1kOWYzLTQ5MDItYWZmYS0wNDMwZTdkYjc5ZWQiLCJjbGllbnRfaWQiOiJmcm9udGVuZCIsInNjb3BlIjpbImFsbCJdfQ.5ZNVN8TLavgpWy8KZQKArcbj7ItJLLaY1zBRaAgMjdo",
"token_type":
"bearer","refresh_token":
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsInVzZXJfbmFtZSI6ImtlZXRzIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImJhZDcyYjE5LWQ5ZjMtNDkwMi1hZmZhLTA0MzBlN2RiNzllZCIsImV4cCI6MTUxMDk5NjU1NiwianRpIjoiYWE0MWY1MjctODE3YS00N2UyLWFhOTgtZjNlMDZmNmY0NTZlIiwiY2xpZW50X2lkIjoiZnJvbnRlbmQifQ.mICT1-lxOAqOU9M-Ud7wZBb4tTux6OQWouQJ2nn1DeE","expires_in":
43195,"scope":
"all","X-KEETS-UserId":
"d6448c24-3c4c-4b80-8372-c2d61868f8c6","jti":
"bad72b19-d9f3-4902-affa-0430e7db79ed","X-KEETS-ClientId":
"frontend"}可以看到在用户名密码通过校验后,客户端收到了授权服务器的response,主要包括accesstoken、refreshtoken。并且表明token的类型为bearer,过期时间expires_in。笔者在jwttoken中加入了自定义的info为UserId和ClientId。2、鉴权的Endpointmethod:
post
url:
http://localhost:12000/oauth/check_tokenheader:{Authorization:
Basic
ZnJvbnRlbmQ6ZnJvbnRlbmQ=,Content-Type:
application/x-www-form-urlencoded}body:{token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsImV4cCI6MTUwODQ0Nzc1NiwidXNlcl9uYW1lIjoia2VldHMiLCJqdGkiOiJiYWQ3MmIxOS1kOWYzLTQ5MDItYWZmYS0wNDMwZTdkYjc5ZWQiLCJjbGllbnRfaWQiOiJmcm9udGVuZCIsInNjb3BlIjpbImFsbCJdfQ.5ZNVN8TLavgpWy8KZQKArcbj7ItJLLaY1zBRaAgMjdo}上面即为check_token请求的详细信息。需要注意的是,笔者将刚刚授权的token放在了body里面,这边可以有多种方法,此处不扩展。{"X-KEETS-UserId":
"d6448c24-3c4c-4b80-8372-c2d61868f8c6","user_name":
"keets","scope":
[
"all"],"active":
true,"exp":
1508447756,"X-KEETS-ClientId":
"frontend","jti":
"bad72b19-d9f3-4902-affa-0430e7db79ed","client_id":
"frontend"}校验token合法后,返回的response如上所示。在response中也是展示了相应的token中的基本信息。3、刷新token由于token的时效一般不会很长,而refreshtoken一般周期会很长,为了不影响用户的体验,可以使用refreshtoken去动态的刷新token。method:
post
url:
http://localhost:12000/oauth/token?grant_type=refresh_token&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsInVzZXJfbmFtZSI6ImtlZXRzIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImJhZDcyYjE5LWQ5ZjMtNDkwMi1hZmZhLTA0MzBlN2RiNzllZCIsImV4cCI6MTUxMDk5NjU1NiwianRpIjoiYWE0MWY1MjctODE3YS00N2UyLWFhOTgtZjNlMDZmNmY0NTZlIiwiY2xpZW50X2lkIjoiZnJvbnRlbmQifQ.mICT1-lxOAqOU9M-Ud7wZBb4tTux6OQWouQJ2nn1DeEheader:{Authorization:
Basic
ZnJvbnRlbmQ6ZnJvbnRlbmQ=}其response和/oauth/token得到正常的相应是一样的,此处不再列出。4、注销tokenmethod:
geturl:
http://localhost:9000/logoutheader:{Authorization:
Basic
ZnJvbnRlbmQ6ZnJvbnRlbmQ=}注销成功则会返回200,注销端点主要是将token和SecurityContextHolder进行清空。5.总结本文是《认证鉴权与API权限控制在微服务架构中的设计与实现》系列文章的总述,从遇到的问题着手,介绍了项目的背景。通过调研现有的技术,并结合当前项目的实际,确定了技术选型。最后对于系统的最终的实现进行展示。后面将从实现的细节,讲解本系统的实现。敬请期待后续文章。
第二节引言:本文系《认证鉴权与API权限控制在微服务架构中的设计与实现》系列的第二篇,本文重点讲解用户身份的认证与token发放的具体实现。1.系统概览在上一篇《认证鉴权与API权限控制在微服务架构中的设计与实现(一)》介绍了该项目的背景以及技术调研与最后选型,并且对于最终实现的Endpoint执行结果进行展示。对系统架构虽然有提到,但是并未列出详细流程图。在笔者的应用场景中,Auth系统与网关进行结合。在网关出配置相应的端点信息,如登录系统申请token授权,校验check_token等端点。下图为网关与Auth系统结合的流程图,网关系统的具体实现细节在后面另写文章介绍。(此处流程图的绘制中,笔者使用极简的语言描述,各位同学轻喷!)授权流程图上图展示了系统登录的简单流程,其中的细节有省略,用户信息的合法性校验实际是调用用户系统。大体流程是这样,客户端请求到达网关之后,根据网关识别的请求登录端点,转发到Auth系统,将用户的信息进行校验。另一方面是对于一般请求的校验。一些不需要权限的公开接口,在网关处配置好,请求到达网关后,匹配了路径将会直接放行。如果需要对该请求进行校验,会将该请求的相关验证信息截取,以及API权限校验所需的上下文信息(笔者项目对于一些操作进行权限前置验证,下一盘文章会讲到),调用Auth系统,校验成功后进行路由转发。身份及API权限校验的流程图这篇文章就重点讲解我们在第一篇文章中提到的用户身份的认证与token发放。这个也主要包含两个方面:用户合法性的认证获取到授权的token2.配置与类图2.1AuthorizationServer主要配置关于AuthorizationServer和ResourceServer的配置在上一篇文章已经列出。AuthorizationServer主要是继承了AuthorizationServerConfigurerAdapter,覆写了其实现接口的三个方法://对应于配置AuthorizationServer安全认证的相关信息,创建ClientCredentialsTokenEndpointFilter核心过滤器@Overridepublic
void
configure(AuthorizationServerSecurityConfigurer
security)
throws
Exception
{
}//配置OAuth2的客户端相关信息@Overridepublic
void
configure(ClientDetailsServiceConfigurer
clients)
throws
Exception
{}//配置身份认证器,配置认证方式,TokenStore,TokenGranter,OAuth2RequestFactory@Overridepublic
void
configure(AuthorizationServerEndpointsConfigurer
endpoints)
throws
Exception
{}2.2主要Authentication类的类图AuthorizationServerUML类图主要的验证方法authenticate(Authenticationauthentication)在接口AuthenticationManager中,其实现类有ProviderManager,有上图可以看出ProviderManager又依赖于AuthenticationProvider接口,其定义了一个List<AuthenticationProvider>全局变量。笔者这边实现了该接口的实现类CustomAuthenticationProvider。自定义一个provider,并在GlobalAuthenticationConfigurerAdapter中配置好改自定义的校验provider,覆写configure()方法。@Configurationpublic
class
AuthenticationManagerConfig
extends
GlobalAuthenticationConfigurerAdapter
{@AutowiredCustomAuthenticationProvider
customAuthenticationProvider;@Overridepublic
void
configure(AuthenticationManagerBuilder
auth)
throws
Exception
{
auth.authenticationProvider(customAuthenticationProvider);//使用自定义的AuthenticationProvider}}AuthenticationManagerBuilder是用来创建AuthenticationManager,允许自定义提供多种方式的AuthenticationProvider,比如LDAP、基于JDBC等等。3.认证与授权token下面讲解认证与授权token主要的类与接口。3.1自定义的验证类CustomAuthenticationProviderCustomAuthenticationProvider中定义了验证方法的具体实现。其具体实现如下所示。//主要的自定义验证方法@Overridepublic
Authentication
authenticate(Authentication
authentication)
throws
AuthenticationException
{
String
username
=
authentication.getName();
String
password
=
(String)
authentication.getCredentials();
Map
data
=
(Map)
authentication.getDetails();
String
clientId
=
(String)
data.get("client");
Assert.hasText(clientId,"clientId
must
have
value"
);
String
type
=
(String)
data.get("type");
//通过调用user服务,校验用户信息
Map
map
=
userClient.checkUsernameAndPassword(getUserServicePostObject(username,
password,
type));
//校验返回的信息,不正确则抛出异常,授权失败
String
userId
=
(String)
map.get("userId");
if
(StringUtils.isBlank(userId))
{
String
errorCode
=
(String)
map.get("code");
throw
new
BadCredentialsException(errorCode);
}
CustomUserDetails
customUserDetails
=
buildCustomUserDetails(username,
password,
userId,
clientId);
return
new
CustomAuthenticationToken(customUserDetails);}//构造一个CustomUserDetails,简单,略去private
CustomUserDetails
buildCustomUserDetails(String
username,
String
password,
String
userId,
String
clientId)
{}//构造一个请求userService的map,内容略private
Map<String,
String>
getUserServicePostObject(String
username,
String
password,
String
type)
{}authenticate()最后返回构造的自定义CustomAuthenticationToken,在CustomAuthenticationToken中,将booleanauthenticated设为true,user信息验证成功。这边传入的参数CustomUserDetails与token生成有关,作为payload中的信息,下面会讲到。//继承抽象类AbstractAuthenticationTokenpublic
class
CustomAuthenticationToken
extends
AbstractAuthenticationToken
{private
CustomUserDetails
userDetails;public
CustomAuthenticationToken(CustomUserDetails
userDetails)
{
super(null);
this.userDetails
=
userDetails;
super.setAuthenticated(true);}...}而AbstractAuthenticationToken实现了接口Authentication和CredentialsContainer,里面的具体信息读者可以自己看下源码。3.2关于JWT用户信息校验完成之后,下一步则是要对该用户进行授权。在讲具体的授权之前,先补充下关于JWTToken的相关知识点。Jsonwebtoken(JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC7519)。该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。从上面的描述可知JWT的定义,这边读者可以对比下token的认证和传统的session认证的区别。推荐一篇文章《什么是JWT–JSONWEBTOKEN》,笔者这边就不详细扩展讲了,只是简单介绍下其构成。JWT包含三部分:header头部、payload信息、signature签名。下面以上一篇生成好的access_token为例介绍。headerJWT的头部承载两部分信息,一是声明类型,这里是JWT;二是声明加密的算法通常直接使用HMACSHA256。第一部分一般固定为:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9playload存放的有效信息,这些有效信息包含三个部分、标准中注册的声明、公共的声明、私有的声明。这边笔者额外添加的信息为X-KEETS-UserId和X-KEETS-ClientId。读者可根据实际项目需要进行定制。最后playload经过base64编码后的结果为:eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsImV4cCI6MTUwODQ0Nzc1NiwidXNlcl9uYW1lIjoia2VldHMiLCJqdGkiOiJiYWQ3MmIxOS1kOWYzLTQ5MDItYWZmYS0wNDMwZTdkYjc5ZWQiLCJjbGllbnRfaWQiOiJmcm9udGVuZCIsInNjb3BlIjpbImFsbCJdfQsignatureJWT的第三部分是一个签证信息,这个签证信息由三部分组成:header(base64后的)、payload(base64后的)、secret。关于secret,细心的读者可能会发现之前的配置里面有具体设置。前两部分连接组成的字符串,通过header中声明的加密方式进行加盐secret组合加密,然后就构成了JWT的第三部分。第三部分结果为:5ZNVN8TLavgpWy8KZQKArcbj7ItJLLaY1zBRaAgMjdo至于具体应用方法,可以参见第一篇文章中构建的/logout端点。3.3自定义的AuthorizationTokenServices现在到了为用户创建token,这边主要与自定义的接口AuthorizationServerTokenServices有关。AuthorizationServerTokenServices主要有如下三个方法://创建tokenOAuth2AccessToken
createAccessToken(OAuth2Authentication
authentication)
throws
AuthenticationException;//刷新tokenOAuth2AccessToken
refreshAccessToken(String
refreshToken,
TokenRequest
tokenRequest)
throws
AuthenticationException;//获取tokenOAuth2AccessToken
getAccessToken(OAuth2Authentication
authentication)由于篇幅限制,笔者这边仅对createAccessToken()的实现方法进行分析,其他的方法实现,读者可以下关注笔者的GitHub项目。public
class
CustomAuthorizationTokenServices
implements
AuthorizationServerTokenServices,
ConsumerTokenServices
{...public
OAuth2AccessToken
createAccessToken(OAuth2Authentication
authentication)
throws
AuthenticationException
{
//通过TokenStore,获取现存的AccessToken
OAuth2AccessToken
existingAccessToken
=
tokenStore.getAccessToken(authentication);
OAuth2RefreshToken
refreshToken;
//移除已有的AccessToken和refreshToken
if
(existingAccessToken
!=
null)
{
if
(existingAccessToken.getRefreshToken()
!=
null)
{
refreshToken
=
existingAccessToken.getRefreshToken();
//
The
token
store
could
remove
the
refresh
token
when
the
//
access
token
is
removed,
but
we
want
to
be
sure
tokenStore.removeRefreshToken(refreshToken);
}
tokenStore.removeAccessToken(existingAccessToken);
}
//recreate
a
refreshToken
refreshToken
=
createRefreshToken(authentication);
OAuth2AccessToken
accessToken
=
createAccessToken(authentication,
refreshToken);
if
(accessToken
!=
null)
{
tokenStore.storeAccessToken(accessToken,
authentication);
}
refreshToken
=
accessToken.getRefreshToken();
if
(refreshToken
!=
null)
{
tokenStore.storeRefreshToken(refreshToken,
authentication);
}
return
accessToken;}...}这边具体的实现在上面有注释,基本没有改写多少,读者此处可以参阅源码。createAccessToken()还调用了两个私有方法,分别创建accessToken和refreshToken。创建accessToken,需要基于refreshToken。此处可以自定义设置token的时效长度,accessToken创建实现如下:private
int
refreshTokenValiditySeconds
=
60
*
60
*
24
*
30;
//
default
30
days.private
int
accessTokenValiditySeconds
=
60
*
60
*
12;
//
default
12
hours.private
OAuth2AccessToken
createAccessToken(OAuth2Authentication
authentication,
OAuth2RefreshToken
refreshToken)
{//对应tokenId,存储的标识
DefaultOAuth2AccessToken
token
=
new
DefaultOAuth2AccessToken(UUID.randomUUID().toString());
int
validitySeconds
=
getAccessTokenValiditySeconds(authentication.getOAuth2Request());
if
(validitySeconds
>
0)
{
token.setExpiration(new
Date(System.currentTimeMillis()
+
(validitySeconds
*
1000L)));
}
token.setRefreshToken(refreshToken);
//scope对应作用范围
token.setScope(authentication.getOAuth2Request().getScope());//上一节介绍的自定义TokenEnhancer,这边使用
return
accessTokenEnhancer
!=
null
?
accessTokenEnhancer.enhance(token,
authentication)
:
token;}既然提到TokenEnhancer,这边简单贴一下代码。public
class
CustomTokenEnhancer
extends
JwtAccessTokenConverter
{private
static
final
String
TOKEN_SEG_USER_ID
=
"X-KEETS-UserId";private
static
final
String
TOKEN_SEG_CLIENT
=
"X-KEETS-ClientId";@Overridepublic
OAuth2AccessToken
enhance(OAuth2AccessToken
accessToken,
OAuth2Authentication
authentication)
{
CustomUserDetails
userDetails
=
(CustomUserDetails)
authentication.getPrincipal();
Map<String,
Object>
info
=
new
HashMap<>();
//从自定义的userDetails中取出UserId
info.put(TOKEN_SEG_USER_ID,
userDetails.getUserId());
DefaultOAuth2AccessToken
customAccessToken
=
new
DefaultOAuth2AccessToken(accessToken);
customAccessToken.setAdditionalInformation(info);
OAuth2AccessToken
enhancedToken
=
super.enhance(customAccessToken,
authentication);
//设置ClientId
enhancedToken.getAdditionalInformation().put(TOKEN_SEG_CLIENT,
userDetails.getClientId());
return
enhancedToken;}}自此,用户身份校验与发放授权token结束。最终成功返回的结果为:{"access_token":
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsImV4cCI6MTUwODQ0Nzc1NiwidXNlcl9uYW1lIjoia2VldHMiLCJqdGkiOiJiYWQ3MmIxOS1kOWYzLTQ5MDItYWZmYS0wNDMwZTdkYjc5ZWQiLCJjbGllbnRfaWQiOiJmcm9udGVuZCIsInNjb3BlIjpbImFsbCJdfQ.5ZNVN8TLavgpWy8KZQKArcbj7ItJLLaY1zBRaAgMjdo",
"token_type":
"bearer","refresh_token":
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsInVzZXJfbmFtZSI6ImtlZXRzIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImJhZDcyYjE5LWQ5ZjMtNDkwMi1hZmZhLTA0MzBlN2RiNzllZCIsImV4cCI6MTUxMDk5NjU1NiwianRpIjoiYWE0MWY1MjctODE3YS00N2UyLWFhOTgtZjNlMDZmNmY0NTZlIiwiY2xpZW50X2lkIjoiZnJvbnRlbmQifQ.mICT1-lxOAqOU9M-Ud7wZBb4tTux6OQWouQJ2nn1DeE","expires_in":
43195,"scope":
"all","X-KEETS-UserId":
"d6448c24-3c4c-4b80-8372-c2d61868f8c6","jti":
"bad72b19-d9f3-4902-affa-0430e7db79ed","X-KEETS-ClientId":
"frontend"}4.总结本文开头给出了Auth系统概述,画出了简要的登录和校验的流程图,方便读者能对系统的实现有个大概的了解。然后主要讲解了用户身份的认证与token发放的具体实现。对于其中主要的类和接口进行了分析与讲解。下一篇文章主要讲解token的鉴定和API级别的上下文权限校验。本文的源码地址:GitHub:/keets2012/Auth-service码云:
/keets/Auth-Service
第三节引言:本文系《认证鉴权与API权限控制在微服务架构中的设计与实现》系列的第三篇,本文重点讲解token以及API级别的鉴权。本文对涉及到的大部分代码进行了分析,欢迎订阅本系列文章。1.前文回顾在开始讲解这一篇文章之前,先对之前两篇文章进行回忆下。在第一篇《认证鉴权与API权限控制在微服务架构中的设计与实现(一)》介绍了该项目的背景以及技术调研与最后选型。第二篇《认证鉴权与API权限控制在微服务架构中的设计与实现(二)》画出了简要的登录和校验的流程图,并重点讲解了用户身份的认证与token发放的具体实现。本文重点讲解鉴权,包括两个方面:token合法性以及API级别的操作权限。首先token合法性很容易理解,第二篇文章讲解了获取授权token的一系列流程,token是否是认证服务器颁发的,必然是需要验证的。其次对于API级别的操作权限,将上下文信息不具备操作权限的请求直接拒绝,当然此处是设计token合法性校验在先,其次再对操作权限进行验证,如果前一个验证直接拒绝,通过则进入操作权限验证。2.资源服务器配置ResourceServer配置在第一篇就列出了,在进入鉴权之前,把这边的配置搞清,即使有些配置在本项目中没有用到,大家在自己的项目有可能用到。@Configuration@EnableResourceServerpublicclassResourceServerConfigextendsResourceServerConfigurerAdapter{
//http安全配置
@Override
publicvoidconfigure(HttpSecurityhttp)throwsException{
//禁掉csrf,设置session策略
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()//默认允许访问
.requestMatchers().antMatchers("/**")
.and().authorizeRequests()
.antMatchers("/**").permitAll()
.anyRequest().authenticated()
.and().logout()//logout注销端点配置
.logoutUrl("/logout")
.clearAuthentication(true)
.logoutSuccessHandler(newHttpStatusReturningLogoutSuccessHandler())
.addLogoutHandler(customLogoutHandler());
}
//添加自定义的CustomLogoutHandler
@Bean
publicCustomLogoutHandlercustomLogoutHandler(){
returnnewCustomLogoutHandler();
}
//资源安全配置相关
@Override
publicvoidconfigure(ResourceServerSecurityConfigurerresources)throwsException{
super.configure(resources);
}}@EnableResourceServer这个注解很重要,OAuth2资源服务器的简便注解。其使得SpringSecurityfilter通过请求中的OAuth2token来验证请求。通常与EnableWebSecurity配合使用,该注解还创建了硬编码的@Order(3)WebSecurityConfigurerAdapter,由于当前spring的技术,order的顺序不易修改,所以在项目中避免还有其他order=3的配置。关联的HttpSecurity,与之前的SpringSecurityXML中的"http"元素配置类似,它允许配置基于web安全以针对特定http请求。默认是应用到所有的请求,通过requestMatcher可以限定具体URL范围。HttpSecurity类图如下。总的来说:HttpSecurity是SecurityBuilder接口的一个实现类,从名字上我们就可以看出这是一个HTTP安全相关的构建器。当然我们在构建的时候可能需要一些配置,当我们调用HttpSecurity对象的方法时,实际上就是在进行配置。authorizeRequests(),formLogin()、httpBasic()这三个方法返回的分别是ExpressionUrlAuthorizationConfigurer、FormLoginConfigurer、HttpBasicConfigurer,他们都是SecurityConfigurer接口的实现类,分别代表的是不同类型的安全配置器。
因此,从总的流程上来说,当我们在进行配置的时候,需要一个安全构建器SecurityBuilder(例如我们这里的HttpSecurity),SecurityBuilder实例的创建需要有若干安全配置器SecurityConfigurer实例的配合。关联的ResourceServerSecurityConfigurer,为资源服务器添加特殊的配置,默认的适用于很多应用,但是这边的修改至少以resourceId为单位。类图如下。ResourceServerSecurityConfigurer创建了OAuth2核心过滤器OAuth2AuthenticationProcessingFilter,并为其提供固定了OAuth2AuthenticationManager。只有被OAuth2AuthenticationProcessingFilter拦截到的oauth2相关请求才被特殊的身份认证器处理。同时设置了TokenExtractor、异常处理实现。OAuth2AuthenticationProcessingFilter是OAuth2保护资源的预先认证过滤器。配合OAuth2AuthenticationManager使用,根据请求获取到OAuth2token,之后就会使用OAuth2Authentication来填充SpringSecurity上下文。
OAuth2AuthenticationManager在前面的文章给出的AuthenticationManager类图就出现了,与token认证相关。这边略过贴出源码进行讲解,读者可以自行阅读。3.鉴权endpoint鉴权主要是使用内置的endpoint
/oauth/check_token,笔者将对端点的分析放在前面,因为这是鉴权的唯一入口。下面我们来看下该API接口中的主要代码。
@RequestMapping(value="/oauth/check_token")
@ResponseBody
publicMap<String,?>checkToken(CheckTokenEntitycheckTokenEntity){
//CheckTokenEntity为自定义的dto
Assert.notNull(checkTokenEntity,"invalidtokenentity!");
//识别token
OAuth2AccessTokentoken=resourceServerTokenServices.readAccessToken(checkTokenEntity.getToken());
//判断token是否为空
if(token==null){
thrownewInvalidTokenException("Tokenwasnotrecognised");
}
//未过期
if(token.isExpired()){
thrownewInvalidTokenException("Tokenhasexpired");
}
//加载OAuth2Authentication
OAuth2Authenticationauthentication=resourceServerTokenServices.loadAuthentication(token.getValue());
//获取response,token合法性验证完毕
Map<String,Object>response=(Map<String,Object>)accessTokenConverter.convertAccessToken(token,authentication);
//checkforapipermission
if(response.containsKey("jti")){
//上下文操作权限校验
Assert.isTrue(checkPermissions.checkPermission(checkTokenEntity));
}
response.put("active",true);
//Alwaystrueiftokenexistsandnotexpired
returnresponse;
}看过security-oauth源码的同学可能立马就看出上述代码与源码不同,熟悉/oauth/check_token校验流程的也会看出来,这边笔者对security-oauth
jar进行了重新编译,修改了部分源码用于该项目需求的场景。主要是加入了前置的API级别的权限校验。4.token合法性验证从上面的CheckTokenEndpoint中可以看出,对于token合法性验证首先是识别请求体中的token。用到的主要方法是ResourceServerTokenServices提供的readAccessToken()方法。该接口的实现类为DefaultTokenServices,在之前的配置中有讲过这边配置了jdbc的TokenStore。publicclassJdbcTokenStoreimplementsTokenStore{
...
publicOAuth2AccessTokenreadAccessToken(StringtokenValue){
OAuth2AccessTokenaccessToken=null;
try{
//使用selectAccessTokenSql语句,调用了私有的extractTokenKey()方法
accessToken=jdbcTemplate.queryForObject(selectAccessTokenSql,newRowMapper<OAuth2AccessToken>(){
publicOAuth2AccessTokenmapRow(ResultSetrs,introwNum)throwsSQLException{
returndeserializeAccessToken(rs.getBytes(2));
}
},extractTokenKey(tokenValue));
}
//异常情况
catch(EmptyResultDataAccessExceptione){
if(LOG.isInfoEnabled()){
LOG.info("Failedtofindaccesstokenfortoken"+tokenValue);
}
}
catch(IllegalArgumentExceptione){
LOG.warn("Failedtodeserializeaccesstokenfor"+tokenValue,e);
//不合法则移除
removeAccessToken(tokenValue);
}
returnaccessToken;
}
...
//提取TokenKey方法
protectedStringextractTokenKey(Stringvalue){
if(value==null){
returnnull;
}
MessageDigestdigest;
try{
//MD5
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2024年中心企业货物互供协议
- 如何防疫课件教学课件
- 2024第三季度上海歌剧院(第三批)人员招聘2人管理单位遴选500模拟题附带答案详解
- 中电科安智慧用电解决方案
- 企业风险管理保证书
- 互联网医疗合同与隐私保护
- 代持房产合同样本
- 企业借款合同范例钟表行业
- 临时工劳动合同环境科学
- 互联网人才培育协议
- 光伏逆变器安装施工方案
- 周志华-机器学习-Chap01绪论-课件
- 部编版六年级年册《第五单元习作 围绕中心意思写》课件
- 2024-2030中国胎牛血清市场现状研究分析与发展前景预测报告
- MOOC 音乐与科学-南京邮电大学 中国大学慕课答案
- 小学校园反恐防暴安全
- 多图中华民族共同体概论课件第十三讲先锋队与中华民族独立解放(1919-1949)根据高等教育出版社教材制作
- 120急救中心-检伤分类专项试题及答案
- 异位妊娠PPT医学课件
- 电子病历安全保障与隐私保护
- 小学生消防安全教育主题
评论
0/150
提交评论