1. 程式人生 > >SpringBoot 整合 JWT 驗證+解決域名問題

SpringBoot 整合 JWT 驗證+解決域名問題

什麼是JWT JSON Web Token(JWT)是一個開放的標準(RFC 7519),它定義了一個緊湊且自包含的方式,用於在各方之間以JSON物件安全地傳輸資訊。這些資訊可以通過數字簽名進行驗證和信任。可以使用祕密(使用HMAC演算法)或使用RSA的公鑰/私鑰對來對JWT進行簽名。  具體的jwt介紹可以檢視官網的介紹:https://jwt.io/introduction/

jwt請求流程 引用官網的圖片 

中文介紹:  1. 使用者使用賬號和麵發出post請求;  2. 伺服器使用私鑰建立一個jwt;  3. 伺服器返回這個jwt給瀏覽器;  4. 瀏覽器將該jwt串在請求頭中像伺服器傳送請求;  5. 伺服器驗證該jwt;  6. 返回響應的資源給瀏覽器。

jwt組成 jwt含有三部分:頭部(header)、載荷(payload)、簽證(signature)

頭部(header) 頭部一般有兩部分資訊:宣告型別、宣告加密的演算法(通常使用HMAC SHA256)  頭部一般使用base64加密:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9  解密後:

{     "typ":"JWT",     "alg":"HS256" } 載荷(payload) 該部分一般存放一些有效的資訊。jwt的標準定義包含五個欄位:  - iss:該JWT的簽發者  - sub: 該JWT所面向的使用者  - aud: 接收該JWT的一方  - exp(expires): 什麼時候過期,這裡是一個Unix時間戳  - iat(issued at): 在什麼時候簽發的  這個只是JWT的定義標準,不強制使用。另外自己也可以新增一些公開的不涉及安全的方面的資訊。

簽證(signature) JWT最後一個部分。該部分是使用了HS256加密後的資料;包含三個部分:  - header (base64後的)  - payload (base64後的)  - secret 私鑰

secret是儲存在伺服器端的,jwt的簽發生成也是在伺服器端的,secret就是用來進行jwt的簽發和jwt的驗證,所以,它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味著客戶端是可以自我簽發jwt了。

在SpringBoot專案中應用 首先需要新增JWT的依賴:

    <dependency>         <groupId>io.jsonwebtoken</groupId>         <artifactId>jjwt</artifactId>         <version>0.6.0</version>     </dependency>

接下來在配置檔案中新增JWT的配置資訊:

##jwt配置 audience:   clientId: 098f6bcd4621d373cade4e832627b4f6   base64Secret: MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY=   name: restapiuser   expiresSecond: 172800

配置資訊的實體類,以便獲取jwt的配置:

@Data @ConfigurationProperties(prefix = "audience") @Component public class Audience {

    private String clientId;     private String base64Secret;     private String name;     private int expiresSecond;

} JWT驗證主要是通過攔截器驗證,所以我們需要新增一個攔截器來驗證請求頭中是否含有後臺頒發的token,這裡請求頭的格式:這裡bearer;後面就是伺服器頒發的token

public class JwtFilter extends GenericFilterBean {

    @Autowired     private Audience audience;

