OAuth2.0授权码模式实战
OAuth2.0是目前比较流行的一种开源授权协议,可以用来授权第三方应用,允许在不将用户名和密码提供给第三方应用的情况下获取一定的用户资源,目前很多网站或APP基于微信或QQ的第三方登录方式都是基于OAuth2实现的。本文将基于OAuth2中的授权码模式,采用数据库配置方式,搭建认证服务器与资源服务器,完成授权与资源的访问。
流程分析
在OAuth2中,定义了4种不同的授权模式,其中授权码模式(authorization code)功能流程相对更加完善,也被更多的系统采用。首先使用图解的方式简单了解一下它的授权流程:

对上面的流程进行一下说明:
1、用户访问客户端2、客户端将用户导向认证服务器
3、用户登录,并对第三方客户端进行授权
4、认证服务器将用户导向客户端事先指定的重定向地址,并附上一个授权码
5、客户端使用授权码,向认证服务器换取令牌
6、认证服务器对客户端进行认证以后,发放令牌
7、客户端使用令牌,向资源服务器申请获取资源
8、资源服务器确认令牌,向客户端开放资源
在对授权码模式的流程有了一定基础的情况下,我们开始动手搭建项目。
项目搭建
准备工作
1、在Project中创建两个module,采用认证服务器和资源服务器分离的架构:

