Java SpringBoot 如何使用 IdentityServer4 作为验证学习笔记
这边记录下如何使用IdentityServer4 作为 Java SpringBoot 的 认证服务器和令牌颁发服务器。本人也是新手,所以理解不足的地方请多多指教。另外由于真的很久没有写中文了,用词不太恰当的地方也欢迎新手大佬小伙伴指出,一起进步。另外这边令牌的获取需要手动使用postman根据令牌端点获取,然后放在请求头里面通过postman发给Java的demo,本身这个demo没有取令牌的功能,请各位注意。
- 背景知识:什么是JWT
- 第一部分:IdentityServer4的服务器搭建
- 第二部分:Java SpringBoot框架和IDS4的结合
- SpringBoot Demo 源码:https://github.com/Danni-Ke/SpringBootDemo
1.什么是Jwt?
关于什么是Jwt,包括里面的参数是什么,这边可以参考下面这个链接做一些了解:
https://www.cnblogs.com/zjutzz/p/5790180.html
下面这个链接是全英文的,但是对jwt是什么是比较详细的,英文好的同学可以上了。
https://tools.ietf.org/html/rfc7519
这个链接主要说了jwt和refernce token不一样,真的很重要,里面也有些说的不对,我就被坑了:
https://www.cnblogs.com/Irving/articles/9357539.html
2.IdentityServer认证&令牌颁发服务器准备
关于IdentityServer4怎么搭建使用,网上已经有太多的教程了,这边我就不多做别的讲解,因为我也是新手。但是我目前自己正在使用的是一个带UI界面的IdentityServer4和Identity(作为用户管理的部分)结合的服务器,很多东西已经帮你搭建好了,对新手可以说是十分友好,省去了探索的步骤。但是不建议新手直接使用,作为自己搭建IdentityServer后还有对IdentitySevrer4一些参数不太理解的地方,可以做进一步的理解。下面是IdentityServer4 UI 的github源码链接:
https://github.com/skoruba/IdentityServer4.Admin
- 这边要是有人感兴趣配置这个IdentityServer4 UI,后期也会记录下相对的这个事怎么搭建的。这个IdentityServer上面在配置相关的信息,比如API,Client,还有用户资源之后,我们会用到如下端点:
- http://x.x.x.x:5000/connect/token 请求令牌的端口,需要提供客户端id,客户端secret,用户名字,用户密码,还有授权方式,这里我选的grant_type是 password。
- http://x.x.x.x:5000/connect/introspect 令牌自省端点,很多国内的说法是用于refenrece token,然后还有很多大佬翻译的官方文档根本就是没认真翻译(估计也不知道实际意思)。这个端点实际上可以用于那些没由相应的包或者library可以用于解析jwt令牌的程序来验证令牌的合法性。只是注意这边唯一不同的是,对于renfence token和jwt token 要发给这个端点的参数是不一样的。对于reference,要发的是 client_id 和 secret。但是对于 jwt token,要发的是 base64编码在请求头部的 Api_name 和 Api_secret, 这里就是为什么 Api有secret这个参数,但是我们几乎没有用到过。
- http://x.x.x.x:5000/.well-known/openid-configuration/jwks (公钥开放端点) 用于获取解析jwt令牌的公钥开放端点。
3.Java SpringBoot
关于如何开启一个新的项目,这边就不多说了,网上教程很多,我们直接进入正题,这边我用的Intellij IDEA。然后注册了过滤器,然后这边提供了两种办法验证jwt:
- 通过自省端点返回验证结果,使用http://x.x.x.x:5000/connect/introspect
- 通过公钥开放端点本地解析token,使用http://x.x.x.x:5000/.well-known/openid-configuration/jwks
由于没有客户端,这边用postman代替求取token,使用http://x.x.x.x:5000/connect/token,然后给我们的java程序发起请求。
3.1通过自省端点返回验证结果
这个就十分简单了,上代码,主要是Filter里面dofiler的部分:
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("开始进行过滤请求,由认证服务器自省端点验证token");
boolean authenticated = false;
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse rep = (HttpServletResponse) servletResponse; //--------------给自省端点发送请求-------------------------------
//--------------准备请求信息----------------------------------------
//其实一个url请求就是一组组键对值,getHeader()方法获取的是头部的你想要的
//键名后面的值,由于请求里面token的keyname是这个,倒是要是要改这里也要改
//这里面header要是没有token这个就不行,会异常
boolean authorizationHeaderExist = req.getHeader("Authorization") != null;
if (!authorizationHeaderExist) {
rep.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return;
} String token = cutToken(req.getHeader("Authorization"));
//获取编码后的ApiSecret和ApiName,在application.propertiesz中
String apiNameSecret = "Basic " + ApiNameSecretbase64();
//倒是可以放到配置里面去,那里统一改
String introspectEndpoint = "http://localhost:5000/connect/introspect"; //-------------创造请求----------------------------------------------
//protected HttpClient client = new DefaultHttpClient();已过时
HttpClient client = HttpClientBuilder.create().build();
HttpPost post = new HttpPost(introspectEndpoint);
//添加请求头
post.setHeader("Authorization", apiNameSecret);
//添加请求主体(body)
List<NameValuePair> urlBodys = new ArrayList<NameValuePair>();
urlBodys.add(new BasicNameValuePair("token", token));
post.setEntity(new UrlEncodedFormEntity((urlBodys)));
HttpResponse response = client.execute(post); System.out.println("\nSending 'POST' request to URL : " + introspectEndpoint);
System.out.println("Post parameters : " + post.getEntity());
System.out.println("Response Code : " +
response.getStatusLine().getStatusCode());
//读取返回reponse的content的信息,含有决定结果
BufferedReader rd = new BufferedReader(
new InputStreamReader(response.getEntity().getContent()));
//注意StringBuffer不是String
StringBuffer result = new StringBuffer();
String line = "";
while ((line = rd.readLine()) != null) {
result.append(line);
}
//调试用,打印得到的请求的content
System.out.println(result.toString());
//-------------------------------决定authenticated结果---------------------------
JSONObject jo = new JSONObject(result.toString());
Boolean active = jo.getBoolean("active"); if (response.getStatusLine().getStatusCode() == 200&& active==true)
{
String role = jo.getString("role");
authenticated = true;
}
//--------------------------------处理authenticated结果,决定是否发出401-----------
if (authenticated)
{
//调用该方法后,表示过滤器经过原来的url请求处理方法
filterChain.doFilter(servletRequest, servletResponse);
} else {
rep.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
} //返回Api名字和secret的编码,请求头的一部分
public String ApiNameSecretbase64()
{
String result = api.getapiName()+":"+api.getapiSecret();
byte[] data=Base64.encodeBase64(result.getBytes());
return new String(data);
}
//处理token字符串,去掉Bearer
public String cutToken(String originToken)
{
String[] temp = originToken.split(" ");
return temp[1];
}
上面的 ApiNameSecretbase64 的功能是读取配置中的信息,返回编码好的 Api Name 和 Secret, 下面是我application.properties相关的配置,同样这些配置需要放到IdentityServer那边,可以是通过内存的方式也可以是通过像我一样使用 UI管理界面直接添加:
#IdentityServer4 配置文件参数
api.name = Api1
api.secret=secreta
同时启动java项目和IdentityServer4之后,请求就可以看到结果了,如果没有带token就会是无授权,这边就不放postman的结果了,因为本篇主要侧重于代码。关于请求这个端点的效果,返回格式参考官方文档,各位自己可以做本地相应的基于回复的验证方式。我这边直接提取了里面active这个布尔量,来确定这个令牌是否合法。
3.2通过请求公钥本地解析返回验证token
废话不多说先上代码:
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException
{
System.out.println("开始进行过滤请求,由认证服务器jwk公钥解析验证token");
boolean authenticated = false;
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse rep = (HttpServletResponse) servletResponse;
boolean authorizationHeaderExist = req.getHeader("Authorization") != null;
if (!authorizationHeaderExist) {
rep.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return;
}
String jwkEndpoint = "http://localhost:5000/.well-known/openid-configuration/jwks";
//String token = cutToken(((HttpServletRequest) servletRequest).getHeader("Authorization"));
String token = cutToken(req.getHeader("Authorization")); //------------解析------------------------------------------------------
//com.nimbusds JWT解析包,这个包目前没有找到源代码,
//https://connect2id.com/products/nimbus-jose-jwt/examples/validating-jwt-access-tokens
//建立解析处理对象
ConfigurableJWTProcessor jwtProcessor = new DefaultJWTProcessor();
//提供公钥地址来获取
JWKSource keySource = new RemoteJWKSet(new URL(jwkEndpoint));
//提供解析算法,算法类型要写对,服务器用的是什么就是什么,目前是RSA256算法
JWSAlgorithm expectedJWSAlg = JWSAlgorithm.RS256;
//填写 RSA 公钥来源从提供公钥地址获取那边得到
JWSKeySelector keySelector = new JWSVerificationKeySelector(expectedJWSAlg, keySource);
if(keySelector==null)
{
rep.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
System.out.println("无法获取公钥。");
return;
}
//设置第一步建立的解析处理对象
jwtProcessor.setJWSKeySelector(keySelector);
//处理收到的token(令牌),错误则返回对象
SecurityContext ctx = null;
JWTClaimsSet claimsSet = null;
try {
claimsSet = jwtProcessor.process(token, ctx);
authenticated = true;
} catch (ParseException e) {
rep.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
e.printStackTrace();
return;
} catch (BadJOSEException e) {
rep.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
e.printStackTrace();
return;
} catch (JOSEException e) {
rep.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
e.printStackTrace();
return;
}
//调试用,打印出来
System.out.println(claimsSet.toJSONObject());
//失败返回无授权
if(claimsSet==null) {
rep.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
//解码里面具体内容,尤其角色,虽然这里不需要,顺利取出
JSONObject jo = new JSONObject(claimsSet.toJSONObject());
String role = jo.getString("role");
//试一下过期的token,删除用户的可以不试试
//--------------------------------处理authenticated结果,决定是否发出401-----------
if (authenticated)
{
//调用该方法后,表示过滤器经过原来的url请求处理方法
filterChain.doFilter(servletRequest, servletResponse);
} else {
rep.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
} //帮助类
public String cutToken(String originToken)
{
String[] temp = originToken.split(" ");
return temp[1];
}
这边主要是用到了一个包,用法链接如下,英文好的同学可以直接研究这个链接:
https://connect2id.com/products/nimbus-jose-jwt/examples/validating-jwt-access-tokens
这个包需要import的东西还要maven依赖如下:
<!-- https://mvnrepository.com/artifact/com.nimbusds/nimbus-jose-jwt -->
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>7.3</version>
</dependency>
import com.nimbusds.jose.*;
import com.nimbusds.jose.jwk.source.*;
import com.nimbusds.jwt.*;
import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jwt.proc.*;
差不多是这样了,还有不是很清楚的地方直接看源代码。
Java SpringBoot 如何使用 IdentityServer4 作为验证学习笔记的更多相关文章
- Java基础及JavaWEB以及SSM框架学习笔记Xmind版
Java基础及JavaWEB以及SSM框架学习笔记Xmind版 转行做程序员也1年多了,最近开始整理以前学习过程中记录的笔记,以及一些容易犯错的内容.现在分享给网友们.笔记共三部分. JavaSE 目 ...
- JAVA中 XML与数据库互转 学习笔记三
要求 必备知识 JAVA基础知识,XML基础知识,数据库的基本操作. 开发环境 MyEclipse10/MySql5.5 资料下载 源码下载 数据库在数据查询,修改,保存,安全等方面与其他数据处理 ...
- Java SpringBoot 实体类数据自动验证
package demo.dto; import org.hibernate.validator.constraints.Length; import javax.validation.constra ...
- JAVA高并发秒杀API项目的学习笔记
一步一步的搭建JAVA WEB项目,采用Maven构建,基于MYBatis+Spring+Spring MVC+Bootstrap技术的秒杀项目学习的视频:http://www.imooc.com/l ...
- java写个自己的mvc框架学习笔记
1. 介绍 MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑.数据.界面显示分离的 ...
- MVC2 扩展Models和自定义验证(学习笔记)
当我们利用Visual Studio生成实体类以后,难免会用到验证功能(例如,用户登录时验证用户名是否为空,并加以显示). Visual Studio实体类:实体类 如果直接去编辑Visual Stu ...
- 1.JAVA中使用JNI调用C++代码学习笔记
Java 之JNI编程1.什么是JNI? JNI:(Java Natibe Inetrface)缩写. 2.为什么要学习JNI? Java 是跨平台的语言,但是在有些时候仍然是有需要调用本地代码 ( ...
- JAVA Web day02--- Android小白的第二天学习笔记
CSS(美工部分知识,了解) 1. CSS概述 1.1.CSS是什么? * CSS 指层叠样式表 样式表:存储样式的地方 层叠:一层一层叠加 高大富有帅气人 1.2.CSS有什么作用? *CSS就是用 ...
- Java马士兵高并发编程视频学习笔记(二)
1.ReentrantLock的简单使用 Reentrant n.再进入 ReentrantLock 一个可重入互斥Lock具有与使用synchronized方法和语句访问的隐式监视锁相同的基本行为和 ...
随机推荐
- 为什么腾讯总能做出好产品?(在互联网行业,往往仅凭一个关键产品就足以改变整个公司的格局)MSN失败在不以用户体验为中心
投递人 itwriter 发布于 2017-07-10 11:16 评论(36) 有3401人阅读 原文链接 [收藏] « » 本文来自微信公众号“郑志昊 Peter”,作者李翔.郑志昊:博客园经授权 ...
- C#基础:ref和out的区别 [转]
ref和out的区别在C# 中,既可以通过值也可以通过引用传递参数.通过引用传递参数允许函数成员更改参数的值,并保持该更改.若要通过引用传递参数, 可使用ref或out关键字.ref和out这两个关键 ...
- Objective
1.NSSet 1.是一个无序的,管理多个对象的集合类,最大特点 是集合中不允许出现重复对象,和数学上的集合含义是一 样的 2.除了无序.不许重复之外,其它功能和NSArray是一样的 2.NSArr ...
- Win8 Metro(C#)数字图像处理--2.60部分彩色保留算法
原文:Win8 Metro(C#)数字图像处理--2.60部分彩色保留算法 [函数名称] 部分彩色保留函数 WriteableBitmap PartialcolorProcess ...
- Win8Metro(C#)数字图像处理--2.21二值图像腐蚀
原文:Win8Metro(C#)数字图像处理--2.21二值图像腐蚀 [函数名称] 二值图像腐蚀函数CorrosionProcess(WriteableBitmap src) [算法说明] 二值 ...
- 问题记录,Release模式和Debug运行效果不一样,Release必须加延时
这个程序大体是这样一个逻辑,通过win32程序与设备交互,主线程先向设备发送命令要求 循环验证 然后一个线程专门负责接收设备返回信息 两边通过全局变量的变化来交流,主线程通过接收线程收到的信息设置界面 ...
- 更换Qt QtEmbedded库的版本出现问题及解决(交叉编译OpenSSL)
近日将QtEmbedded库的版本由4.7.0更新到4.7.4.工具链并未改变,仍为 Target: arm-none-linux-gnueabiConfigured with: ......Thre ...
- Visual C++ 异常(Exception)常见问题 (原文标题:A Visual C++ Exception FAQ)
Visual C++ 异常(Exception)常见问题 版权:Doug Harrison 2001 – 2007 翻译:magictong(童磊) 2011年3月 原文地址:http://membe ...
- ngnix 安装
1安装PCRE库 ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/ 下载最新的 PCRE 源码包,使用下面命令下载编译和安装 PCRE 包: ...
- Python 爬虫从入门到进阶之路(六)
在之前的文章中我们介绍了一下 opener 应用中的 ProxyHandler 处理器(代理设置),本篇文章我们再来看一下 opener 中的 Cookie 的使用. Cookie 是指某些网站服务器 ...