天天看點

springboot-jwtJWT

JWT

            JSON Web Token (JWT)是一個開放标準(RFC 7519),它定義了一種緊湊的、自包含的方式,用于作為JSON對象在各方之間安全地傳輸資訊。該資訊可以被驗證和信任,因為它是數字簽名的。

       你做資料操作時,後端怎麼知道是你這個人操作了而不是别人,一般web服務肯定不是你一個人在用吧? 

    倘若是前後端分離的那種形式,後端隻出api,那怎麼保證是你允許的人來通路你的api呢?不可能人人都響應吧?

  那就要談一談token的認證和傳統的session認證,http協定本身是一種無狀态的協定,那怎麼保證是我們允許的人來操作呢?

        首先我們要有身份的确立吧,比如登入,登入完,我們給它配置設定一個用于識别身份的資訊,每次請求都要求他們把身份資訊給到我們,我們就能知道是誰操作了吧!

        傳統的Session,都知道我們是把它儲存在記憶體中的,小使用者量可以很完美的跑起來,但是使用者量巨大,線上人數巨大,那對伺服器來說這種是一個不小的負擔,那麼怎麼解決呢?

       Token    倘若我們不儲存它們的資訊,把它們的身份資訊按照與它們的約定做成一個憑證,我們伺服器可以校驗這個憑證的真僞,那就沒有這種壓力了吧(因為是可以解密的是以你懂的)

好,我們就來看看jwt

   這是我整合的一個例子   主要用到了  springboot + swagger + mybatisplus+jwt+mysql

springboot-jwtJWT
springboot-jwtJWT
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.zkb</groupId>
    <artifactId>spring-boot-mybatisplus</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-mybatisplus</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.18</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.21</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.2</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.3.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.60</version>
        </dependency>

        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.30</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>



        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
            <exclusions>
                <exclusion>
                    <groupId>io.swagger</groupId>
                    <artifactId>swagger-annotations</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.swagger</groupId>
                    <artifactId>swagger-models</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>1.5.22</version>
        </dependency>

        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-models</artifactId>
            <version>1.5.22</version>
        </dependency>
        <!-- swagger-ui -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.9.1</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
           
server:
  port: 8081
  servlet:
    context-path: /

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test-demo?zeroDateTimeBehavior=convertToNull&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
    username: root
    password: baishou888
    # mysql8.0 驅動
    driver-class-name: com.mysql.cj.jdbc.Driver

    debug: false
    #Druid#
    name: test
    type: com.alibaba.druid.pool.DruidDataSource
    filters: stat
    maxActive: 20
    initialSize: 1
    maxWait: 60000
    minIdle: 1
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: select 'x'
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    maxOpenPreparedStatements: 20
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    auto-mapping-behavior: full
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath*:mapper/**/*Mapper.xml
  global-config:
    # 邏輯删除配置
    db-config:
      # 删除前
      logic-not-delete-value: 1
      # 删除後
      logic-delete-value: 0
           
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zkb.mapper.UserMapper">

    <!-- 通用查詢映射結果 -->
    <resultMap id="BaseResultMap" type="com.zkb.model.User">
        <id column="id" property="id" />
        <result column="username" property="username" />
        <result column="password" property="password" />
    </resultMap>

</mapper>
           
package com.zkb;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.zkb.mapper")
public class SpringBootMybatisplusApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootMybatisplusApplication.class, args);
    }

}
           
package com.zkb.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

    這邊我自定義了一個注解,用來給需要token驗證的接口标記一下

package com.zkb.model;

import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.TableField;
import java.io.Serializable;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * <p>
 * 
 * </p>
 *
 * @author zkb
 * @since 2020-11-23
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_user")
@ApiModel(value = "使用者資訊", description = "使用者資訊")
public class User extends Model<User> {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    @ApiModelProperty(value = "主鍵")
    @JsonSerialize(using = ToStringSerializer.class)
    private Long id;

    @ApiModelProperty(value = "使用者名")
    private String username;

    @ApiModelProperty(value = "密碼")
    private String password;

}
           
