这边记录下如何使用IdentityServer4 做为 Java SpringBoot 的 认证服务器和令牌颁发服务器。本人也是新手,因此理解不足的地方请多多指教。另外因为真的好久没有写中文了,用词不太恰当的地方也欢迎新手大佬小伙伴指出,一块儿进步。另外这边令牌的获取须要手动使用postman根据令牌端点获取,而后放在请求头里面经过postman发给Java的demo,自己这个demo没有取令牌的功能,请各位注意。html
关于什么是Jwt,包括里面的参数是什么,这边能够参考下面这个连接作一些了解:java
https://www.cnblogs.com/zjutzz/p/5790180.htmlgit
下面这个连接是全英文的,可是对jwt是什么是比较详细的,英文好的同窗能够上了。github
https://tools.ietf.org/html/rfc7519算法
这个连接主要说了jwt和refernce token不同,真的很重要,里面也有些说的不对,我就被坑了:api
https://www.cnblogs.com/Irving/articles/9357539.html服务器
关于IdentityServer4怎么搭建使用,网上已经有太多的教程了,这边我就很少作别的讲解,由于我也是新手。可是我目前本身正在使用的是一个带UI界面的IdentityServer4和Identity(做为用户管理的部分)结合的服务器,不少东西已经帮你搭建好了,对新手能够说是十分友好,省去了探索的步骤。可是不建议新手直接使用,做为本身搭建IdentityServer后还有对IdentitySevrer4一些参数不太理解的地方,能够作进一步的理解。下面是IdentityServer4 UI 的github源码连接:app
https://github.com/skoruba/IdentityServer4.Admin框架
关于如何开启一个新的项目,这边就很少说了,网上教程不少,咱们直接进入正题,这边我用的Intellij IDEA。而后注册了过滤器,而后这边提供了两种办法验证jwt:maven
因为没有客户端,这边用postman代替求取token,使用http://x.x.x.x:5000/connect/token,而后给咱们的java程序发起请求。
这个就十分简单了,上代码,主要是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这个布尔量,来肯定这个令牌是否合法。
废话很少说先上代码:
@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.*;
差很少是这样了,还有不是很清楚的地方直接看源代码。