首頁>技術>

什麼是JWT

Json Web Token (JWT)是為了在網路應用環境間傳遞宣告而執行的一種基於JSON的開放標準(RFC 7519)該token被設計為緊湊且安全的 特別適用於分散式站點的單點登入(SSO)場景

隨著JWT的出現 使得校驗方式更加簡單便捷化JWT實際上就是一個字串 它由三部分組成:頭部 載荷簽名用[.]分隔這三個部分 最終的格式類似於:xxxx.xxxx.xxxx

該伺服器直接根據token取出儲存的使用者資訊 即可對token的可用性進行校驗 使得單點登入更為簡單

JWT校驗的過程1、瀏覽器傳送使用者名稱和密碼 發起登入請求2、服務端驗證身份 根據演算法將使用者識別符號打包生成token字串 並且返回給瀏覽器3、當瀏覽器需要發起請求時 將token一起傳送給伺服器4、伺服器發現數據中攜帶有token 隨即進行解密和鑑權5、校驗成功 伺服器返回請求的資料JWT的使用

專案使用:springboot+mysql+mybatisplus+lombok

不懂的可以先去學習一下,或者可以使用其他方法大同小異

第一步:建立SpringBoot專案我這裡用的Maven構建

第二步:導包修改pom.xml下面的dependencies節點加入如下

server:  port: 8089spring:  datasource:    driver-class-name: com.mysql.jdbc.Driver    url: jdbc:mysql://127.0.0.1:3306/jwt?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false    type: com.alibaba.druid.pool.DruidDataSource    username: root    password: rootmybatis-plus:  mapper-locations: classpath:mapper/*.xml  configuration:    map-underscore-to-camel-case: true  type-aliases-package: com.kexun.springbootjwt.entity

第四步:建立資料庫(jwt)和表user結構如下

