JWT概述
前言:
編程不良人——JWT認證原理、流程整合springboot實戰應用,前后端分離認證的解決方案!
https://www.bilibili.com/video/BV1i54y1m7cP?p=1
歡迎大家支持。
以下是個人學習筆記。
一、簡介
1.1 含義
JWT簡稱JSON Web Token,也就是通過JSON形式作為Web應用中的令牌,用于各方之間安全地將信息作為JSON對象傳輸,在數據傳輸的過程中還可以完成數據加密、簽名等相關處理。
1.2 認證流程
1.認證流程
-
首先,前端通過Web表單將自己的用戶名和密碼發送到后端的接口。這一過程一般是一個HTTP POST請求。建議的方式是通過SSL加密的傳輸(https協議),從而避免敏感信息被嗅探。
-
后端核對用戶名和密碼成功后,將用戶的id等其他信息作為JWT Payload(負載),將其與頭部分別進行Base64編碼拼接后簽名,形成一個JWT(Token)。形成的JWT就是一個形同xxx.yyy.zzz的字符串。 token head.payload.singurater
-
后端將JWT字符串作為登錄成功的返回結果返回給前端。前端可以將返回的結果保存在localStorage或sessionStorage上,退出登錄時前端刪除保存的JWT即可。
-
前端在每次請求時將JWT放入HTTP Header中的Authorization位。(解決XSS和XSRF問題) HEADER
-
后端檢查是否存在,如存在驗證JWT的有效性。例如,檢查簽名是否正確;檢查Token是否過期;檢查Token的接收方是否是自己(可選)。
-
驗證通過后后端使用JWT中包含的用戶信息進行其他邏輯操作,返回相應結果。
2.與傳統的基于Session認證相比
由于HTTP是無狀態鏈接,用戶第一次請求時,服務器會將用戶信息保存在Session里,并且返回SessionID給用戶,保存在Cookied中;用戶再次請求時,可以通過SessionID從服務器中取出響應的數據,不需要再次認證。
基于session認證暴力的問題:
- 每一個用戶經過我們的應用認證之后,我們的應用都會在服務端做一次記錄,一遍用戶下次請求鑒別,通常而言session都是保存在在內存中,隨著用戶數的增加,服務器的開銷會明顯增大。
- 用戶認證之后,服務器做認證,如果認證的記錄保存在內存中,這意味下次認證必須還是這臺服務器,在分布式系統中明顯限制了負載均衡的能力。
- Session是基于Cookie進行用戶識別的,如果Cookie被獲取容易造成信息安全問題,用戶容易受到跨站請求偽造的攻擊。
2.jwt優勢
-
簡潔(Compact): 可以通過URL,POST參數或者在HTTP header發送,因為數據量小,傳輸速度也很快
-
自包含(Self-contained):負載中包含了所有用戶所需要的信息,避免了多次查詢數據庫
-
因為Token是以JSON加密的形式保存在客戶端的,所以JWT是跨語言的,原則上任何web形式都支持。
-
不需要在服務端保存會話信息,特別適用于分布式微服務。
1.3JWT結構
令牌形成
- 1.標頭(Header)
- 2.有效載荷(Payload)
- 3.簽名(Signature)
Header
- 標頭通常由兩部分組成:令牌的類型(即JWT)和所使用的簽名算法,例如HMAC SHA256(默認)或RSA。它會使用Base64編碼組成JWT結構的第一部分。
Payload
- 將能用到的用戶信息放在 Payload中。官方建議不要放特別敏感的信息,例如密碼。
簽名
- 簽名由三部分組成,header和payload分別經base64Url(一種在base64上做了一點改變的編碼)編碼后由’.’連接,服務器生成秘鑰(secret),連接之后的字符串在經header中聲明的加密方式和秘鑰加密,再用’.'和加密前的字符串連接。服務器驗證token時只會驗證第三部分。
二、使用JWT
2.1 導入依賴
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.4.0</version> </dependency>2.2 生成令牌
@Testvoid contextLoads() {HashMap<String, Object> map = new HashMap<>();Calendar instance = Calendar.getInstance();instance.add(Calendar.SECOND,100);String token = JWT.create().withHeader(map) //header.withClaim("userId",21) //playload.withClaim("username","xiaoming").withExpiresAt(instance.getTime()) //指定令牌的過期時間.sign(Algorithm.HMAC256("!q@w#s$er")); //簽名System.out.println(token);}2.3 驗證令牌
@Testpublic void test(){//創建驗證對象JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("!q@w#s$er")).build();DecodedJWT verify = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NDA0NTUwNTEsInVzZXJJZCI6MjEsInVzZXJuYW1lIjoieGlhb21pbmcifQ.gSzHO_u4-bzThmXaj28cFOq6IkSgFNbqbbJIrbT18bw");System.out.println(verify.getHeader()); //通過解碼拿出playload信息System.out.println(verify.getClaim("userId").asInt());System.out.println(verify.getClaim("username").asString());}三、JWT工具類封裝
創建JWTUtils.java
public class JWTUtils {private static final String SING = "!q@w#s$er";/** 生成token header.payload.sing* */public static String getToken(Map<String,String> map){Calendar instance = Calendar.getInstance();instance.add(Calendar.DATE,7);JWTCreator.Builder builder = JWT.create();map.forEach((k,v)->{builder.withClaim(k,v);}); //加入用戶信息//signString token = builder.withExpiresAt(instance.getTime()).sign(Algorithm.HMAC256(SING));//簽名return token;}/** 驗證token* */public static DecodedJWT vertify(String token){return JWT.require(Algorithm.HMAC256(SING)).build().verify(token);}//獲取token信息方法public static DecodedJWT getTokenInfo(String token){DecodedJWT verify = JWT.require(Algorithm.HMAC256(SING)).build().verify(token);return verify;} }四、整合SpringBoot
4.1 導入依賴
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.4.0</version></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.3</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.19</version></dependency><!--mysql 依賴--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><!--lombok 依賴--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>4.2 配置文件配置:
spring:datasource:username: rootpassword: 123#?serverTimezone=UTC解決時區的報錯url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8driver-class-name: com.mysql.cj.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSource#Spring Boot 默認是不注入這些屬性值的,需要自己綁定#druid 數據源專有配置initialSize: 5minIdle: 5maxActive: 20maxWait: 60000timeBetweenEvictionRunsMillis: 60000minEvictableIdleTimeMillis: 300000validationQuery: SELECT 1 FROM DUALtestWhileIdle: truetestOnBorrow: falsetestOnReturn: falsepoolPreparedStatements: true#配置監控統計攔截的filters,stat:監控統計、log4j:日志記錄、wall:防御sql注入#如果允許時報錯 java.lang.ClassNotFoundException: org.apache.log4j.Priority#則導入 log4j 依賴即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4jfilters: stat,wall,log4jmaxPoolPreparedStatementPerConnectionSize: 20useGlobalDataSourceStat: trueconnectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500mybatis:type-aliases-package: com.kuang.springbootjwt.pojomapper-locations: classpath:mapper/*.xmlserver:port: 80804.3數據庫新建庫和表:
4.4 新建實體類
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors;@Data @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) public class User {private Integer id;private String name;private String pwd;public User(String name, String pwd) {this.name = name;this.pwd = pwd;} }4.5 dao層
import com.kuang.springbootjwt.mapper.UserMapper; import com.kuang.springbootjwt.pojo.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository;@Repository public class UserDao {@Autowiredprivate UserMapper userMapper;public User Login(User user) {return userMapper.Login(user);}public User checkUserName(String username) {return userMapper.checkUserName(username);} }4.6UserMapper.xml文件
import com.kuang.springbootjwt.pojo.User; import org.apache.ibatis.annotations.Mapper; import org.springframework.stereotype.Repository;import java.util.List;//這個注解表示了這是一個Mybatis的Mapper @Mapper @Repository public interface UserMapper {// 獲取所有部門信息List<User> queryUserList();// 通過id獲得用戶User queryUserById(int id);// 通過用戶密碼查詢User Login(User user);//查詢用戶姓名User checkUserName(String username);//添加用戶Integer addUser(User user);int deleteUser(int id);4.7service層
public interface UserService {public User UserLogin(User user); } @Transactional @Service public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Transactional(propagation = Propagation.SUPPORTS)@Overridepublic User UserLogin(User user){System.out.println(user.getName());System.out.println(user.getPwd());User userDB = userDao.Login(user);if(userDB != null){return userDB;}throw new RuntimeException("登錄失敗");} }4.8 controller層
@RestController @Slf4j public class UserController {@Autowiredprivate UserServiceImpl userService;@GetMapping("/user/login")public Map login(User user){log.info("用戶名:",user.getName());log.info("密碼:",user.getPwd());HashMap<String, Object> map = new HashMap<>();try {User userDB = userService.UserLogin(user);HashMap<String, String> payload = new HashMap<>();payload.put("id",userDB.getName());String token = JWTUtils.getToken(payload);map.put("state",true);map.put("msg","登錄成功");map.put("token",token);}catch (Exception e){map.put("state",false);map.put("msg",e.getMessage());}return map;} }認證token
@GetMapping("/user/test1")public Map<String,Object> test1(String token) {HashMap<String, Object> map = new HashMap<>();log.info("當前token:[{}]",token);try{JWTUtils.vertify(token);map.put("state",true);map.put("msg","登錄成功");return map;}catch (SignatureVerificationException e){e.printStackTrace();map.put("msg","無效簽名");}catch (TokenExpiredException e){map.put("msg","token過期");e.printStackTrace();}catch (AlgorithmMismatchException e){map.put("msg","算法不一致");e.printStackTrace();}catch (Exception e){e.printStackTrace();}map.put("state",false);return map;}4.9測試
登錄成功
登錄失敗
認證成功:
五、JWT封裝成攔截器
5.1 創建JWTInteceptor.java
public class JWTInteceptor implements HandlerInterceptor {public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws IOException {String token = request.getHeader("token"); //獲取請求頭中的令牌HashMap<String , Object> map = new HashMap<>();try{JWTUtils.vertify(token);return true;}catch (SignatureVerificationException e){e.printStackTrace();map.put("msg","無效簽名");}catch (TokenExpiredException e){map.put("msg","token過期");e.printStackTrace();}catch (AlgorithmMismatchException e){map.put("msg","算法不一致");e.printStackTrace();}catch (Exception e){e.printStackTrace();}map.put("state",false);//將map轉為jsonString json = new ObjectMapper().writeValueAsString(map);//以json形式轉回前臺response.setContentType("application/json;charset=UTF-8");response.getWriter().println(json);return false;}5.2 攔截器
@Configuration public class InterceptorConfig implements WebMvcConfigurer {public void addInterceptors(InterceptorRegistry registry){registry.addInterceptor(new JWTInteceptor()).addPathPatterns("/user/test") //其他接口保護.excludePathPatterns("user/login"); //所有用戶都放行} }5.3處理業務邏輯
@PosttMapping("/user/test")public Map<String,Object> test(HttpServletRequest request) {String token = request.getHeader("token");DecodedJWT vertify = JWTUtils.vertify(token); // 處理業務邏輯 // log.info("當前token為:[{}]",vertify.getClaim("name").asString());HashMap<String, Object> map = new HashMap<>();map.put("state",true);map.put("msg","登錄成功");return map;}總結
- 上一篇: CDN可以防护什么种类的攻击?
- 下一篇: 定陶创维科技-自己创业的经历(1)