热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:SpringSecurityOAuth2Demogood

1.添加依赖授权服务是基于SpringSecurity的,因此需要在项目中引入两个依赖:


1. 添加依赖
授权服务是基于Spring Security的,因此需要在项目中引入两个依赖:

<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-oauth2artifactId>
dependency>

前者为 Security,后者为Security的OAuth2扩展。

2. 添加注解和配置

在启动类中添加@EnableAuthorizationServer注解:

@SpringBootApplication
@EnableAuthorizationServer
public class AlanOAuthApplication {
public static void main(String[] args) {
SpringApplication.run(AlanOAuthApplication.
class, args);
}
}

完成这些我们的授权服务最基本的骨架就已经搭建完成了。
但是要想跑通整个流程,我们必须分配 client_id, client_secret才行。
Spring Security OAuth2的配置方法是编写@Configuration类继承AuthorizationServerConfigurerAdapter,
然后重写void configure(ClientDetailsServiceConfigurer clients)方法,如:

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
// 使用in-memory存储
.withClient("client") // client_id
.secret("secret") // client_secret
.authorizedGrantTypes("authorization_code") // 该client允许的授权类型
.scopes("app"); // 允许的授权范围
}

3. 授权流程
访问授权页面:

localhost:8080/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://www.baidu.com

此时浏览器会让你输入用户名密码,这是因为 Spring Security 在默认情况下会对所有URL添加Basic Auth认证。
默认的用户名为user, 密码是随机生成的,在控制台日志中可以看到。

技术分享

画风虽然很简陋,但是基本功能都具备了。点击Authorize后,浏览器就会重定向到百度,并带上code参数:

 技术分享

拿到code以后,就可以调用

POST/GET http://client:[email&#160;protected]:8080/oauth/token

来换取access_token了:

curl -X POST -H "Content-Type: application/x-www-form-urlencoded"

-d grant_type=authorization_code&code=Li4NZo&redirect_uri=http://www.baidu.com

"http://client:[email&#160;protected]:8080/oauth/token"

注意,URL中的client为上文中通过ClientDetailsServiceConfigurer类指定的clientId。
由于authorization_code的授权方式不需要 client_secret, 因此secret可以填写任意值

返回如下:

{
"access_token": "32a1ca28-bc7a-4147-88a1-c95abcc30556", // 令牌
"token_type": "bearer",
"expires_in": 2591999,
"scope": "app"
}

到此我们最最基本的授权服务就搭建完成了。然而,这仅仅是个demo,如果要在生产环境中使用,还需要做更多的工作。

 

4. 使用mysql存储access_token和client信息

在上面的例子中,所有的token信息都是保存在内存中的,这显然无法在生产环境中使用(进程结束后所有token丢失, 用户需要重新授权),因此我们需要将这些信息进行持久化操作。 把授权服务器中的数据存储到数据库中并不难,因为 Spring Cloud Security OAuth 已经为我们设计好了一套Schema和对应的DAO对象。但在使用之前,我们需要先对相关的类有一定的了解。

4.1 相关接口

Spring Cloud Security OAuth2通过DefaultTokenServices类来完成token生成、过期等 OAuth2 标准规定的业务逻辑,而DefaultTokenServices又是通过TokenStore接口完成对生成数据的持久化。在上面的demo中,TokenStore的默认实现为InMemoryTokenStore,即内存存储。 对于Client信息,ClientDetailsService接口负责从存储仓库中读取数据,在上面的demo中默认使用的也是InMemoryClientDetialsService实现类。说到这里就能看出,要想使用数据库存储,只需要提供这些接口的实现类即可。庆幸的是,框架已经为我们写好JDBC实现了,即JdbcTokenStore和JdbcClientDetailsService。

4.2 建表

要想使用这些JDBC实现,首先要建表。框架为我们提前设计好了schema, 在github上:https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql

在使用这套表结构之前要注意的是,对于MySQL来说,默认建表语句中主键是varchar(255)类型,在mysql中执行会报错,原因是mysql对varchar主键长度有限制。所以这里改成128即可。其次,语句中会有某些字段为LONGVARBINARY类型,它对应mysql的blob类型,也需要修改一下。

4.3 配置

数据库建好后,下一步就是配置框架使用JDBC实现。方法还是编写@Configuration类继承AuthorizationServerConfigurerAdapter:

@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
@Bean
// 声明TokenStore实现
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
@Bean
// 声明 ClientDetails实现
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
@Override
// 配置框架应用上述实现
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
endpoints.tokenStore(tokenStore());
// 配置TokenServices参数
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(endpoints.getTokenStore());
tokenServices.setSupportRefreshToken(
false);
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
tokenServices.setAccessTokenValiditySeconds( (
int) TimeUnit.DAYS.toSeconds(30)); // 30天
endpoints.tokenServices(tokenServices);
}