package com.zkb.model;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * <h3>spring-boot-mybatisplus</h3>
 * <p></p>
 *
 * @author : zkb
 * @date : 2020-12-04 15:31
 **/

@Data
@ApiModel(value = "使用者資訊輔助", description = "使用者資訊輔助")
public class UserFo {
    @ApiModelProperty(value = "使用者名")
    private String username;

    @ApiModelProperty(value = "密碼")
    private String password;
}
           
package com.zkb.mapper;

import com.zkb.model.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author zkb
 * @since 2020-11-23
 */
public interface UserMapper extends BaseMapper<User> {

}
           
package com.zkb.service.impl;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.zkb.model.User;
import com.zkb.service.TokenService;
import org.springframework.stereotype.Service;

/**
 * <h3>spring-boot-mybatisplus</h3>
 * <p></p>
 *
 * @author : zkb
 * @date : 2020-12-04 15:23
 **/
@Service("TokenService")
public class TokenServiceImpl implements TokenService {
    @Override
    public String getToken(User user) {
        String token="";
        token= JWT.create().withAudience(user.getId().toString())// 将 user id 儲存到 token 裡面
                .sign(Algorithm.HMAC256(user.getPassword()));// 以 password 作為 token 的密鑰
        return token;
    }
}
           
package com.zkb.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zkb.model.User;
import com.zkb.mapper.UserMapper;
import com.zkb.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * <p>
 *  服務實作類
 * </p>
 *
 * @author zkb
 * @since 2020-11-23
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Resource
    private UserMapper mapper;

    @Override
    public User findByUsername(User user) {
        QueryWrapper<User> query = new QueryWrapper<User>();
        query.eq("username", user.getUsername());
        return mapper.selectOne(query);
    }
}
           
package com.zkb.service;

import com.zkb.model.User;

/**
 * <h3>spring-boot-mybatisplus</h3>
 * <p></p>
 *
 * @author : zkb
 * @date : 2020-12-04 15:22
 **/
public interface TokenService {
    String getToken(User user);
}
           
package com.zkb.service;

import com.zkb.model.User;
import com.baomidou.mybatisplus.extension.service.IService;

/**
 * <p>
 *  服務類
 * </p>
 *
 * @author zkb
 * @since 2020-11-23
 */
public interface UserService extends IService<User> {

    User findByUsername(User user);
}
           
package com.zkb.conf;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.zkb.annotation.ZkbToken;
import com.zkb.exc.AuthenticationException;
import com.zkb.model.User;
import com.zkb.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;



public class AuthenticationInterceptor implements HandlerInterceptor {

    @Autowired
    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();
        //檢查有沒有需要使用者權限的注解
        if (method.isAnnotationPresent(ZkbToken.class)) {
            ZkbToken ZkbToken = method.getAnnotation(ZkbToken.class);
            if (ZkbToken.required()) {
                // 執行認證
                if (token == null) {
                    throw new AuthenticationException("無token,請重新登入");
                }
                // 擷取 token 中的 user id
                String userId;
                try {
                    userId = JWT.decode(token).getAudience().get(0);
                } catch (JWTDecodeException j) {
                    throw new AuthenticationException("401");
                }
                User user = userService.getById(userId);
                if (user == null) {
                    throw new AuthenticationException("使用者不存在,請重新登入");
                }
                // 驗證 token
                JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
                try {
                    jwtVerifier.verify(token);
                } catch (JWTVerificationException e) {
                    throw new AuthenticationException("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 {

    }
}
           
package com.zkb.conf;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationInterceptor())
                .addPathPatterns("/**");    // 攔截所有請求
    }
    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }
}
           
package com.zkb.conf;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 配置分頁插件
 *
 */
@Configuration
public class MybatisPlusConfig {