2、spring-security-oauth2是对Oauth2协议规范的一种实现,这里可以直接使用spring-cloud-starter-oauth2,就不需要分别引入spring-security和oauth2了。在父pom中引入:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
3、数据库建表,OAuth2需要的表结构如下:
DROP TABLE IF EXISTS `oauth_access_token`;
CREATE TABLE `oauth_access_token` (
`token_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`token` blob NULL,
`authentication_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`client_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authentication` blob NULL,
`refresh_token` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`resource_ids` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`client_secret` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`scope` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authorized_grant_types` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`web_server_redirect_uri` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authorities` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`access_token_validity` int(11) NULL DEFAULT NULL,
`refresh_token_validity` int(11) NULL DEFAULT NULL,
`additional_information` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
`autoapprove` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT 'false',
PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
DROP TABLE IF EXISTS `oauth_code`;
CREATE TABLE `oauth_code` (
`code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authentication` blob NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
DROP TABLE IF EXISTS `oauth_refresh_token`;
CREATE TABLE `oauth_refresh_token` (
`token_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`token` blob NULL,
`authentication` blob NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
oauth_access_token:存储生成的access_token,由类JdbcTokenStore操作oauth_client_details:存储客户端的配置信息,由类JdbcClientDetailsService操作oauth_code:存储服务端系统生成的code的值,由类JdbcAuthorizationCodeServices操作oauth_refresh_token:存储刷新令牌的refresh_token,如果客户端的grant_type不支持refresh_token,那么不会用到这张表,同样由类JdbcTokenStore操作
其余spring security相关的用户表、角色表以及权限表的表结构在这里省略,可以在文末的Git地址中下载。
认证服务器
认证服务器是服务提供者专门用来处理认证授权的服务器,主要负责获取用户授权并颁发token,以及完成后续的token认证工作。认证部分功能主要由spring security 负责,授权则由oauth2负责。
1、开启Spring Security配置
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
通过@Configuration 和@EnableWebSecurity 开启Spring Security配置,继承WebSecurityConfigurerAdapter的方法,实现个性化配置。如果使用内存配置用户,可以重写其中的configure方法进行配置,由于我们使用数据库中的用户信息,所以不需要在这里进行配置。并且采用认证服务器和资源服务器分离,也不需要在这里对服务资源进行权限的配置。
在类中创建了两个Bean,分别是用于处理认证请求的认证管理器AuthenticationManager,以及配置全局统一使用的密码加密方式BCryptPasswordEncoder,它们会在认证服务中被使用。
2、开启并配置认证服务器
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager; //认证管理器
@Autowired
private BCryptPasswordEncoder passwordEncoder;//密码加密方式
@Autowired
private DataSource dataSource; // 注入数据源
@Autowired
private UserDetailsService userDetailsService; //自定义用户身份认证
@Bean
public ClientDetailsService jdbcClientDetailsService(){
//将client信息存储在数据库中
return new JdbcClientDetailsService(dataSource);
}
@Bean
public TokenStore tokenStore(){
//对token进行持久化存储在数据库中,数据存储在oauth_access_token和oauth_refresh_token
return new JdbcTokenStore(dataSource);
}
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
//加入对授权码模式的支持
return new JdbcAuthorizationCodeServices(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//设置客户端的配置从数据库中读取,存储在oauth_client_details表
clients.withClientDetails(jdbcClientDetailsService());
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore())//token存储方式
.authenticationManager(authenticationManager)// 开启密码验证,来源于 WebSecurityConfigurerAdapter
.userDetailsService(userDetailsService)// 读取验证用户的信息
.authorizationCodeServices(authorizationCodeServices())
.setClientDetailsService(jdbcClientDetailsService());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// 配置Endpoint,允许请求,不被Spring-security拦截
security.tokenKeyAccess("permitAll()") // 开启/oauth/token_key 验证端口无权限访问
.checkTokenAccess("isAuthenticated()") // 开启/oauth/check_token 验证端口认证权限访问
.allowFormAuthenticationForClients()// 允许表单认证
.passwordEncoder(passwordEncoder); // 配置BCrypt加密
}
}
在类中,通过@EnableAuthorizationServer 注解开启认证服务,通过继承父类AuthorizationServerConfigurerAdapter,对以下信息进行了配置:
ClientDetailsServiceConfigurer:配置客户端服务,这里我们通过JdbcClientDetailsService从数据库读取相应的客户端配置信息,进入源码可以看到客户端信息是从表oauth_client_details中拉取。AuthorizationServerEndpointsConfigurer:用来配置授权(authorization)以及令牌(token)的访问端点,以及令牌服务的配置信息。该类作为一个装载类,装载了Endpoints所有的相关配置。AuthorizationServerSecurityConfigurer:配置令牌端点(endpoint)的安全约束,OAuth2开放了端点用于检查令牌,/oauth/check_token和/oauth/token_key这些端点默认受到保护,在这里配置可被外部调用。
3、采用从数据库中获取用户信息的方式进行身份验证
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private TbUserService userService;
@Autowired
private TbPermissionService permissionService;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
TbUser tbUser = userService.getUserByUserName(userName);
if (tbUser==null){
throw new UsernameNotFoundException("username : "+userName+" is not exist");
}
List<GrantedAuthority> authorities=new ArrayList<>();
//获取用户权限
List<TbPermission> permissions = permissionService.getByUserId(tbUser.getId());
permissions.forEach(permission->{
authorities.add(new SimpleGrantedAuthority(permission.getEname()));
});
return new User(tbUser.getUsername(),tbUser.getPassword(),authorities);
}
}
创建UserDetailServiceImpl 实现UserDetailsService接口,并实现loadUserByUsername方法,根据用户名从数据库查询用户信息及权限。
4、启动服务
首先发起请求获取授权码(code),直接访问下面的url:
http://localhost:9004/oauth/authorize?client_id=client1&redirect_uri=http://localhost:8848/nacos&response_type=code&scope=select
看一下各个参数的意义:
client_id:因为认证服务器要知道是哪一个应用在请求授权,所以client_id就是认证服务器给每个应用分配的id
redirect_uri:重定向地址,会在这个重定向地址后面附加授权码,让第三方应用获取code
response_type:code表明采用授权码认证模式
scope:需要获得哪些授权,这个参数的值是由服务提供商定义的,不能随意填写
首先会重定向到登录验证页面,因为之前的url中只明确了第三方应用的身份,这里要确定第三方应用要请求哪一个用户的授权。输入数据库表tb_user中配置的用户信息 admin/123456:

注意url中请求的参数必须和在数据库中的表oauth_client中配置的相同,如果不存在或信息不一致都会报错,在参数填写错误时会产生如下报错信息:

如果参数完全匹配,会请求用户向请求资源的客户端client授权:

点击Authorize同意授权,会跳转到redirect_uri定义的重定向地址,并在url后面附上授权码code:

这样,用户的登录和授权的操作都在浏览器中完成了,接下来我们需要获取令牌,发送post请求到/oauth/token接口,使用授权码获取access_token。在发送请求时,需要在请求头中包含clientId和clientSecret,并且携带参数 grant_type、code、redirect_uri,这里会对redirect_uri做二次验证:

这样,就通过/oauth/token端点获取到了access_token,并一同拿到了它的令牌类型、过期时间、授权范围信息,这个令牌将在请求资源服务器的资源时被使用。
资源服务器
资源服务器简单来说就是资源的访问入口,主要负责处理用户数据的api调用,资源服务器中存储了用户数据,并对外提供http服务,可以将用户数据返回给经过身份验证的客户端。资源服务器和认证服务器可以部署在一起,也可以分离部署,我们这里采用分开部署的形式。
1、配置资源服务器
@Configuration
@EnableResourceServer
public class ResourceConfig extends ResourceServerConfigurerAdapter {
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Primary
public RemoteTokenServices remoteTokenServices(){
final RemoteTokenServices tokenServices=new RemoteTokenServices();
//设置授权服务器check_token Endpoint 完整地址
tokenServices.setCheckTokenEndpointUrl("http://localhost:9004/oauth/check_token");
//设置客户端id与secret,注意:client_secret 值不能使用passwordEncoder加密
tokenServices.setClientId("client1");
tokenServices.setClientSecret("client-secret");
return tokenServices;
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
http.authorizeRequests()
.anyRequest().authenticated();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("oauth2").stateless(true);
}
}
在类中主要实现了以下功能:
@EnableResourceServer注解表明开启OAuth2资源服务器,在请求资源服务器的请求前,需要通过认证服务器获取access_token令牌,然后在访问资源服务器中的资源时需要携带令牌才能正常进行请求通过
RemoteTokenServices实现自定义认证服务器,这里配置了我们之前创建的认证服务器重写
configure(HttpSecurity http)方法,开启所有请求需要授权才可以访问配置资源相关设置
configure(ResourceServerSecurityConfigurer resources),这里只设置resourceId,作为该服务资源的唯一标识
2、测试接口,负责提供用户信息
@RestController
public class TestController {
@GetMapping("/user/{name}")
public User user(@PathVariable String name){
return new User(name, 20);
}
}
3、启动服务
不携带access_token,直接访问接口http://127.0.0.1:9005/user/hydra:

使用Postman,在Authorization中配置使用Bearer Token,并填入从认证服务器获取的access_token(或在Headers中的Authorization字段直接填写Bearer 'access_token'),再次访问接口,可以正常访问接口资源:

如果文章对您有所帮助,欢迎关注公众号 码农参上,项目Git地址:
OAuth2.0授权码模式实战的更多相关文章
- oauth2.0授权码模式详解
授权码模式原理 授权码模式(authorization code)是功能最完整.流程最严密的授权模式.它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动. 它 ...
- OAuth2.0授权码模式
OAuth2.0简单说就是一种授权的协议,OAuth2.0在客户端与服务提供商之间,设置了一个授权层(authorization layer).客户端不能直接登录服务提供商,只能登录授权层,以此将用户 ...
- OAuth2.0 授权码理解
OAuth2.0授权模式 本篇文章介绍OAuth的经典授权模式,授权码模式 所谓授权无非就是授权与被授权,被授权方通过请求得到授权方的同意,并赋予某用权力,这个过程就是授权. 那作为授权码 ...
- Spring Security OAuth2 Demo —— 授权码模式
本文可以转载,但请注明出处https://www.cnblogs.com/hellxz/p/oauth2_oauthcode_pattern.html 写在前边 在文章OAuth 2.0 概念及授权流 ...
- 单点登录 - OAuth 2.0 授权码模式(一)
OAuth 2.0定义了四种授权方式 授权码模式(authorization code) 简化模式(implicit) 密码模式(resource owner password credentials ...
- oauth2.0授权协议
参考文章 一.OAuth是什么? OAuth的英文全称是Open Authorization,它是一种开放授权协议.OAuth目前共有2个版本,2007年12月的1.0版(之后有一个修正版1.0a)和 ...
- 基于OWIN WebAPI 使用OAUTH2授权服务【授权码模式(Authorization Code)】
之前已经简单实现了OAUTH2的授权码模式(Authorization Code),但是基于JAVA的,今天花了点时间调试了OWIN的实现,基本就把基于OWIN的OAUHT2的四种模式实现完了.官方推 ...
- Oauth2认证模式之授权码模式实现
Oauth2认证模式之授权码模式(authorization code) 本示例实现了Oauth2之授权码模式,授权码模式(authorization code)是功能最完整.流程最严密的授权模式.它 ...
- OAuth 2.0之授权码模式
转载自:http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html OAuth 2.0授权码模式 授权码模式(authorization code)是功 ...
随机推荐
- Self-XSS All In One
Self-XSS All In One Self-XSS(自跨站脚本)攻击 警告! 使用此控制台可能会给攻击者可乘之机,让其利用 Self-XSS(自跨站脚本)攻击来冒充您并窃取您的信息.请勿输入或粘 ...
- POST 非幂等
POST 非幂等 HTTP幂等方法,是指无论调用这个url多少次,都不会有不同的结果的HTTP方法; 也就是不管你调用1次还是调用100次,1000次,结果都是一样的(前提是服务器端的数据没有被人为手 ...
- nodejs 在windows10中设置动态(视频)壁纸
github 项目地址 node版本 λ node -v v12.16.2 main.js const ffi = require("@saleae/ffi"); const ch ...
- NGK钱包真的安全吗?
对于数字资产持有者而言,资产的安全永远是首要的,因而数字钱包的安全性显得尤为重要.数字钱包分为冷钱包和热钱包两种.热钱包叫做在线钱包,而冷钱包被称为离线钱包,也叫硬件钱包.数字钱包一旦被盗,被追回的概 ...
- [转]关于特征点法、直接法、光流法slam的对比
转载网址:https://blog.csdn.net/weixin_38203573/article/details/79787499 特征点法: 通过特征点匹配来跟踪点,计算几何关系得到R,t,BA ...
- Java线程池实现原理及其在美团业务中的实践
本文转载自Java线程池实现原理及其在美团业务中的实践 导语 随着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流.使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器.J.U.C提供 ...
- Dubbo与Zookeeper开发
1.Dubbo 1.1RPC RPC全称是remote procedure call,即远程过程调用.比如有两台服务器A和B,它们上面分别部署了一个服务.此时B服务器想调用A服务器上提供的方法,由于不 ...
- ffmpeg番外篇:听说这款水印曾经在某音很火?办它!
今天在瞎逛时,偶然看到一个CSDN上的哥们说,他们曾经被一个水印难住了,仔细看了下,感觉可以用一行命令实现. 需求如下:视频加gif水印,gif循环,同时n秒后水印切换位置继续循环 这哥们遇到了两个问 ...
- springboot项目打包成jar包在Linux服务器默认80端口运行
springboot项目端口设置 在application.properties文件 server.port=80 在application.yml文件 server: port: 80 然后在ide ...
- Sapper:迈向理想的 Web 应用框架
扎稳阵脚,再进一步. 注意:原文发表于2017-12-31,随着框架不断演进,部分内容可能已不适用. 给迫不及待的小伙伴们的快速入门:Sapper 文档 和快速模板 starter template ...