Spring Security Oauth2 认证(获取token/刷新token)流程(password模式)

https://blog.csdn.net/bluuusea/article/details/80284458

1.本文介绍的认证流程范围

本文主要对从用户发起获取token的请求(/oauth/token),到请求结束返回token中间经过的几个关键点进行说明。

2.认证会用到的相关请求

注:所有请求均为post请求。

  • 获取access_token请求(/oauth/token)
    请求所需参数:client_id、client_secret、grant_type、username、password
http://localhost/oauth/token?client_id=demoClientId&client_secret=demoClientSecret&grant_type=password&username=demoUser&password=50575tyL86xp29O380t1
  • 检查头肯是否有效请求(/oauth/check_token)
    请求所需参数:token
http://localhost/oauth/check_token?token=f57ce129-2d4d-4bd7-1111-f31ccc69d4d1
  • 刷新token请求(/oauth/token)
    请求所需参数:grant_type、refresh_token、client_id、client_secret
    其中grant_type为固定值:grant_type=refresh_token
http://localhost/oauth/token?grant_type=refresh_token&refresh_token=fbde81ee-f419-42b1-1234-9191f1f95be9&client_id=demoClientId&client_secret=demoClientSecret

2.认证核心流程

注:文中介绍的认证服务器端token存储在Reids,用户信息存储使用数据库,文中会包含相关的部分代码。

2.1.获取token的主要流程:

加粗内容为每一步的重点,不想细看的可以只看加粗内容:

  1. 用户发起获取token的请求。
  2. 过滤器会验证path是否是认证的请求/oauth/token,如果为false,则直接返回没有后续操作。
  3. 过滤器通过clientId查询生成一个Authentication对象
  4. 然后会通过username和生成的Authentication对象生成一个UserDetails对象,并检查用户是否存在。
  5. 以上全部通过会进入地址/oauth/token,即TokenEndpoint的postAccessToken方法中。
  6. postAccessToken方法中会验证Scope,然后验证是否是refreshToken请求等。
  7. 之后调用AbstractTokenGranter中的grant方法。
  8. grant方法中调用AbstractUserDetailsAuthenticationProvider的authenticate方法,通过username和Authentication对象来检索用户是否存在
  9. 然后通过DefaultTokenServices类从tokenStore中获取OAuth2AccessToken对象
  10. 然后将OAuth2AccessToken对象包装进响应流返回

2.2.刷新token(refresh token)的流程

刷新token(refresh token)的流程与获取token的流程只有⑨有所区别:

  • 获取token调用的是AbstractTokenGranter中的getAccessToken方法,然后调用tokenStore中的getAccessToken方法获取token。
  • 刷新token调用的是RefreshTokenGranter中的getAccessToken方法,然后使用tokenStore中的refreshAccessToken方法获取token。

2.3.tokenStore的特点

tokenStore通常情况为自定义实现,一般放置在缓存或者数据库中。此处可以利用自定义tokenStore来实现多种需求,如:

  • 同已用户每次获取token,获取到的都是同一个token,只有token失效后才会获取新token。
  • 同一用户每次获取token都生成一个完成周期的token并且保证每次生成的token都能够使用(多点登录)。
  • 同一用户每次获取token都保证只有最后一个token能够使用,之前的token都设为无效(单点token)。

3.获取token的详细流程(代码截图)

3.1.代码截图梳理流程

1.一个比较重要的过滤器
这里写图片描述
2.此处是①中的attemptAuthentication方法
这里写图片描述
3.此处是②中调用的authenticate方法
这里写图片描述
4.此处是③中调用的AbstractUserDetailsAuthenticationProvider类的authenticate方法
这里写图片描述
5.此处是④中调用的DaoAuthenticationProvider类的retrieveUser方法
这里写图片描述
6.此处为⑤中调用的ClientDetailsUserDetailsService类的loadUserByUsername方法,执行完后接着返回执行④之后的方法
这里写图片描述
7.此处为④中调用的DaoAuthenticationProvider类的additionalAuthenticationChecks方法,此处执行完则主要过滤器执行完毕,后续会进入/oauth/token映射的方法。
这里写图片描述
8.此处进入/oauth/token映射的TokenEndpoint类的postAccessToken方法
这里写图片描述
9.此处为⑧中调用的AbstractTokenGranter类的grant方法
这里写图片描述
10.此处为⑨中调用的ResourceOwnerPasswordTokenGranter类中的getOAuth2Authentication方法
这里写图片描述
11.此处为⑩中调用的自定义的CustomUserAuthenticationProvider类中的authenticate方法,此处校验用户密码是否正确,此处执行完则返回⑨执行后续方法。
这里写图片描述
12.此处为⑨中调用的DefaultTokenServices中的createAccessToken方法
这里写图片描述
13.此处为12中调用的RedisTokenStore中的getAccessToken方法等,此处执行完,则一直向上返回到⑧中执行后续方法。
这里写图片描述
14.此处为⑧中获取到token后需要包装返回流操作
这里写图片描述

