SpringBoot整合Token

JWT

什麼是JSON Web Token?

JSON Web Token(JWT)是一個開放標準(RFC 7519),它定義了一種緊湊且獨立的方式,用於在各方之間做爲JSON對象安全地傳輸信息。此信息能夠經過數字簽名進行驗證和信任。JWT可使用祕密(使用HMAC算法)或使用RSAECDSA的公鑰/私鑰對進行簽名html

雖然JWT能夠加密以在各方之間提供保密,但咱們將專一於簽名令牌。簽名令牌能夠驗證其中包含的聲明的完整性,而加密令牌則隱藏其餘方的聲明。當使用公鑰/私鑰對簽名令牌時,簽名還證實只有持有私鑰的一方是簽署它的一方。java

何時應該使用JSON Web令牌?

如下是JSON Web令牌有用的一些場景:算法

  • 受權:這是使用JWT的最多見方案。一旦用戶登陸,每一個後續請求將包括JWT,容許用戶訪問該令牌容許的路由,服務和資源。Single Sign On是一種如今普遍使用JWT的功能,由於它的開銷很小,而且可以在不一樣的域中輕鬆使用。
  • 信息交換:JSON Web令牌是在各方之間安全傳輸信息的好方法。由於JWT能夠簽名 - 例如,使用公鑰/私鑰對 - 您能夠肯定發件人是他們所說的人。此外,因爲使用標頭和有效負載計算簽名,您還能夠驗證內容是否未被篡改。

什麼是JSON Web令牌結構?

在緊湊的形式中,JSON Web Tokens由dot(.)分隔的三個部分組成,它們是:json

  • Header
  • Payload
  • Signature

所以,JWT一般以下所示。api

xxxxx.yyyyy.zzzzz
複製代碼

讓咱們分解不一樣的部分。瀏覽器

Header

標頭一般由兩部分組成:令牌的類型,即JWT,以及正在使用的簽名算法,例如HMAC SHA256或RSA。安全

例如:bash

{
  "alg": "HS256",
  "typ": "JWT"
}
複製代碼

而後,這個JSON被編碼爲Base64Url,造成JWT的第一部分。服務器

Payload

令牌的第二部分是payload,其中包含聲明。聲明是關於實體(一般是用戶)和其餘數據的聲明。聲明有三種類型:註冊公開私有聲明app

  • 已註冊的聲明:這些是一組預約義聲明,不是強制性的,但建議使用,以提供一組有用的,可互操做的聲明。其中一些是:iss (issuer), exp (expiration time), sub (subject), aud(audience)等。

    請注意,聲明名稱只有三個字符,由於JWT意味着緊湊。

  • 公開聲明:這些能夠由使用JWT的人隨意定義。但爲避免衝突,應在 IANA JSON Web令牌註冊表中定義它們,或者將其定義爲包含防衝突命名空間的URI。

  • 私有聲明:私有聲明是提供者和消費者所共同定義的聲明,通常不建議存放敏感信息,由於base64是對稱解密的,意味着該部分信息能夠歸類爲明文信息。

示例payload能夠是:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}
複製代碼

而後,payload通過Base64Url編碼,造成JSON Web令牌的第二部分。

請注意,對於簽名令牌,此信息雖然能夠防止被篡改,但任何人均可以讀取。除非加密,不然不要將祕密信息放在JWT的payload或header中。

Signature

要建立簽名部分,必須採用 header, payload, secret,標頭中指定的算法,並對其進行簽名。

例如,若是要使用HMAC SHA256算法,將按如下方式建立簽名:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)
複製代碼

簽名用於驗證消息在此過程當中未被更改,而且,在使用私鑰簽名的令牌的狀況下,它還能夠驗證JWT的發件人是不是它所聲稱的人。

所有放在一塊兒

輸出是三個由點分隔的Base64-URL字符串,能夠在HTML和HTTP環境中輕鬆傳遞,而與基於XML的標準(如SAML)相比更加緊湊。

下面顯示了一個JWT,它具備先前的頭和有效負載編碼,並使用機密簽名。

編碼JWT

JWT工做原理

1)用戶登錄的時候使用用戶名和密碼發送POST請求。

2)服務器使用私鑰建立一個jwt。

3)服務器返回這個jwt到瀏覽器。

