這邊記錄下如何使用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.*;
差很少是這樣了,還有不是很清楚的地方直接看源代碼。