3.2.示例中spring-security.xml的部分配置

<!-- 认证地址 -->
<sec:http pattern="/oauth/token" create-session="stateless"
              authentication-manager-ref="authenticationManager" >
    <sec:intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY" />
    <sec:anonymous enabled="false" />
    <sec:http-basic entry-point-ref="clientAuthenticationEntryPoint" />
    <sec:custom-filter ref="clientCredentialsTokenEndpointFilter" before="BASIC_AUTH_FILTER" />
    <sec:access-denied-handler ref="oauthAccessDeniedHandler" />
</sec:http>

<bean id="clientAuthenticationEntryPoint"
          class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
    <property name="realmName" value="springsec/client" />
    <property name="typeName" value="Basic" />
</bean>

<bean id="clientCredentialsTokenEndpointFilter"
      class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
    <property name="authenticationManager" ref="authenticationManager" />
</bean>

<bean id="oauthAccessDeniedHandler"
      class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler">
</bean>


<!-- 认证管理器-->
<sec:authentication-manager alias="authenticationManager">
    <sec:authentication-provider user-service-ref="clientDetailsUserService" />
</sec:authentication-manager>

<!-- 注入自定义clientDetails-->
<bean id="clientDetailsUserService"
      class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
    <constructor-arg ref="clientDetails" />
</bean>

<!-- 自定义clientDetails-->
<bean id="clientDetails" class="com.xxx.core.framework.oauth.CustomClientDetailsServiceImpl">
</bean>
<!-- 注入自定义provider-->
<sec:authentication-manager id="userAuthenticationManager">
    <sec:authentication-provider ref="customUserAuthenticationProvider" />
</sec:authentication-manager>
<!--自定义用户认证provider-->
<bean id="customUserAuthenticationProvider"
      class="com.xxx.core.framework.oauth.CustomUserAuthenticationProvider">
</bean>

<oauth:authorization-server
        client-details-service-ref="clientDetails" token-services-ref="tokenServices" check-token-enabled="true" >
    <oauth:authorization-code />
    <oauth:implicit/>
    <oauth:refresh-token/>
    <oauth:client-credentials />
    <oauth:password authentication-manager-ref="userAuthenticationManager"/>
</oauth:authorization-server>

<!-- 自定义tokenStore-->
<bean id="tokenStore"
      class="com.xxx.core.framework.oauth.RedisTokenStore" />

<!-- 设置access_token有效期,设置支持refresh_token,refresh_token有效期默认为30天-->
<bean id="tokenServices"
      class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
    <property name="tokenStore" ref="tokenStore" />
    <property name="supportRefreshToken" value="true" />
    <property name="accessTokenValiditySeconds" value="43200"></property>
    <property name="clientDetailsService" ref="clientDetails" />
</bean>

4.总结

本文中的流程能够结果的问题:

  • 需要自定义修改获取到的token
  • token单点问题
  • 使用refresh_token的情况

PS:梳理这个认证流程也是因为最近工作需要设置token超时机制,刷新token,检查token等,需要对认证这块了解的特别清楚才行,后面的代码截图只是详细的介绍了获取access_token的流程,其实refresh_token与access_token的流程基本是一样的,如果您在使用refresh_token过程中有什么问题,也可以详细看下上面的截图,或许会有一些收获。

 