完成这些后,框架就会将中间产生的数据写到mysql中了。oauth_client_details是client表,可以直接在该表中添加记录来添加client:

技术分享

4.4 需要注意的地方

这里不得不说 Spring 设计有一个奇葩地的方。注意看oauth_access_token表是存放访问令牌的,但是并没有直接在字段中存放token。Spring 使用OAuth2AccessToken来抽象与令牌有关的所有属性,在写入到数据库时,Spring将该对象通过JDK自带的序列化机制序列成字节 直接保存到了该表的token字段中。也就是说,如果只看数据表你是看不出access_token的值是多少,过期时间等信息的。这就给资源服务器的实现带来了麻烦。我们的资源提供方并没有使用Spring Security,也不想引入 Spring Security 的任何依赖,这时候就只能将 DefaultOAuth2AccessToken的源码copy到资源提供方的项目中,然后读取token字段并反序列化还原对象来获取token信息。但是如果这样做还会遇到反序列化兼容性的问题,具体解决方法参考我另一篇博文: http://blog.csdn.net/neosmith/article/details/52539614

5. 总结

至此一个能在生产环境下使用的授权服务就搭建好了。其实我们在实际使用时应该适当定制JdbcTokenStore或ClientDetailsService来实适应业务需要,甚至可以直接从0开始实现接口,完全不用框架提供的实现。另外,Spring 直接将DefaultOAuth2AccessToken序列化成字节保存到数据库中的设计,我认为是非常不合理的。或许设计者的初衷是保密access_token,但是通过加密的方法也可以实现,完全不应该直接扔字节。不过通过定制TokenStore接口,我们可以使用自己的表结构而不拘泥于默认实现。

6. 个人看法

Spring的OAuth2实现有些过于复杂了,oauth2本身只是个非常简单的协议,完全可以自己在SpringMVC的基础上自由实现,没有难度,也不复杂。我想很多人去用框架应该是担心oauth2协议复杂实现起来健壮性不足,其实是多虑了。如果是开发我个人的项目,我肯定会不使用任何框架。

github地址: https://github.com/wanghongfei/spring-security-oauth2-example

 

项目使用的是MySql存储, 需要先创建以下表结构:

