基于spring-security-oauth2搭建授权服务器
背景:
-
需要API网关控制权限,单点登陆。
-
做前后端分离的应用,前端使用vue+elementui实现。
当前关于这方面的系统资料较少,因此大多是找寻网上零散的示例解析,结合官方文档中的demo再加上源码跟踪调试来进行学习与搭建。但由于涉及的知识点较多,且零散示例中配置或实现方式各有不同,作者经常只会记录关键、核心部分内容,因此会漏掉一些基础配置信息,给初学者带来极大困难。往往按照多个示例拼凑出来的demo无法正常运行,或者不明就里。官方给出的文档又需要具备一定英文功底。综合以上因素,这篇文章横空出世:)
既包含周边理论知识浅析,又包含实际案例demo完整代码(为了保证下载即可正常运行已附上SQL语句)
涉及知识点:
- Oauht2基础知识(授权类型,运行流程)
- 服务器证书,Keytool工具基本使用(常用命令参数解析及证书类别)
- SpringBoot项目增加https支持(https协议简单概述)
- Springsecurity知识(spring-security-oauth2是基于springsecurit的,很多配置相关都是沿用springsecurity的)
- 具体项目实现(包含服务端及客户端具体实现,鉴权、SSO)
Oauht2基础知识
既然是要搭建基于spring-security-oauth2的授权服务器,那Oauth2的基础原理和运行流程我们还是需要了解一下的,否则对于授权模式选择和认证授权流程会比较晕。对于Oauth2的介绍这里强烈推荐: 理解OAuth2.0,在这里只简单描述和摘抄部分关键信息.
核心角色介绍
-
三方客户端
-
资源所有者
-
认证服务器
-
资源服务器
为了便于理解,这里举一个大家耳熟能详的应用场景来介绍各个角色在实际应用中是如何交互的:在一个愉快的周六午后,小明同学吃完午饭就迫不及待的躺在床上,拿起手机打开吃鸡游戏(三方客户端),为了便于同朋友开黑,小明选择了微信登陆的方式,这时候吃鸡游戏(三方客户端)跳转到一个微信登陆认证页面(认证服务器),在这里小明(资源所有者)输入账号密码(身份认证),登陆成功后界面上显示:是否授权使吃鸡游戏可以访问你的微信头像、昵称、好友资料等。(授权)。授权通过后,剩下是后台处理,用户不可见(返回给客户端一个授权码,客户端拿到授权码结合开始申请授权时的APPKEY申请TOKEN,通过后客户端凭着这个TOKEN去资源服务器获取用户头像、昵称等信息)
客户端的授权模式
-
授权码模式(authorization code)
-
简化模式(implicit)
-
密码模式(resource owner password credentials)
-
客户端模式(client credentials)
关于这4种授权模式的区别,详细内容请参见[OAuth2.0](http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html "OAuth2.0"),在这里我给出了自己比较土的解释:
授权码模式:安全性最高,也是比较常用的方式,但是整个流程最长。运行流程见上面举的“吃鸡”游戏登陆授权例子。
简化模式: 跳过了获取授权码的环节,后续流程同授权码模式。
密码模式:把账号密码给到客户端程序,由客户端程序去请求认证服务器获取token,这种方式客户端是能够获取到用户名、密码信息的。
客户端模式:这种其实就和用户授权没什么关系,需要认证的主体是客户端本身了,客户端本身以自己的名义去找认证服务器要授权。
服务器证书,Keytool工具基本使用
涉及到鉴权,当然少不了https的应用。在使用https前,需要创建服务器证书,关于证书这部分的详细内容请参见:[HTTPS与SSL证书概要](http://www.runoob.com/w3cnote/https-ssl-intro.html "HTTPS与SSL证书概要")
本文中,选择使用keytool工具来自己动手生成一个。由于springboot默认只支持jks和p12格式的。在此我们选择生成jks格式的证书,命令如下(需要注意的是得用管理员权限打开cmd命令行,否则生成keystore文件会失败,报filenotfound错误)
- 选用JKS的证书,生成语句:
keytool -genkeypair -alias server -keyalg RSA -validity 3650 -keystore server.jks
其中需要特别注意到的是,在输入以上命令后,会要求你写名称、区域等信息,如下图
这里的姓氏、名称是你的域名,如果域名与证书内容不符,访问时将报SSLHandshakeException: 由此可知是ssl 握手失败!的错误.
- 导出cer证书(给到第三步导入用的)
keytool -export -alias server -keystore server.jks -rfc -file server.cer - 导入到jdk中
keytool -import -alias server -file server.cer -keystore cacerts
生成文件如下:
踩坑爬坑
Bad Request This combination of host and port requires TLS
使用 http://localhost:8081/oauth/authorize?client_id=erpmanager&response_type=code&redirect_uri=www.forever24.cn 获取code码时返回报错信息
解决方案:返回信息告诉我们,表示需要通过https来访问,用https://localhost:8081/oauth/authorize?client_id=testcode&response_type=code&redirect_uri=www.forever24.cn访问结果如下:
选择信任后,转到登陆页面如下,这个登陆页面可以直接替换成自己的(需要自己写登陆页面,否则默认就是这个简陋的)
client信息读取有in-memory,jdbc等多种方式,这里采用的是jdbc方式,简单来说就是自己从数据库中查询看然后跟用户输入的账号密码比对,需要自己重写UserService部分
输入账号密码后,满怀期待😊 ,but:又报错了,奶奶的熊,明明输入的账号密码是正确的为什么还报 Handling ClientRegistrationException error: No client with requested id: erpmanager 的错呢?
而且,自己写的 SupplierInfo supplierInfo = supplierManRepository.findByuserName(username); 查询user信息中,明明用户名密码填写是对的,也有erpmanager这个用户 根据报错信息,显示的是2019-01-10 11:22:15.974 INFO 9444 --- [nio-8081-exec-4] o.s.s.o.p.e.AuthorizationEndpoint : Handling ClientRegistrationException error: No client with requested id: erpmanager
根据关键字搜索可以找到,报错信息是从org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint
报出来的,并且访问的是oauth/authorize
,可以找到这个url对应的处理方法。
获取code码,身份验证的代码在:
获取token的入口代码在:
Full authentication is required to access this resourceunauthorized
数据库表说明(使用spring-oauth-server的系统表):http://andaily.com/spring-oauth-server/db_table_description.html
关于SecurityConfig 的配置,需要先了解 构造器模式,否则比较难以理解。可以参见 https://www.cnblogs.com/iCanhua/p/8636085.html
@Override
protected void configure(HttpSecurity http) throws Exception { // @formatter:off
http.requestMatchers().antMatchers("/login", "/oauth/authorize")
.and().authorizeRequests().anyRequest().authenticated()
.and().formLogin().permitAll();
} // @formatter:on
豁然开朗,查下数据库的[oauth_client_details]表,发现根本没有设置 erpmanager 这个用户。