Spring Security Oauth2.0认证授权_/oauth/authorize-CSDN博客

  1. 基本概念

  • 认证: 用户认证就是判断一个用户的身份是否合法的过程 ,用户去访问系统资源时系统要求验证用户的身份信息,身份合法方可继续访问,不合法则拒绝访问。常见的用户身份认证方式有:用户名密码登录,二维码登录,手机短信登录,指纹认证等方式。

  • 会话:用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保证在会话中。会话就是系统为了保持当前用户的登录状态所提供的机制,常见的有基于session方式、基于token方式等。

  • 授权:授权是用户认证通过后根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问。授权可简单理解为Who对What(which)进行How操作,

  • Who,即主体( Subject), 主体一般是指用户,也可以是程序,需要访问系统中的资源。

  • What,即资源( Resource), 如系统菜单、页面、按钮、代码方法、系统商品信息、系统订单信息等。系统菜单、页面、按钮、代码方法都属于 系统功能资源,对于web系统每个功能资源通常对应一个URL ;系统商品信息系统订单信息都属于 实体资源(数据资源), 实体资源由资源类型和资源实例组成,比如商品信息为资源类型,商品编号为001的商品为资源实例。

  • How ,权限/许可( Permission), 规定了用户对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个代码方法的调用权限、编号为001的用户的修改权限等,通过权限可知用户对哪些资源都有哪些操作许可。

 

资源和权限可以合并一起

  • RBAC

基于角色的访问控制 if (主体.hasRole()){}

基于资源的访问控制 if(主体.hasPermission()){}

  1. Spring Boot Security

对所有进入系统的请求进行拦截,校验每个请求是否能够访问它所期望的资源,通过Filter实现

2.1 结构原理

初始化Spring Security时,初始化一个类型为org.springframework.security.web.FilterChainProxy的过滤器,

外部的请求会经过此类。FilterChainProxy是一个代理,真正起作用的是FilterChainProxy中SecurityFilterChain所包含的各个Filter,同时这些Filter作为Bean被Spring管理,它们是Spring Security核心,并不直接处理用户的认证,也不直接处理用户的授权,而是把它们交给认证管理器(AuthenticationManager)和 决策管理器(AccessDecisionManager)进行处理

spring Security功能的实现主要是由一系列过滤器链相互配合完成。

2.2 认证过程

1. 用户提交用户名、密码被SecurityFilterChain中的 UsernamePasswordAuthenticationFilter 过滤器获取到,封装为请求Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。

2. 然后过滤器将Authentication提交至认证管理器(AuthenticationManager)进行认证

3. 认证成功后, AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除) Authentication 实例。

4. SecurityContextHolder 安全上下文容器将第3步填充了信息的 Authentication ,通过

SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。

  • AuthenticationManager接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,

它的实现类为ProviderManager。

  • 而Spring Security支持多种认证方式,因此ProviderManager维护着一个List<AuthenticationProvider> 列表,存放多种认证方式,最终实际的认证工作是由AuthenticationProvider完成。不同的认证方式使用不同的AuthenticationProvider。如使用用户名密码登录时,使用DaoAuthenticationProvide

  • DaoAuthenticationProvider,它的内部又维护着一个UserDetailsService负责UserDetails的获取。最终AuthenticationProvider将UserDetails填充至Authentication。

2.3 密码处理

Spring Security提供了很多内置的PasswordEncoder,能够开箱即用,使用某种PasswordEncoder只需要进行如

下声明即可

常见的密码编码器有

NoOpPasswordEncoder、BCryptPasswordEncoder、Pbkdf2PasswordEncode、SCryptPasswordEncoder

2.4 授权过程

  • 已认证用户访问受保护的web资源将被SecurityFilterChain中的 FilterSecurityInterceptor 的子类拦截。

  • FilterSecurityInterceptor会从 SecurityMetadataSource 的子类DefaultFilterInvocationSecurityMetadataSource 获取要访问当前资源所需要的权限Collection<ConfigAttribute>

  • SecurityMetadataSource其实就是读取“安全拦截机制”

  • FilterSecurityInterceptor会调用 AccessDecisionManager 进行授权决策,若决策通过,则允许访问资源,否则将禁止访问。此处会从安全上下文中获取用户:Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 再进行比对和判断。

  • decide接口就是用来鉴定当前用户是否有访问对应受保护资源的权限。

  • authentication:要访问资源的访问者的身份

  • object:要访问的受保护资源,web请求对应FilterInvocation

  • confifigAttributes:是受保护资源的访问策略,通过SecurityMetadataSource获取。

Spring Security内置了三个基于投票的AccessDecisionManager实现类

AffirmativeBased 只要有一个赞成票,则表示同意用户访问

ConsensusBased:赞成票多余反对票,则表示同意用户访问

UnanimousBased:只要有一个反对票,则表示反对用户访问

 

spring security为防止CSRF(Cross-site request forgery跨站请求伪造)的发生,限制了除了get以外的大多数方法,

解决方法1:

屏蔽CSRF控制,即spring security不再限制CSRF。httpSecurity.csrf().disable()

解决方法2:

表单添加隐藏域: <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>

2.5 实际应用