    /**
     * 分頁插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
}
           
package com.zkb.conf;

import io.swagger.annotations.ApiOperation;
import io.swagger.models.auth.In;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.Arrays;
import java.util.List;


@Configuration
@EnableSwagger2
public class SwaggerApp {



    @Bean
    public Docket createRestApi1() {
        return new Docket(DocumentationType.SWAGGER_2).enable(true).apiInfo(apiInfo()).select()
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .apis(RequestHandlerSelectors.basePackage("com.zkb.controller"))
                .paths(PathSelectors.any()).build().securitySchemes(apiKeyList()).groupName("接口中心");
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("API")
                .contact(new Contact("XXXXX", "http://XXXXXX.XXXX/", ""))
                .version("1.0")
                .description("API 描述")
                .build();
    }

    private List<ApiKey> apiKeyList() {
        return Arrays.asList(new ApiKey("登入token", "token", In.HEADER.name()),
                new ApiKey("裝置類型(android,ios,pc)---必填", "deviceType", In.HEADER.name()));
    }
}           
package com.zkb.controller;


import com.alibaba.fastjson.JSONObject;
import com.zkb.annotation.ZkbToken;
import com.zkb.model.User;
import com.zkb.model.UserFo;
import com.zkb.service.TokenService;
import com.zkb.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;


@RestController
@RequestMapping("/user")
@Api(value = "使用者api",tags = "使用者api")
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private TokenService tokenService;


    @PostMapping("/login")
    @ApiOperation(value="使用者登入",notes = "使用者登入")
    public Object login( UserFo userFo){
        User user = new User();
        BeanUtils.copyProperties(userFo,user);
        JSONObject jsonObject=new JSONObject();
        User userForBase=userService.findByUsername(user);
        if(userForBase==null){
            jsonObject.put("message","登入失敗,使用者不存在");
            return jsonObject;
        }else {
            if (!userForBase.getPassword().equals(user.getPassword())){
                jsonObject.put("message","登入失敗,密碼錯誤");
                return jsonObject;
            }else {
                String token = tokenService.getToken(userForBase);
                jsonObject.put("token", token);
                jsonObject.put("user", userForBase);
                return jsonObject;
            }
        }
    }



    @ZkbToken
    @GetMapping("/getUserById")
    @ApiOperation(value="根據id查詢使用者",notes = "根據id查詢使用者")
    public User getUserById(@RequestParam("id") Long id){
        return userService.getById(id);
    }

}
           
package com.zkb.exc;

public class AuthenticationException extends Exception {
    /*無參構造函數*/
    public AuthenticationException(){
        super();
    }
    
    //用詳細資訊指定一個異常
    public AuthenticationException(String message){
        super(message);
    }
    
    //用指定的詳細資訊和原因構造一個新的異常
    public AuthenticationException(String message, Throwable cause){
        super(message,cause);
    }
    
    //用指定原因構造一個新的異常
    public AuthenticationException(Throwable cause) {
        super(cause);
    }
}           

 這裡我自己定義了一個異常,準備用來校驗token的時候,不正确就抛出來給攔截住

package com.zkb.exc;

import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * <h3>OrderSystem</h3>
 * <p></p>
 *
 * @author : zkb
 * @date : 2020-11-12 11:23
 **/
@ControllerAdvice
public class GlobalExceptionHandler {


    @ExceptionHandler(AuthenticationException.class)    //自定義身份驗證異常
    @ResponseBody
    public JSONObject AuthenticationException(AuthenticationException e){
        JSONObject jsonObject=new JSONObject();
        jsonObject.put("Token無效","");
        jsonObject.put("msg",e.getMessage());
        return jsonObject;
    }
}
           

 攔截住自己定義的異常,就知道是身份校驗出現異常了,就傳回給前端

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zkb.mapper.UserMapper">

    <!-- 通用查詢映射結果 -->
    <resultMap id="BaseResultMap" type="com.zkb.model.User">
        <id column="id" property="id" />
        <result column="username" property="username" />
        <result column="password" property="password" />
    </resultMap>

</mapper>
           

 run起來測試一下

springboot-jwtJWT

好我取回token了

springboot-jwtJWT

 我如果設定一個錯誤的token去請求(或者不設定token都可以,自己試試)

springboot-jwtJWT

 如果我們設定正确的token

springboot-jwtJWT
springboot-jwtJWT

 這就你登入生成的token令牌了,完事了

demo自取 https://download.csdn.net/download/qq_14926283/13452767

繼續閱讀