CREATE SCHEMA IF NOT EXISTS `alan-oauth` DEFAULT CHARACTER SET utf8 ;
USE `alan-oauth` ;
-- -----------------------------------------------------
--
Table `alan-oauth`.`clientdetails`
--
-----------------------------------------------------
CREATE TABLE IF NOT EXISTS `alan-oauth`.`clientdetails` (
`appId`
VARCHAR(128) NOT NULL,
`resourceIds`
VARCHAR(256) NULL DEFAULT NULL,
`appSecret`
VARCHAR(256) NULL DEFAULT NULL,
`scope`
VARCHAR(256) NULL DEFAULT NULL,
`grantTypes`
VARCHAR(256) NULL DEFAULT NULL,
`redirectUrl`
VARCHAR(256) NULL DEFAULT NULL,
`authorities`
VARCHAR(256) NULL DEFAULT NULL,
`access_token_validity`
INT(11) NULL DEFAULT NULL,
`refresh_token_validity`
INT(11) NULL DEFAULT NULL,
`additionalInformation`
VARCHAR(4096) NULL DEFAULT NULL,
`autoApproveScopes`
VARCHAR(256) NULL DEFAULT NULL,
PRIMARY KEY (`appId`))
ENGINE
= InnoDB
DEFAULT CHARACTER SET = utf8;
-- -----------------------------------------------------
--
Table `alan-oauth`.`oauth_access_token`
--
-----------------------------------------------------
CREATE TABLE IF NOT EXISTS `alan-oauth`.`oauth_access_token` (
`token_id`
VARCHAR(256) NULL DEFAULT NULL,
`token` BLOB
NULL DEFAULT NULL,
`authentication_id`
VARCHAR(128) NOT NULL,
`
user_name` VARCHAR(256) NULL DEFAULT NULL,
`client_id`
VARCHAR(256) NULL DEFAULT NULL,
`authentication` BLOB
NULL DEFAULT NULL,
`refresh_token`
VARCHAR(256) NULL DEFAULT NULL,
PRIMARY KEY (`authentication_id`))
ENGINE
= InnoDB
DEFAULT CHARACTER SET = utf8;
-- -----------------------------------------------------
--
Table `alan-oauth`.`oauth_approvals`
--
-----------------------------------------------------
CREATE TABLE IF NOT EXISTS `alan-oauth`.`oauth_approvals` (
`userId`
VARCHAR(256) NULL DEFAULT NULL,
`clientId`
VARCHAR(256) NULL DEFAULT NULL,
`scope`
VARCHAR(256) NULL DEFAULT NULL,
`status`
VARCHAR(10) NULL DEFAULT NULL,
`expiresAt`
DATETIME NULL DEFAULT NULL,
`lastModifiedAt`
DATETIME NULL DEFAULT NULL)
ENGINE
= InnoDB
DEFAULT CHARACTER SET = utf8;
-- -----------------------------------------------------
--
Table `alan-oauth`.`oauth_client_details`
--
-----------------------------------------------------
CREATE TABLE IF NOT EXISTS `alan-oauth`.`oauth_client_details` (
`client_id`
VARCHAR(128) NOT NULL,
`resource_ids`
VARCHAR(256) NULL DEFAULT NULL,
`client_secret`
VARCHAR(256) NULL DEFAULT NULL,
`scope`
VARCHAR(256) NULL DEFAULT NULL,
`authorized_grant_types`
VARCHAR(256) NULL DEFAULT NULL,
`web_server_redirect_uri`
VARCHAR(256) NULL DEFAULT NULL,
`authorities`
VARCHAR(256) NULL DEFAULT NULL,
`access_token_validity`
INT(11) NULL DEFAULT NULL,
`refresh_token_validity`
INT(11) NULL DEFAULT NULL,
`additional_information`
VARCHAR(4096) NULL DEFAULT NULL,
`autoapprove`
VARCHAR(256) NULL DEFAULT NULL,
PRIMARY KEY (`client_id`))
ENGINE
= InnoDB
DEFAULT CHARACTER SET = utf8;
-- -----------------------------------------------------
--
Table `alan-oauth`.`oauth_client_token`
--
-----------------------------------------------------
CREATE TABLE IF NOT EXISTS `alan-oauth`.`oauth_client_token` (
`token_id`
VARCHAR(256) NULL DEFAULT NULL,
`token` BLOB
NULL DEFAULT NULL,
`authentication_id`
VARCHAR(128) NOT NULL,
`
user_name` VARCHAR(256) NULL DEFAULT NULL,
`client_id`
VARCHAR(256) NULL DEFAULT NULL,
PRIMARY KEY (`authentication_id`))
ENGINE
= InnoDB
DEFAULT CHARACTER SET = utf8;
-- -----------------------------------------------------
--
Table `alan-oauth`.`oauth_code`
--
-----------------------------------------------------
CREATE TABLE IF NOT EXISTS `alan-oauth`.`oauth_code` (
`code`
VARCHAR(256) NULL DEFAULT NULL,
`authentication` BLOB
NULL DEFAULT NULL)
ENGINE
= InnoDB
DEFAULT CHARACTER SET = utf8;
-- -----------------------------------------------------
--
Table `alan-oauth`.`oauth_refresh_token`
--
-----------------------------------------------------
CREATE TABLE IF NOT EXISTS `alan-oauth`.`oauth_refresh_token` (
`token_id`
VARCHAR(256) NULL DEFAULT NULL,
`token` BLOB
NULL DEFAULT NULL,
`authentication` BLOB
NULL DEFAULT NULL)
ENGINE
= InnoDB
DEFAULT CHARACTER SET = utf8;

 

然后在oauth_client_details表中插入记录:

# client_id, resource_ids, client_secret, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity,

refresh_token_validity, additional_information, autoapprove
client, NULL, secret, app, authorization_code, http://www.baidu.com, NULL, NULL, NULL, NULL, NULL

 

这时就可以访问授权页面了:

localhost:8080/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://www.baidu.com

访问时Spring让你登陆,随便输入一个用户名密码即可。 注意, 如果每次登陆时输入的用户名不一样,那么Spring Security会认为是不同的用户,因此访问/token/authorize会再次显示授权页面。如果用户名一致, 则只需要授权一次

数据库连接信息在application.properties中配置。

Spring Cloud Security OAuth2 是 Spring 对 OAuth2 的开源实现,优点是能与Spring Cloud技术栈无缝集成,如果全部使用默认配置,开发者只需要添加注解就能完成 OAuth2 授权服务的搭建。

https://github.com/wanghongfei/spring-security-oauth2-example


推荐阅读
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • 本文介绍了在Linux下安装和配置Kafka的方法,包括安装JDK、下载和解压Kafka、配置Kafka的参数,以及配置Kafka的日志目录、服务器IP和日志存放路径等。同时还提供了单机配置部署的方法和zookeeper地址和端口的配置。通过实操成功的案例,帮助读者快速完成Kafka的安装和配置。 ... [详细]
author-avatar
31號_K
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有