用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保存在会话中。spring security提供会话管理,认证通过后将身份信息放入SecurityContextHolder上下文,SecurityContext与当前线程进行绑定,方便获取用户身份。

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

2.5.1 会话控制

  • Spring Security会为每个登录成功的用户会新建一个Session

httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)

  • session超时、session失效后,通过Spring Security 设置跳转的路径。

httpSecurity.sessionManagement()

.expiredUrl("/login‐view?error=EXPIRED_SESSION")

.invalidSessionUrl("/login‐view?error=INVALID_SESSION");

  • session退出

httpSecurity.logout()

.logoutUrl("/logout")

.logoutSuccessUrl("/login‐view?logout")

.logoutSuccessHandler(logoutSuccessHandler)

.addLogoutHandler(logoutHandler)

.invalidateHttpSession(true)

定制的 LogoutSuccessHandler ,实现用户退出成功时的处理。如果指定了这个选项那么logoutSuccessUrl() 的设置被忽略;

添加一个 LogoutHandler ,用于实现用户退出时的清理工作.默认 SecurityContextLogoutHandler 会被添加为最后一个 LogoutHandler;

指定是否在退出时让 HttpSession 无效。 默认设置为 true

2.5.2 web授权

使用 http.authorizeRequests() 对web资源进行授权保护

httpSecurity

.authorizeRequests()