    /**      *  Reserved claims(保留),它的含義就像是程式語言的保留字一樣,屬於JWT標準裡面規定的一些claim。JWT標準裡面定好的claim有:

     iss(Issuser):代表這個JWT的簽發主體;      sub(Subject):代表這個JWT的主體,即它的所有人;      aud(Audience):代表這個JWT的接收物件;      exp(Expiration time):是一個時間戳,代表這個JWT的過期時間;      nbf(Not Before):是一個時間戳,代表這個JWT生效的開始時間,意味著在這個時間之前驗證JWT是會失敗的;      iat(Issued at):是一個時間戳,代表這個JWT的簽發時間;      jti(JWT ID):是JWT的唯一標識。      * @param req      * @param res      * @param chain      * @throws IOException      * @throws ServletException      */     @Override     public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain)             throws IOException, ServletException {

        final HttpServletRequest request = (HttpServletRequest) req;         final HttpServletResponse response = (HttpServletResponse) res;         //等到請求頭資訊authorization資訊         final String authHeader = request.getHeader("authorization");

        if ("OPTIONS".equals(request.getMethod())) {             response.setStatus(HttpServletResponse.SC_OK);             chain.doFilter(req, res);         } else {

            if (authHeader == null || !authHeader.startsWith("bearer;")) {                 throw new LoginException(ResultEnum.LOGIN_ERROR);             }             final String token = authHeader.substring(7);

            try {                 if(audience == null){                     BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());                     audience = (Audience) factory.getBean("audience");                 }                 final Claims claims = JwtHelper.parseJWT(token,audience.getBase64Secret());                 if(claims == null){                     throw new LoginException(ResultEnum.LOGIN_ERROR);                 }                 request.setAttribute(Constants.CLAIMS, claims);             } catch (final Exception e) {                 throw new LoginException(ResultEnum.LOGIN_ERROR);             }

            chain.doFilter(req, res);         }     } } 註冊JWT攔截器,可以在配置類中,也可以直接在SpringBoot的入口類中

    @Bean     public FilterRegistrationBean jwtFilter() {         final FilterRegistrationBean registrationBean = new FilterRegistrationBean();         registrationBean.setFilter(new JwtFilter());         //新增需要攔截的url         List<String>  urlPatterns = Lists.newArrayList();         urlPatterns.add("/article/insert");         registrationBean.addUrlPatterns(urlPatterns.toArray(new String[urlPatterns.size()]));         return registrationBean;     }

登入處理,也就是jwt的頒發

  @PostMapping("login")     public ResultVo login(@RequestParam(value = "usernameOrEmail", required = true) String usernameOrEmail,                           @RequestParam(value = "password", required = true) String password,                           HttpServletRequest request) {         Boolean is_email = MatcherUtil.matcherEmail(usernameOrEmail);         User user = new User();         if (is_email) {             user.setEmail(usernameOrEmail);         } else {             user.setUsername(usernameOrEmail);         }         User query_user = userService.get(user);         if (query_user == null) {             return ResultVOUtil.error("400", "使用者名稱或郵箱錯誤");         }         //驗證密碼         PasswordEncoder encoder = new BCryptPasswordEncoder();         boolean is_password = encoder.matches(password, query_user.getPassword());         if (!is_password) {             //密碼錯誤,返回提示             return ResultVOUtil.error("400", "密碼錯誤");         }

       String jwtToken = JwtHelper.createJWT(query_user.getUsername(),                                            query_user.getId(),                                            query_user.getRole().toString(),                                            audience.getClientId(),                                            audience.getName(),                                            audience.getExpiresSecond()*1000,                                            audience.getBase64Secret());

        String result_str = "bearer;" + jwtToken;         return ResultVOUtil.success(result_str);     }

這裡將jwt的頒發處理抽離出來了,JWT工具類:

public class JwtHelper {

    /**      * 解析jwt      */     public static Claims parseJWT(String jsonWebToken, String base64Security){         try         {             Claims claims = Jwts.parser()                     .setSigningKey(DatatypeConverter.parseBase64Binary(base64Security))                     .parseClaimsJws(jsonWebToken).getBody();             return claims;         }         catch(Exception ex)         {             return null;         }     }

    /**      * 構建jwt      */     public static String createJWT(String name, String userId, String role,                                    String audience, String issuer, long TTLMillis, String base64Security)     {         SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        long nowMillis = System.currentTimeMillis();         Date now = new Date(nowMillis);

        //生成簽名金鑰         byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(base64Security);         Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

        //新增構成JWT的引數         JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")                 .claim("role", role)                 .claim("unique_name", name)                 .claim("userid", userId)                 .setIssuer(issuer)                 .setAudience(audience)                 .signWith(signatureAlgorithm, signingKey);         //新增Token過期時間         if (TTLMillis >= 0) {             long expMillis = nowMillis + TTLMillis;             Date exp = new Date(expMillis);             builder.setExpiration(exp).setNotBefore(now);         }

        //生成JWT         return builder.compact();     } }

最後,jwt可能會出現跨域的問題,所以最好新增一下對跨域的處理

@Configuration public class CorsConfig {

    @Bean     public FilterRegistrationBean corsFilter() {         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();         CorsConfiguration config = new CorsConfiguration();         config.setAllowCredentials(true);         config.addAllowedOrigin("*");         config.addAllowedHeader("*");         config.addAllowedMethod("OPTIONS");         config.addAllowedMethod("HEAD");         config.addAllowedMethod("GET");         config.addAllowedMethod("PUT");         config.addAllowedMethod("POST");         config.addAllowedMethod("DELETE");         config.addAllowedMethod("PATCH");         source.registerCorsConfiguration("/**", config);         final FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));         bean.setOrder(0);         return bean;     }

    @Bean     public WebMvcConfigurer mvcConfigurer() {         return new WebMvcConfigurerAdapter() {             @Override             public void addCorsMappings(CorsRegistry registry) {                 registry.addMapping("/**").allowedMethods("GET", "PUT", "POST", "GET", "OPTIONS");             }         };     } } ---------------------  作者:wqh3520  來源:CSDN  原文:https://blog.csdn.net/wqh8522/article/details/78953379