java jwks_Java SpringBoot 如何使用 IdentityServer4 作为验证服务器学习笔记

这边记录下如何使用IdentityServer4 作为 Java SpringBoot 的 认证服务器和令牌颁发服务器。本人也是新手,所以理解不足的地方请多多指教。另外由于真的很久没有写中文了,用词不太恰当的地方也欢迎新手大佬小伙伴指出,一起进步。另外这边令牌的获取需要手动使用postman根据令牌端点获取,然后放在请求头里面通过postman发给Java的demo,本身这个demo没有取令牌的功能,请各位注意。

背景知识:什么是JWT

第一部分:IdentityServer4的服务器搭建

第二部分:Java SpringBoot框架和IDS4的结合

1.什么是Jwt?

关于什么是Jwt,包括里面的参数是什么,这边可以参考下面这个链接做一些了解:

下面这个链接是全英文的,但是对jwt是什么是比较详细的,英文好的同学可以上了。

这个链接主要说了jwt和refernce token不一样,真的很重要,里面也有些说的不对,我就被坑了:

2.IdentityServer认证&令牌颁发服务器准备

关于IdentityServer4怎么搭建使用,网上已经有太多的教程了,这边我就不多做别的讲解,因为我也是新手。但是我目前自己正在使用的是一个带UI界面的IdentityServer4和Identity(作为用户管理的部分)结合的服务器,很多东西已经帮你搭建好了,对新手可以说是十分友好,省去了探索的步骤。但是不建议新手直接使用,作为自己搭建IdentityServer后还有对IdentitySevrer4一些参数不太理解的地方,可以做进一步的理解。下面是IdentityServer4 UI 的github源码链接:

这边要是有人感兴趣配置这个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的部分:

@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throwsIOException, 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= newHttpPost(introspectEndpoint);//添加请求头

post.setHeader("Authorization", apiNameSecret);//添加请求主体(body)

List urlBodys = new ArrayList();

urlBodys.add(new BasicNameValuePair("token", token));

post.setEntity(newUrlEncodedFormEntity((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 = newBufferedReader(newInputStreamReader(response.getEntity().getContent()));//注意StringBuffer不是String

StringBuffer result = newStringBuffer();

String line= "";while ((line = rd.readLine()) != null) {

result.append(line);

}//调试用,打印得到的请求的content

System.out.println(result.toString());//-------------------------------决定authenticated结果---------------------------

JSONObject jo = newJSONObject(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的编码,请求头的一部分

publicString ApiNameSecretbase64()

{

String result= api.getapiName()+":"+api.getapiSecret();byte[] data=Base64.encodeBase64(result.getBytes());return newString(data);

}//处理token字符串,去掉Bearer

publicString 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

废话不多说先上代码:

@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throwsIOException, 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 = newDefaultJWTProcessor();//提供公钥地址来获取

JWKSource keySource = new RemoteJWKSet(newURL(jwkEndpoint));//提供解析算法,算法类型要写对,服务器用的是什么就是什么,目前是RSA256算法

JWSAlgorithm expectedJWSAlg =JWSAlgorithm.RS256;//填写 RSA 公钥来源从提供公钥地址获取那边得到

JWSKeySelector keySelector = newJWSVerificationKeySelector(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 = newJSONObject(claimsSet.toJSONObject());

String role= jo.getString("role");//试一下过期的token,删除用户的可以不试试//--------------------------------处理authenticated结果,决定是否发出401-----------

if(authenticated)

{//调用该方法后,表示过滤器经过原来的url请求处理方法

filterChain.doFilter(servletRequest, servletResponse);

}else{

rep.setStatus(HttpServletResponse.SC_UNAUTHORIZED);return;

}

}//帮助类

publicString cutToken(String originToken)

{

String[] temp= originToken.split(" ");return temp[1];

}

这边主要是用到了一个包,用法链接如下,英文好的同学可以直接研究这个链接:

https://connect2id.com/products/nimbus-jose-jwt/examples/validating-jwt-access-tokens

这个包需要import的东西还要maven依赖如下:

com.nimbusds

nimbus-jose-jwt

7.3

import com.nimbusds.jose.*;import com.nimbusds.jose.jwk.source.*;import com.nimbusds.jwt.*;importcom.nimbusds.jose.proc.JWSKeySelector;importcom.nimbusds.jose.proc.JWSVerificationKeySelector;import com.nimbusds.jwt.proc.*;

差不多是这样了,还有不是很清楚的地方直接看源代码。

你可能感兴趣的:(java,jwks)