.antMatchers("/r/r1").hasAuthority("p1") (

.antMatchers("/r/r2").hasAuthority("p2")

.antMatchers("/r/r3").access("hasAuthority('p1') and hasAuthority('p2')")

.antMatchers("/r/**").authenticated()

.anyRequest().permitAll()

.and()

.formLogin()

  • 保护URL常用的方法有:

authenticated() 保护URL,需要用户登录

permitAll() 指定URL无需保护,一般应用与静态资源文件

hasRole(String role) 限制单个角色访问,角色将被增加 “ROLE_” .所以”ADMIN” 将和 “ROLE_ADMIN”进行比较.

hasAuthority(String authority) 限制单个权限访问

hasAnyRole(String… roles)允许多个角色访问.

hasAnyAuthority(String… authorities) 允许多个权限访问.

access(String attribute) 该方法使用 SpEL表达式, 所以可以创建复杂的限制.

hasIpAddress(String ipaddressExpression) 限制IP地址或子网

2.5.3.方法授权

以在任何 @Configuration 实例上使用 @EnableGlobalMethodSecurity 注释来启用基于注解的安全性。

@PreAuthorize,@PostAuthorize, @Secured三类注解作用域服务层方法上,限制访问的访问

例如:

  • 匿名访问

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")

@PreAuthorize("isAnonymous()")

方法可匿名访问,底层使用WebExpressionVoter投票器

  • @PreAuthorize

@PreAuthorize("hasAuthority('p_transfer') and hasAuthority('p_read_account')")

同时拥有p_transfer和p_read_account权限才能访问,底层使用WebExpressionVoter投票器

另外,还有注解@PostAuthorize

  1. 分布式系统认证方案

软件的架构由单体结构演变为分布式架构,具有分布式架构的系统叫分布式系统,分布式系统的运行通常依赖网络,它将单体结构的系统分为若干服务,服务之间通过网络交互来完成用户的业务处理,当前流行的微服务架构就是分布式系统架构。

3.1 分布式认证需求

分布式系统的每个服务都会有认证、授权的需求,考虑分布式系统共享性的特点,需要由独立的认证服务处理系统认证授权的请求;考虑分布式系统开放性的特点,不仅对系统内部服务提供认证,对第三方系统也要提供认证。分布式认证的需求总结如下:

统一认证授权

提供独立的认证服务,统一处理认证授权。无论是不同类型的用户,还是不同种类的客户端(web端,H5、APP),均采用一致的认证、权限、会话机制,实现统一认证授权。要实现统一则认证方式必须可扩展,支持各种认证需求,比如:用户名密码认证、短信验证码、二维码、人脸识别等认证方式,并可以非常灵活的切换。

应用接入认证

应提供扩展和开放能力,提供安全的系统对接机制,并可开放部分API给接入第三方使用,一方应用(内部系统服务)和三方应用(第三方应用)均采用统一机制接入。

3.2 选型分析

3.2.1 基于Session认证

每个应用服务都需要在session中存储用户身份信息,通常的做法有下面几种:

Session复制:多台应用服务器之间同步session,使session保持一致,对外透明。

Session黏贴:当用户访问集群中某台服务器后,强制指定后续所有请求均落到此机器上。

Session集中存储:将Session存入分布式缓存中,所有服务器应用实例统一从分布式缓存中存取Session。

基于session认证的认证方式,可以更好的在服务端对会话进行控制,且安全性较高。但是,session机制方式基于cookie,在复杂多样的移动客户端上不能有效的使用,并且无法跨域,另外随着系统的扩展需提高session的复制、黏贴及存储的容错性。

3.2.2 基于Token认证

基于token的认证方式,服务端不用存储认证数据,易维护扩展性强, 客户端可以把token 存在任意地方,并且可以实现web和app统一认证机制。其缺点也很明显,token由于自包含信息,因此一般数据量较大,而且每次请求都需要传递,因此比较占带宽。另外,token的签名验签操作也会给cpu带来额外的处理负担。

3.2.3 技术方案

根据 选型分析,采用token认证,它的优点是:

  1. 适合统一认证的机制,客户端、一方应用、三方应用都遵循一致的认证机制。

  1. token认证方式对第三方应用接入更适合,因为它更开放,可使用当前有流行的开放协议Oauth2.0、JWT等。

  1. 一般情况服务端无需存储会话信息,减轻了服务端的压力。

3.3 Oauth2.0

OAuth(开放授权)是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。例如,第三方登录,用户 授权 电商网站 访问 用户存储在微信平台的用户信息

OAauth2.0包括以下角色:

  1. 客户端

本身不存储资源,需要通过资源拥有者的授权去请求资源服务器的资源,比如:Android客户端、Web客户端(浏览器端)、微信客户端等。

  1. 资源拥有者

通常为用户,也可以是应用程序,即该资源的拥有者。

  1. 资源服务器

存储资源的服务器,例如微信平台用户信息资源。

服务提供商会给准入的接入方一个身份,用于接入时的凭据:

client_id:客户端标识

client_secret:客户端秘钥

  1. 授权服务器(也称认证服务器)

用于服务提供商对资源拥有的身份进行认证、对访问资源进行授权,认证成功后会给客户端发放令牌,作为客户端访问资源服务器的凭据。例如:微信认证服务器。

授权服务器对OAuth2.0中的两个角色进行认证授权,分别是资源拥有者客户端

  1. Spring Cloud Security OAuth2

Spring-Security-OAuth2是对OAuth2的一种实现,并且跟Spring Security相辅相成,与SpringCloud体系的集成也非常便利。其服务实现包括两个服务:

  • 认证授权服务 (Authorization Server)

应包含对接入端以及登入用户的合法性进行验证并颁发token等功能,对令牌的请求端点由 Spring MVC 控制器进行实现,下面是配置一个认证服务必须要实现的endpoints:

AuthorizationEndpoint 服务于认证请求。默认 URL:

/oauth/authorize 。

TokenEndpoint 服务于访问令牌的请求。默认 URL:

/oauth/token 。

  • 资源服务 (Resource Server)

应包含对资源的保护功能,对非法请求进行拦截,对请求中token进行解析鉴权等,下面的过滤器用于实现 OAuth 2.0 资源服务:

OAuth2AuthenticationProcessingFilter用来对请求给出的身份令牌解析鉴权。

4.1 授权服务器配置

可以使用

@Configuration

@EnableAuthorizationServer

注解并继承AuthorizationServerConfifigurerAdapter来配置OAuth2.0 授权服务器。

 

AuthorizationServerConfifigurerAdapter要求配置

ClientDetailsServiceConfifigurer:用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化

AuthorizationServerEndpointsConfifigurer:用来配置令牌(token)的访问端点和令牌服务(tokenservices)。

AuthorizationServerSecurityConfifigurer:用来配置令牌访问端点的安全约束

 

4.1.1 客户端详情

ClientDetailsServiceConfifigurer能够使用内存或者JDBC来实现客户端详情,ClientDetailsService负责查找ClientDetails,而ClientDetails有几个重要的属性

客户端详情(Client Details)能够在应用程序运行的时候进行更新,可以通过访问底层的存储服务(例如将客户端详情存储在一个关系数据库的表中,就可以使用 JdbcClientDetailsService)或者通过实现

ClientRegistrationService接口(同时你也可以实现 ClientDetailsService 接口)来管理。

此次客户端详情读取数据库的配置:

 

4.1.2 令牌管理服务

AuthorizationServerTokenServices 接口定义了一些操作可以对令牌进行一些必要的管理,这个接口的实现,则需要继承 DefaultTokenServices,里面包含了一些有用实现,你可以使用它来修改令牌的格式和令牌的存储,其持久化令牌委托一个 TokenStore 接口来实现,TokenStore 这个接口有一个默认的实现,它就是 InMemoryTokenStore。

  • InMemoryTokenStore:

这个版本的实现是被默认采用的,它可以完美的工作在单服务器上(即访问并发量压力不大的情况下,并且它在失败的时候不会进行备份),大多数的项目都可以使用这个版本的实现来进行尝试,可以在开发的时候使用它来进行管理,因为不会被保存到磁盘中,更易于调试。

  • JdbcTokenStore:

这是一个基于JDBC的实现版本,令牌会被保存进关系型数据库。使用这个版本的实现时,你可以在不同的服务器之间共享令牌信息,使用这个版本的时候需要把"spring-jdbc"这个依赖加入到classpath。

  • JwtTokenStore

这个版本的全称是 JSON Web Token(JWT),它可以把令牌相关的数据进行编码(因此对于后端服务来说,它不需要进行存储,这将是一个重大优势),但是它有一个缺点,那就是撤销一个已经授权令牌将会非常困难,所以它通常用来处理一个生命周期较短的令牌以及撤销刷新令牌(refresh_token)。另外一个缺点就是这个令牌占用的空间会比较大,如果你加入了比较多用户凭证信息。JwtTokenStore 不会保存任何数据,但是它在转换令牌值以及授权信息方面与 DefaultTokenServices 所扮演的角色是一样的。

4.1.3 令牌访问端点

4.1.3.1 认证管理器

AuthorizationServerEndpointsConfifigurer通过设定以下属性决定支持的授权类型(Grant Types):

  • authenticationManager

认证管理器,选择了资源所有者密码(password)授权类型的时候,请设置这个属性注入一个 AuthenticationManager 对象。

  • userDetailsService

设置了这个属性,需要有一个 UserDetailsService 接口的实现

  • authorizationCodeServices:

用来设置授权码服务(即 AuthorizationCodeServices 的实例对象),主要用于 "authorization_code" 授权码类型模式。

  • implicitGrantServic

这个属性用于设置隐式授权模式,用来管理隐式授权模式的状态。

  • tokenGranter:

授权将会交由自己来完全掌控,并且会忽略掉上面的这几个属性,这个属性一般是用作拓展用途

4.1.3.2 令牌访问点

框架的默认URL访问链接如下列表

/oauth/authorize:授权端点。

/oauth/token:令牌端点。

/oauth/confifirm_access:用户确认授权提交端点。

/oauth/error:授权服务错误信息端点。

/oauth/check_token:用于资源服务访问的令牌解析端点。

/oauth/token_key:提供公有密匙的端点,如果你使用JWT令牌的话。

AuthorizationServerEndpointsConfifigurer 这个配置对象有一个叫做 pathMapping() 的方法用来配置端点URL链接,它有两个参数:

第一个参数:String 类型的,这个端点URL的默认链接。

第二个参数:String 类型的,你要进行替代的URL链接。

4.1.4 令牌访问端点安全约束

AuthorizationServerSecurityConfigurer: :用来配置令牌端点(Token Endpoint)的安全约束

4.1.5 WEB安全配置

4.1.6 授权类型

4.1.6.1授权码模式
  • 资源拥有者打开客户端,客户端要求资源拥有者给予授权,它将浏览器被重定向到授权服务器,重定向时会附加客户端的身份信息

/uaa/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.baidu.com

  • 浏览器出现向授权服务器授权页面,之后用户同意授权

  • 授权服务器将授权码(AuthorizationCode)经浏览器发送给client

  • 客户端拿着授权码向授权服务器索要访问access_token

/uaa/oauth/token?client_id=c1&client_secret=secret&grant_type=authorization_code&code=5PgfcD&redirect_uri=http://www.baidu.com

  • 授权服务器返回令牌(access_token)

 

参数列表如下

client_id:客户端准入标识。

response_type:授权码模式固定为code。

scope:客户端权限。

client_secret:客户端秘钥。

grant_type:授权类型,填写authorization_code,表示授权码模式

code:授权码,就是刚刚获取的授权码,注意:授权码只使用一次就无效了,需要重新申请。

redirect_uri:申请授权码时的跳转url,一定和申请授权码时用的redirect_uri一致。

4.1.6.2 简化模式
  • 资源拥有者打开客户端,客户端要求资源拥有者给予授权,它将浏览器被重定向到授权服务器,重定向时会附加客户端的身份信息

/uaa/oauth/authorize?client_id=c1&response_type=token&scope=all&redirect_uri=http://www.baidu.com

  • 浏览器出现向授权服务器授权页面,之后用户同意授权

  • 授权服务器将授权码将令牌(access_token)以Hash的形式存放在重定向uri的fargment中发送给浏览器。fragment 主要是用来标识 URI 所标识资源里的某个资源,在 URI 的末尾通过 (#)作为 fragment 的开头,其中 # 不属于 fragment 的值。

4.1.6.3 密码模式
  • 资源拥有者将用户名、密码发送给客户端

  • 客户端拿着资源拥有者的用户名、密码向授权服务器请求令牌(access_token)

/uaa/oauth/token?

client_id=c1&client_secret=secret&grant_type=password&username=shangsan&password=123

  • 授权服务器将令牌(access_token)发送给client

4.1.6.4 客户端模式
  • 客户端向授权服务器发送自己的身份信息,并请求令牌(access_token)

  • 确认客户端身份无误后,将令牌(access_token)发送给client

/uaa/oauth/token?client_id=c1&client_secret=secret&grant_type=client_credentials

4.2资源服务器配置

@Configuration

@EnableResourceServer

public class ResouceServerConfig extends ResourceServerConfigurerAdapter

@EnableResourceServer 注解自动增加了一个类型为 OAuth2AuthenticationProcessingFilter 的过滤器链

 

  • ResourceServerSecurityConfifigurer中主要包括:

tokenServices:ResourceServerTokenServices 类的实例,用来实现令牌服务。

tokenStore:TokenStore类的实例,指定令牌如何访问,与tokenServices配置可选

resourceId:这个资源服务的ID,这个属性是可选的,但是推荐设置并在授权服务中进行验证。

其他的拓展属性例如 tokenExtractor 令牌提取器用来提取请求中的令牌。

  • HttpSecurity配置这个与Spring Security类似:

请求匹配器,用来设置需要进行保护的资源路径,默认的情况下是保护资源服务的全部路径。

通过http.authorizeRequests()来设置受保护资源的访问规则

其他的自定义权限保护规则通过 HttpSecurity 来进行配置。

RemoteTokenServices 资源服务器通过 HTTP 请求来解码令牌,每次都请求授权服务器端点 /oauth/check_token

此处使用JWT校验令牌

配置安全拦截机制

@EnableResourceServer 注解自动增加了一个类型为 OAuth2AuthenticationProcessingFilter 的过滤器链,此过滤器作用,主要是用来解析网关传过来的用户信息字符串,并存入安全上下文中

 

4.3 网关

网关整合 OAuth2.0作为OAuth2.0的资源服务器角色,实现接入客户端权限拦截、令牌解析并转发当前登录用户信息(jsonToken)给微服务

public class AuthFilter extends ZuulFilter

校验客户端token,验证接入客户端权限,并解析后明文token,转发请求到微服务

一、OAuth 2.0协议 关于Scope的说明

1.概念

Scope是 OAuth 2.0 中的一种机制,用于限制应用程序对用户帐户的访问。应用程序可以请求一个或多个范围,然后该信息会在同意屏幕中呈现给用户,并且颁发给应用程序的访问令牌将仅限于授予的范围。

OAuth 规范允许授权服务器或用户根据请求修改授予应用程序的范围,尽管在实践中这样做的服务示例并不多。

OAuth 没有为范围定义任何特定值,因为它高度依赖于服务的内部架构和需求。

GitHub 文档描述
通过作用域,您可以准确指定所需的访问权限类型。 作用域限制 OAuth 令牌的访问权限。 它们不会授予超出用户权限范围的任何额外权限。

在 GitHub 上设置 OAuth 应用程序时,请求的作用域会在授权表单上显示给用户。

例如通过授权码获取访问令牌后,可以通过以下方式查询当前作用域:

$ curl -H "Authorization: token OAUTH-TOKEN" https://api.github.com/users/codertocat -I
HTTP/2 200
X-OAuth-Scopes: repo, user \\ 列出令牌已授权的作用域。
X-Accepted-OAuth-Scopes: user \\ 列出操作检查的作用域。

GitHub定义了一些可用作用域
在这里插入图片描述

1. 添加作用域
在数据库或者内存中,给当前客户端添加了三个作用域。

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    // 配置客户端
    clients
            // 使用内存设置
            .inMemory()
            // client_id
            .withClient("client")
            // client_secret
            .secret(passwordEncoder.encode("secret"))
            // 授权类型: 授权码、刷新令牌、密码、客户端、简化模式、短信验证码 "refresh_token"
            .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "sms_code")
            // 授权范围,也可根据这个范围标识,进行鉴权
            .scopes("admin:org","write:org","read:org")
            .accessTokenValiditySeconds(300)
            .refreshTokenValiditySeconds(3000)
            // 授权码模式 授权页面是否自动授权
            //.autoApprove(false)
            // 拥有的权限
            .authorities("add:user")
            // 允许访问的资源服务 ID
            //.resourceIds("oauth2-resource-server001-demo")
            // 注册回调地址
            .redirectUris("http://localhost:20000/code");
}

