目录
前言
1.介绍
2.Oauth2.0过程详解
3.Oauth 整合到 Spring Boot 实践
4.方法及配置详解:
总结
前言
Oauth2.0 是非常流行的网络授权表准,已经广泛应用在全球范围内,比较大的公司,如腾讯等都有大量的应用场景。
1.介绍
Oauth2.0 全称是 Open Authorization,是一种开放授权协议。目前使用的版本是 2.0 版本,也就是 Oauth2.0,它主要用于授权认证环节。
从官网文档可知道 Oauth 具有如下特点:
- 需要第三方进行授权,会存储用户登录授权凭据。
- 服务器必须支持密码认证。
- 有限范围内获取所有者(用户)的部分数据。
简而言之,就是用于第三方在用户授权的前提下获取用户的相关信息。而 Oauth2.0 的授权模式比较多,常用的有如下四种:
- 授权码模式:最常规的模式,适用于有服务器端的第三方应用(如网页应用)。在这种模式下,用户代理(如浏览器)首先被重定向到授权服务器进行身份验证,授权服务器通过用户代理回调客户端,并提供一个授权码。客户端随后会使用这个授权码向授权服务器后端请求访问令牌(access token),支持刷新的 token。
- Clinet 模式:适用于客户端应用直接访问它们自己的资源服务器,没有用户直接参与的场景。在这个模式中,客户端应用凭借自己的凭据(客户端ID及客户端密钥)向授权服务器请求访问令牌。其他应用或者程序通过 api 进行调用,获取对应的 token。
- 简化模式:用于没有服务器端能力的客户端应用,如 JavaScript 应用。在简化模式中,客户端通过用户代理直接从授权服务器获取访问令牌,不通过中间的授权码交换步骤。由于这种模式暴露了访问令牌,因此不如授权码模式安全。
- 密码模式 :如果用户对第三方应用高度信任(例如,设备操作系统或具备原生应用的第三方服务),则可以直接将用户名和密码提供给应用,应用使用这些凭据直接从授权服务器获取访问令牌。由于此流程涉及明文传递用户凭据,因此安全性较低。
总结:从中其实可以看出,最关键的部分就是这三方相互确认的过程。授权码模式就可以看成三方之间都不信任,所以需要不断地相互确认;而简化模式则可以看作授权服务器对第三方应用比较信任,表示直接就给了你我的令牌,你并不会拿我的令牌去做坏事,也就是说它们之间比较互信;密码模式则是资源拥有者对第三方应用比较信任,可以将自己的信息放心的放给第三方;客户端模式则可以看成三方都比较信任,不需要过多的验证,请求就给令牌就行了,比较适合我们内部的应用场景。
梳理 Oauth2.0 中有一个主线,就是三方参与者之间的信任程度。
2.Oauth2.0过程详解
前面我们对四个常见的模式进行了详细的了解,为了更加方便理解,对基本的过程再次进行详细解释。Oauth 的基本认正过程如图,分为三个角色:用户、第三方、以及认证服务器。
首先,用户去访问第三方应用,第三方应用引导用户进行授权,跳转到授权页面。用户进行授权后将数据传递给认证服务器,认证服务器返回 code 给第三方应用,第三方应用发起新的请求来获取访问授权令牌(access token),最后用户获取到授权结果。
3.Oauth 整合到 Spring Boot 实践
客户端凭证是最常用的认证方式之一。例如,微信授权就是这种方式,通过携带 access token 来获取用户资源。
新建Spring Boot 项目,实现方案是使用 SpringSecurity 和 Oauth2.0 模块,pom.xml 添加如下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
为了实现 Oauth 认证,需要对两方面进行配置,一是认证服务配置,包含 token 定义,用户客户端的信息以及授权服务和令牌服务。二是需要对资源服务进行配置,如资源访问权限设置,哪些需要 token 验证。
首先,编写认证服务配置,定义授权以及令牌服务。
package org.example.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
/**
* 认证服务器类
*
* @author freejava
*/
@Configuration
//此注解不像其他的 Enable注解一样内部有 Component 注解
@EnableAuthorizationServer
public class MySuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private AuthorizationCodeServices authorizationCodeServices;
//令牌访问安全策略
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//用于表单方式提交 client_id,client_secret
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()").
allowFormAuthenticationForClients();
}
@Bean
public PasswordEncoder noOpPasswordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
/**
* 配置客户端信息 哪些客户端可以发送请求
* @param clients 定义客户端信息的配置们,可以初始化客户端信息。
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//client_id
.withClient("myClientId")
// client_secret
.secret(NoOpPasswordEncoder.getInstance().encode("123456"))
//授权方式
.authorizedGrantTypes("authorization_code", "password","client_credentials","implicit","refresh_token")
.resourceIds("res1")//可以访问哪些资源
//授权范围
.scopes("all")//write
.autoApprove(false)//false跳转到授权页面
//加上验证回调地址
.redirectUris("http://www.baidu.com");
}
@Autowired
private ClientDetailsService clientDetailsService;
// 令牌服务
@Bean
public AuthorizationServerTokenServices tokenService(){
DefaultTokenServices services = new DefaultTokenServices();
services.setClientDetailsService(clientDetailsService);
services.setSupportRefreshToken(true);
services.setTokenStore(tokenStore());
services.setAccessTokenValiditySeconds(7200);//2小时
services.setRefreshTokenValiditySeconds(259200);//刷新令牌默认有效期 3 天
return services;
}
//设置授权码模式如何存储
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new InMemoryAuthorizationCodeServices();
}
private String SIGNING_KEY = "uaa123";
//内存方式
@Bean
public TokenStore tokenStore(){
return new InMemoryTokenStore();//new JwtTokenStore(accessTokenConverter());
}
/* @Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY); //对称秘钥,资源服务器使用该秘钥来验证
return converter;
}*/
/**
* 定义授权和令牌服务
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//super.configure(endpoints);
endpoints.authenticationManager(authenticationManager)
.authorizationCodeServices(authorizationCodeServices)//授权码服务
.tokenServices(tokenService())//令牌管理服务
.allowedTokenEndpointRequestMethods(HttpMethod.POST,HttpMethod.GET);//允许POST提交
}
}
资源配置编写,先编写一个 Controller 文件,代码如下:
package org.example.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ResController {
@GetMapping("/res/{id}")
public String testOauth(@PathVariable String id) {
return "Get the resource " + id;
}
}
然后编写该资源的访问权限配置,代码如下:
package org.example.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
/**
* 用于拦截请求的配置类
*/
@Configuration
@EnableResourceServer
public class MyResourceServerConfigurer extends ResourceServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
//这里重点注意
resources.tokenStore(tokenStore).resourceId("res1");
}
/**
* 用于拦截 http 请求
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable().
authorizeRequests()
.antMatchers("/oauth/**").permitAll() // 允许所有人访问 /oauth/* 下面的内容。
.anyRequest().authenticated(); // 其他所有请求都需要认证。
/*http.authorizeRequests().anyRequest()
.authenticated();
//("/res/**").authenticated();*/
}
}
为授权码模式编写一个webconfig:
package org.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author Administrator
* @version 1.0
**/
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//认证管理器
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
//安全拦截机制(最重要)
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/orther/**").hasAnyAuthority("p1")
.antMatchers("/login*").permitAll()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
通过 Postman 进行测试,访问 localhost:8080/res/1 会返回一个 unauthorized 的错误返回,这里需要传递 access token,所以需要先请求获取 access token 的接口 /oauth/token,之后再用该token进行请求即可。
4.方法及配置详解:
ClientDetailsServiceConfigurer
详解:
ClientDetailsServiceConfigurer
是用于配置客户端详细信息服务的类。客户端详细信息服务定义了客户端(应用程序)的信息,这包括客户端ID、客户端密钥、授权模式、令牌有效期以及重定向的URI等信息。客户端详细信息可以通过硬编码、数据库等方式来存储和管理。
以下是 ClientDetailsServiceConfigurer
的一些常用方法:
-
inMemory()
在内存中配置客户端存储,主要用于测试或简单应用程序,不适合生产使用。 -
jdbc(DataSource dataSource)
使用 JDBC 连接数据库,并在数据库中维护客户端信息。 -
withClient(String clientId)
创建新的客户端,并设置其客户端id。 -
secret(String secret)
设置客户端的密钥。对于使用Spring Security 5的应用程序,需要注意密码存储格式,通常是{bcrypt}
、{noop}
等。 -
redirectUris(String… redirectUris)
设置客户端的重定向URI,这些URI在OAuth2授权码模式下使用。 -
authorizedGrantTypes(String… authorizedGrantTypes)
设置客户端允许的授权模式,如authorization_code
,password
,refresh_token
,client_credentials
。 -
authorities(String… authorities)
为客户端设置Spring Security的权限。 -
scopes(String… scopes)
设置客户端的作用域(scope),限定客户端的访问范围。 -
accessTokenValiditySeconds(int accessTokenValiditySeconds)
设置访问令牌的有效时间(以秒计)。 -
refreshTokenValiditySeconds(int refreshTokenValiditySeconds)
设置刷新令牌的有效时间(以秒计)。 -
additionalInformation(Map<String, ?> additionalInformation)
设置一个包含其他客户端详细信息的Map。 -
autoApprove(boolean autoApprove)
如果设置为true,则不需要用户在授权阶段进行手动批准。 -
autoApprove(String… scopes)
指定特定范围的自动批准。 -
resourceIds(String… resourceIds)
限定客户端可以访问的资源服务器的ID。
AuthorizationServerEndpointsConfigurer详解:
默认得到访问路径:
- /oauth/authorize: 授权端点
- /oauth/token : 令牌端点。
- /loauth/confirm_access : 用户确认授权提交端点。
- /loauth/error: 授权服务错误信息端点。
- /oauth/check_token : 用于资源服务访问的令牌解析端点
- /oauth/token_key : 提供公有密匙的端点,如果你使用JWT令牌的话.
AuthorizationServerEndpointsConfigurer
是Spring Security OAuth2中用于定义授权服务器端点配置的类,它允许你定义与认证相关的各种参数和实现。也就是配置令牌的访问端点(发放令牌的地址)和令牌服务(如何发放),下面是一些 AuthorizationServerEndpointsConfigurer
的主要配置方法,以及它们的用途:
-
authenticationManager(AuthenticationManager authenticationManager)
设置认证管理器。在password
授权模式下,需要配置此项来验证用户的用户名和密码。 -
userDetailsService(UserDetailsService userDetailsService)
设置用户细节服务。如果你设置了一个自定义的用户细节服务,可以通过这个方法来配置。 -
authorizationCodeServices(AuthorizationCodeServices authorizationCodeServices)
设置授权码服务。用于authorization_code
授权类型。这能够改变默认的授权码存储方式(通常是内存)。 -
tokenStore(TokenStore tokenStore)
设置令牌存储方式。可以使用各种不同的存储方式,如内存、JDBC或JWT tokens。 -
accessTokenConverter(AccessTokenConverter accessTokenConverter)
设置访问令牌转换器。例如,使用JWT token时,可以配置一个JwtAccessTokenConverter
。 -
tokenEnhancer(TokenEnhancer tokenEnhancer)
设置令牌增强器。可以用来扩展JWT token内容。 -
approvalStore(ApprovalStore approvalStore)
设置批准存储。这跟token的保存方式类似,定义了用户批准记录的存储方式。 -
reuseRefreshTokens(boolean reuseRefreshTokens)
设置是否复用refresh tokens。默认为true,即当新的access token被创建后,原始的refresh token将会被复用。 -
refreshTokenGranter(TokenGranter refreshTokenGranter)
设置自定义的refresh token授权者。 -
grantTypes(TokenGranter tokenGranter)
设置自定义授权类型,可以添加或覆盖默认的授权类型来扩展功能。 -
tokenValidator(OAuth2TokenValidator<Jwt> tokenValidator)
在使用JWT时设置令牌验证器,用以验证JWT的有效性。 -
tokenServices(DefaultTokenServices tokenServices)
设置token服务的实例。这个服务负责创建和管理OAuth2 tokens。 -
exceptionTranslator(WebResponseExceptionTranslator<OAuth2Exception> exceptionTranslator)
设置异常翻译器。用来定义认证异常的响应格式。 -
requestFactory(OAuth2RequestFactory requestFactory)
设置OAuth2请求工厂。这允许你插入自定义逻辑来解析传入的请求。
AuthorizationServerSecurityConfigurer :
用来配置令牌端点的安全约束,比如哪些人可以访问。
ResourceServerSecurityConfigurer详解
:
ResourceServerSecurityConfigurer
是 Spring Security OAuth2 中用于配置资源服务器的安全细节的一个类。它允许你设置一些关于资源服务器的关键配置,比如 tokenStore、resourceId 和 tokenServices 等。下面是一些 ResourceServerSecurityConfigurer
的常用方法及其简要说明:
-
tokenStore(TokenStore tokenStore)
设置用于读取令牌信息的TokenStore
。这个TokenStore
被资源服务器用来解码访问令牌。常见的实现是JdbcTokenStore
、JwtTokenStore
和InMemoryTokenStore
。 -
resourceId(String resourceId)
设置此资源服务器的资源ID。这通常用于客户端请求的 Scope 限制,以及在多个资源服务器存在时加以区分。 -
tokenServices(TokenServices tokenServices)
设置ResourceServerTokenServices
实例,这是用来解码访问令牌的另一种方式。如果你的令牌是 JWT,则可以提供JwtTokenServices
。 -
tokenExtractor(TokenExtractor tokenExtractor)
设置用于提取访问令牌的TokenExtractor
。默认情况下,Spring OAuth2 使用BearerTokenExtractor
来处理来自请求头或请求参数的 Bearer 令牌。 -
authenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint)
定制用于处理认证错误的AuthenticationEntryPoint
。通常情况下,这用于处理资源服务器返回给客户端的401响应。 -
accessDeniedHandler(AccessDeniedHandler accessDeniedHandler)
设置自定义AccessDeniedHandler
以处理接收到访问被拒绝时的情况。这通常用来自定义服务器对403 Forbidden响应的处理。 -
stateless(Boolean stateless)
设置此资源服务器是否只关心无状态请求应答。这确定了资源服务器是否会在请求间创建HttpSession
。 -
authenticationManager(AuthenticationManager authenticationManager)
设置AuthenticationManager
。如果需要,可以用来登陆用户或验证用户的访问令牌。 -
eventPublisher(ApplicationEventPublisher eventPublisher)
设置ApplicationEventPublisher
以发布认证事件。这可以用来记录认证成功或者失败等事件。
总结
OAuth 2.0 是一个用于授权的开放标准,由 IETF OAuth 工作组于 2012 年发布。它允许用户授权第三方应用访问其在某一服务上的信息,而无需将用户名和密码提供给第三方应用,大大促进了当前互联网间的信息共享。