CREATE DATABASE `jwt` CREATE TABLE `user` (  `id` varchar(32) DEFAULT NULL,  `username` varchar(20) DEFAULT NULL,  `password` varchar(20) DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8

第五步:配置資料來源Druid配置類

@Configurationpublic class DruidConfig {    @ConfigurationProperties(prefix = "spring.datasource")    @Bean    public DruidDataSource druidDataSource() {        return new DruidDataSource();    }}

第六步:建立實體

@Datapublic class User {    @TableId(value = "id", type = IdType.UUID)    private String id;    private String username;    private String password;}

第七步:建立mapper

public interface UserMapper extends BaseMapper<User> {}

第八步:建立自定義註解標記的這個註解的

@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface CheckToken {    boolean required() default true;} 

第九步:建立攔截器

public class LoginInterceptor implements HandlerInterceptor {    private UserService userService;    public LoginInterceptor(UserService userService) {        this.userService = userService;    }    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        // 從 http 請求頭中取出 token        String token = request.getHeader("token");        if (StringUtils.isEmpty(token)) {            Cookie[] cookies = request.getCookies();            if (cookies != null) {                for (Cookie cookie : cookies) {                    String name = cookie.getName();                    if ("x-token".equals(name)) {                        token = cookie.getValue();                    }                }            }        }        // 如果不是對映到方法直接透過        if (!(handler instanceof HandlerMethod)) {            return true;        }        HandlerMethod handlerMethod = (HandlerMethod) handler;        Method method = handlerMethod.getMethod();        //檢查有沒有需要使用者許可權的註解        if (method.isAnnotationPresent(CheckToken.class)) {            CheckToken checkToken = method.getAnnotation(CheckToken.class);            if (checkToken.required()) {                // 執行認證                if (StringUtils.isEmpty(token)) {                    throw new RuntimeException("無token,請重新登入");                }                // 獲取 token 中的 user id                String userId;                try {                    userId = JWT.decode(token).getClaim("id").asString();                } catch (JWTDecodeException j) {                    throw new RuntimeException("訪問異常!");                }                User user = userService.getById(userId);                if (user == null) {                    throw new RuntimeException("使用者不存在,請重新登入");                }                Boolean verify = JwtUtils.isVerify(token, user);                if (!verify) {                    throw new RuntimeException("非法訪問!");                }                return true;            }        }        return true;    }}

第十步:註冊攔截器:

@Configurationpublic class WebConfig implements WebMvcConfigurer {    @Autowired    private UserService userService;    @Override    public void addInterceptors(InterceptorRegistry registry) {        registry.addInterceptor(new LoginInterceptor(userService)).addPathPatterns("/**");    }}

異常處理類

@RestControllerAdvicepublic class GlobalExceptionHandler {    @ExceptionHandler(Exception.class)    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)    public Result onRuntime(Exception e) {        e.printStackTrace();        return Result.error(e.getMessage());    }}

返回包裝類

package com.kexun.springbootjwt.utils;public class Result {    private static final long serialVersionUID = 1L;    private int code;    private String msg;    private Object data;    public static long getSerialVersionUID() {        return serialVersionUID;    }    public int getCode() {        return code;    }    public void setCode(int code) {        this.code = code;    }    public String getMsg() {        return msg;    }    public void setMsg(String msg) {        this.msg = msg;    }    public Object getData() {        return data;    }    public void setData(Object data) {        this.data = data;    }    public Result(int code, String msg, Object data) {        this.code = code;        this.msg = msg;        this.data = data;    }    public Result(int code, String msg) {        this.code = code;        this.msg = msg;    }    public Result() {    }    public static Result success() {        return new Result(0, "ok", null);    }    public static Result success(String msg) {        return new Result(0, msg, null);    }    public static Result success(String msg, Object data) {        return new Result(0, msg, data);    }    public static Result error() {        return new Result(500, "error", null);    }    public static Result error(String msg) {        return new Result(500, msg, null);    }    public static Result error(String msg, Object data) {        return new Result(500, msg, data);    }}

JwtUtils

public class JwtUtils {    /**     * 使用者登入成功後生成Jwt     * 使用Hs256演算法  私匙使用使用者密碼     *     * @param ttlMillis jwt過期時間     * @param user      登入成功的user物件     * @return     */    public static String createJWT(long ttlMillis, User user) {        //指定簽名的時候使用的簽名演算法,也就是header那部分,jjwt已經將這部分內容封裝好了。        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;        long nowMillis = System.currentTimeMillis();        Date now = new Date(nowMillis);        //建立payload的私有宣告(根據特定的業務需要新增,如果要拿這個做驗證,一般是需要和jwt的接收方提前溝通好驗證方式的)        Map<String, Object> claims = new HashMap<String, Object>();        claims.put("id", user.getId());        claims.put("username", user.getUsername());        claims.put("password", user.getPassword());        //生成簽名的時候使用的秘鑰secret,這個方法本地封裝了的,一般可以從本地配置檔案中讀取,切記這個秘鑰不能外露哦。它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味著客戶端是可以自我簽發jwt了。        String key = user.getPassword();        //生成簽發人        String subject = user.getUsername();        //下面就是在為payload新增各種標準宣告和私有聲明瞭        //這裡其實就是new一個JwtBuilder,設定jwt的body        JwtBuilder builder = Jwts.builder()                //如果有私有宣告,一定要先設定這個自己建立的私有的宣告,這個是給builder的claim賦值,一旦寫在標準的宣告賦值之後,就是覆蓋了那些標準的宣告的                .setClaims(claims)                //設定jti(JWT ID):是JWT的唯一標識,根據業務需要,這個可以設定為一個不重複的值,主要用來作為一次性token,從而回避重放攻擊。                .setId(UUID.randomUUID().toString())                //iat: jwt的簽發時間                .setIssuedAt(now)                //代表這個JWT的主體,即它的所有人,這個是一個json格式的字串,可以存放什麼userid,roldid之類的,作為什麼使用者的唯一標誌。                .setSubject(subject)                //設定簽名使用的簽名演算法和簽名使用的秘鑰                .signWith(signatureAlgorithm, key);        if (ttlMillis >= 0) {            long expMillis = nowMillis + ttlMillis;            Date exp = new Date(expMillis);            //設定過期時間            builder.setExpiration(exp);        }        return builder.compact();    }    /**     * Token的解密     *     * @param token 加密後的token     * @param user  使用者的物件     * @return     */    public static Claims parseJWT(String token, User user) {        //簽名秘鑰,和生成的簽名的秘鑰一模一樣        String key = user.getPassword();        //得到DefaultJwtParser        Claims claims = Jwts.parser()                //設定簽名的秘鑰                .setSigningKey(key)                //設定需要解析的jwt                .parseClaimsJws(token).getBody();        return claims;    }    /**     * 校驗token     * 在這裡可以使用官方的校驗,我這裡校驗的是token中攜帶的密碼於資料庫一致的話就校驗透過     *     * @param token     * @param user     * @return     */    public static Boolean isVerify(String token, User user) {        //簽名秘鑰,和生成的簽名的秘鑰一模一樣        String key = user.getPassword();        //得到DefaultJwtParser        Claims claims = Jwts.parser()                //設定簽名的秘鑰                .setSigningKey(key)                //設定需要解析的jwt                .parseClaimsJws(token).getBody();        if (claims.get("password").equals(user.getPassword())) {            return true;        }        return false;    }}

啟動類:

@SpringBootApplication@MapperScan("com.kexun.springbootjwt.mapper")public class SpringbootJwtApplication {    public static void main(String[] args) {        SpringApplication.run(SpringbootJwtApplication.class, args);    }}

具體演示:

登入:http://127.0.0.1:8089/login?username=lidong&password=123456

鑑權:http://127.0.0.1:8089/checkToken

清空cookie在鑑權

授權失敗

附gitee原始碼地址:https://gitee.com/gdianqimeng/springboot-jwt.git

16
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 每天學一個 Linux 命令(12):chown