2. 授权页面选择scope

授权使用不带scope参数访问授权码端点:

http://localhost:20000/oauth/authorize?client_id=client&client_secret=secret&response_type=code

可以看到授权页面会弹出所有的scope范围
在这里插入图片描述

如果携带了scope参数,则表明只需要对当前作用域进行授权:

http://localhost:20000/oauth/authorize?client_id=client&client_secret=secret&response_type=code&scope=admin:org

在这里插入图片描述

如果当前客户端没有配置scope作用域,申请的时候也没有传递scope参数,则会报错:

Handling OAuth2 error: error="invalid_scope", error_description="Empty scope (either the client or the user is not allowed the requested scopes)"

3.资源服务器对于scope的访问控制

授权获取到授权码以后,申请访问令牌,通过访问令牌访问资源服务器。先看下这时认证信息都有些啥
在这里插入图片描述

可以看到当前令牌对应的认证信息,包含授权码模式中的用户及Oauth信息,OAuth2Request对象,就保存了授权时的scope信息,如果没有授权的scope则不会出现在这里。

这样资源服务器也就获取到了scope数据,那么具体应该怎么使用scope进行访问控制呢?

之前有分析过Security 基于注解的权限控制@PreAuthorize等注解的使用方法,在spring-security-oauth也提供了相应的表达式,我们只需要在注解中使用oauth2相关的表达式就可以了。