4)瀏覽器將該jwt串加入請求頭中向服務器發送請求。

5)服務器驗證jwt。

6)返回相應的資源給客戶端。

SpringBoot集成

引入依賴

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.4.0</version>
</dependency>
複製代碼

定義註解

PassToken:跳過驗證。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
    boolean required() default true;
}
複製代碼

UserLoginToken:須要驗證。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
    boolean required() default true;
}
複製代碼

定義實體類

public class UserDo {
    private String id;

    private String userName;

    private String userPasswd;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id == null ? null : id.trim();
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName == null ? null : userName.trim();
    }

    public String getUserPasswd() {
        return userPasswd;
    }

    public void setUserPasswd(String userPasswd) {
        this.userPasswd = userPasswd == null ? null : userPasswd.trim();
    }
}
複製代碼

編寫TokenServiceImpl

@Service
public class TokenServiceImpl implements TokenService {
    @Override
    public String getToken(UserDo userDo) {
        String token="";
        token= JWT.create().withAudience(userDo.getId())
                .sign(Algorithm.HMAC256(userDo.getUserPasswd()));
        return token;
    }
}

複製代碼

編寫攔截器

public class AuthenticationInterceptor implements HandlerInterceptor {
    @Reference
    UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
        String token = httpServletRequest.getHeader("token");// 從 http 請求頭中取出 token
        // 若是不是映射到方法直接經過
        if (!(object instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) object;
        Method method = handlerMethod.getMethod();
        //檢查是否有passtoken註釋,有則跳過認證
        if (method.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if (passToken.required()) {
                return true;
            }
        }
        //檢查有沒有須要用戶權限的註解
        if (method.isAnnotationPresent(UserLoginToken.class)) {
            UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
            if (userLoginToken.required()) {
                // 執行認證
                if (token == null) {
                    throw new RuntimeException("無token,請從新登陸");
                }
                // 獲取 token 中的 user id
                String userId;
                try {
                    userId = JWT.decode(token).getAudience().get(0);
                } catch (JWTDecodeException j) {
                    throw new RuntimeException("401");
                }
                UserDo user = userService.findUserById(userId);
                if (user == null) {
                    throw new RuntimeException("用戶不存在,請從新登陸");
                }
                // 驗證 token
                JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getUserPasswd())).build();
                try {
                    jwtVerifier.verify(token);
                } catch (JWTVerificationException e) {
                    throw new RuntimeException("401");
                }
                return true;
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest,
                           HttpServletResponse httpServletResponse,
                           Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest,
                                HttpServletResponse httpServletResponse,
                                Object o, Exception e) throws Exception {
    }
}

複製代碼

流程圖以下:

配置攔截器

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationInterceptor())
                .addPathPatterns("/**");
    }
    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }
}

複製代碼

controller-api

@RestController
@CrossOrigin
@RequestMapping("/user")
@Api(tags = "用戶登陸")
public class LoginController extends BaseController {
    @Reference
    UserService userService;
    @Reference
    TokenService tokenService;

    //登陸
    @PostMapping("/login")
    @ResponseBody
    public Object login(UserDo user) {
        JSONObject jsonObject = new JSONObject();
        UserDo userForBase = userService.findUserByUsername(user.getUserName());
        if (userForBase == null) {
            jsonObject.put("message", "登陸失敗,用戶不存在");
            return jsonObject;
        } else {
            if (!userForBase.getUserPasswd().equals(user.getUserPasswd())) {
                jsonObject.put("message", "登陸失敗,密碼錯誤");
                return jsonObject;
            } else {
                String token = tokenService.getToken(userForBase);
                jsonObject.put("token", token);
                jsonObject.put("user", userForBase);
                return jsonObject;
            }
        }
    }

    @UserLoginToken
    @GetMapping("/getMessage")
    public String getMessage() {
        return "你已經過驗證";
    }
}

複製代碼

即傳入token才能夠調用getMessage()方法。

接口調試

調用getMessage()

使用postmen調用接口:http://localhost:8094/user/getMessage

輸出錯誤信息以下:

調用login()

使用postmen調用接口:http://localhost:8094/user/login

傳入id,用戶名和密碼三個參數,輸出結果以下:

再次調用getMessage()

在Headers中加入token參數,輸出結果以下:

相關文章
相關標籤/搜索