在源码中,可以看到Oauth2相关的表达式写法,其中就有对scope作用域的访问控制。
在这里插入图片描述

以下例子,可以使用hasScope,表示当前Oauth应用,毕竟具有admin:user的作用域,否则会拒绝访问.

  @GetMapping("/resource")
    @PreAuthorize("#oauth2.hasScope('admin:user')") //
    public String resource(){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        System.out.println(authentication.toString());
        return "访问到了resource 资源 ";
    }

可以看到当前没有admin:user,所以去访问资源的服务器时会报错。
在这里插入图片描述
在这里插入图片描述

二、总结

通过以上案例分析,Spring Security Oauth2除了使用resourceId对服务级别进行控制,也能基于scope 添加更小粒度的控制。

https://blog.csdn.net/jiangjun_dao519/article/details/125242434

 

Spring Security OAuth2和RBAC权限授权_rbac oauth2-CSDN博客

1、什么是RBAC?
RBAC是指基于角色的权限访问控制,通过权限与角色相关联,用户可以通过成为适当角色的成员而得到这些角色的权限。简单来说,RBAC认为权限授权的过程可以抽象为:
Who能否对What进行How的操作?并对这个逻辑表达式进行判断是否为True的求解过程,所以如果项目上需要设计实现权限管理模块,那必定要考虑RBAC的思想。

RBAC分3个组成部分:用户、角色和权限。
User(用户):每个用户都有唯一的UID识别,并被授予不同的角色
Role(角色):不同角色具有不同的权限
Permission(权限):访问权限
用户-角色映射:用户和角色之间的映射关系
角色-权限映射:角色和权限之间的映射
它们之间的关系如下图所示:

2、为什么开发权限管理都喜欢使用Oauth?

OAuth是关于授权的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版。oauth2根据使用场景分4种实现模式:
1、授权码模式
2、简化模式
3、密码模式
4、客户端模式
在实际项目中,我们通常使用授权码模式(微信登陆)

Oauth2授权主要分两部分:
1、认证服务
2、资源服务
在实际项目中以上两个服务板块可以部署在同一台服务器,也可以分开部署,而且实际开发中,推荐使用Spring Security Oauth2的实现方式,因为配置灵活,代码开发方便,如果结合路由组件,能更好的实现微服务权限控制扩展。

 
 
posted @ 2024-02-04 15:36  CharyGao  阅读(6098)  评论(0编辑  收藏  举报