回顧
在之前的系統(tǒng)中,我們利用UUID
配合Redis
以達(dá)到角色登錄的功能。
當(dāng)前整個(gè)系統(tǒng)存在一個(gè)問題:人為修改token值
后,用戶仍然能在前端進(jìn)行數(shù)據(jù)庫(kù)操作,后臺(tái)沒有校驗(yàn)當(dāng)前用戶token
就允許一些請(qǐng)求,導(dǎo)致系統(tǒng)存在安全漏洞
。
解決方法:Jwt簽名驗(yàn)證
。整合Jwt
后,前端發(fā)出的請(qǐng)求后端會(huì)先進(jìn)行token驗(yàn)證
,然后執(zhí)行操作。
整合Jwt
的效果如下:找到token
值,然后進(jìn)行修改
在token
前加上值123,保存后進(jìn)行一些操作
此時(shí)點(diǎn)擊頁(yè)面修改按鈕,會(huì)彈出token錯(cuò)誤
的信息
后端也會(huì)記錄token錯(cuò)誤
的信息
現(xiàn)在開始來實(shí)現(xiàn)這個(gè)功能
添加依賴
Jwt依賴
在pom文件中添加下述依賴
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
Jwt配置
在common
文件夾下新建一個(gè)文件夾utils
,然后新建java
文件JwtUtil
寫上下述代碼,注釋已標(biāo)出
package com.ums.common.utils;
import com.alibaba.fastjson2.JSON;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
@Component
public class JwtUtil {
// 有效期
private static final long JWT_EXPIRE = 30*60*1000L; //半小時(shí), 單位為毫秒
// 令牌秘鑰
private static final String JWT_KEY = "123456";
// Object data 可放入U(xiǎn)ser對(duì)象,給User中的信息加密后成為token
public String createToken(Object data){
// 當(dāng)前時(shí)間
long currentTime = System.currentTimeMillis();
// token過期時(shí)間
long expTime = currentTime+JWT_EXPIRE;
// 構(gòu)建jwt
JwtBuilder builder = Jwts.builder()
.setId(UUID.randomUUID()+"")
.setSubject(JSON.toJSONString(data)) // User對(duì)象序列化
.setIssuer("system")
.setIssuedAt(new Date(currentTime))
.signWith(SignatureAlgorithm.HS256, encodeSecret(JWT_KEY)) // 加密
.setExpiration(new Date(expTime));
return builder.compact();
}
// 加密算法
private SecretKey encodeSecret(String key){
byte[] encode = Base64.getEncoder().encode(key.getBytes());
SecretKeySpec aes = new SecretKeySpec(encode, 0, encode.length, "AES");
return aes;
}
// token 解密
public Claims parseToken(String token){
Claims body = Jwts.parser()
.setSigningKey(encodeSecret(JWT_KEY))
.parseClaimsJws(token)
.getBody();
return body;
}
// token 解密,并返回一個(gè)對(duì)象,可是User對(duì)象
public <T> T parseToken(String token,Class<T> clazz){
Claims body = Jwts.parser()
.setSigningKey(encodeSecret(JWT_KEY))
.parseClaimsJws(token)
.getBody();
return JSON.parseObject(body.getSubject(),clazz);
}
}
定義Jwt攔截器
在XAdminApplication
同級(jí)目錄下新建文件夾interceptor
,再新建java文件JwtValidateInterceptor
文件中寫入以下代碼,注釋已給出
package com.ums.interceptor;
import com.alibaba.fastjson2.JSON;
import com.ums.common.utils.JwtUtil;
import com.ums.common.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
// HandlerInterceptor繼承該接口,然后重寫方法
@Component
@Slf4j
public class JwtValidateInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// X-Token 是前端定義的token標(biāo)頭,與前端保持一致
String token = request.getHeader("X-Token");
log.debug(request.getRequestURI() +"需要驗(yàn)證:"+ token); // 后臺(tái)日志記錄
if (token != null){
try {
jwtUtil.parseToken(token);
// 不要寫System.out.println(); 此為垃圾代碼
// 加上注解@Slf4j , 用log.debug()來打印
log.debug(request.getRequestURI() +"驗(yàn)證通過:");
return true;
}catch (Exception e) {
e.printStackTrace();
}
}
log.debug(request.getRequestURI() +"驗(yàn)證失敗,禁止訪問"); // 后臺(tái)日志記錄
// 創(chuàng)建一個(gè)返回對(duì)象,當(dāng)token錯(cuò)誤后反饋給前端
Result<Object> fail = Result.fail(20003, "token無效,請(qǐng)重新登錄");
// 驗(yàn)證不成功,給前端返回?cái)?shù)據(jù)
response.setContentType("application/json;charset=utf-8"); // 定義返回?cái)?shù)據(jù)格式
response.getWriter().write(JSON.toJSONString(fail)); // 將對(duì)象序列化后以json格式反饋至前端
return false; // 攔截當(dāng)前用戶的操作
}
}
注冊(cè)Jwt攔截器,配置需要驗(yàn)證token的URL
在config
目錄下新建java文件MyInterceptorConfig
寫入以下代碼
package com.ums.config;
import com.ums.interceptor.JwtValidateInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {
@Autowired
private JwtValidateInterceptor iwtValidateInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration registration = registry.addInterceptor(iwtValidateInterceptor);
registration.addPathPatterns("/**") // 攔截所有URL請(qǐng)求
.excludePathPatterns( // 開放下述URL請(qǐng)求
"/user/login",
"/user/info",
"/user/logout"
);
}
}
自此,Jwt
就算配置完畢
總共新建下述三個(gè)文件
測(cè)試Jwt
新建一個(gè)測(cè)試類JwtUtilsTest
@Autowired
private JwtUtil jwtUtil;
@Test
public void testCreateJwt(){
User user = new User();
user.setUsername("anthony");
user.setPhone("14766665555");
String token = jwtUtil.createToken(user);
System.out.println(token);
}
運(yùn)行testCreateJwt()
,系統(tǒng)會(huì)打印出一個(gè)加密后的字符串,此串會(huì)作為token
使用。
將這個(gè)字符串復(fù)制
新建一個(gè)解密的測(cè)試方法testParseJwt()
,下述代碼中復(fù)制你自己的token
@Test
public void testParseJwt(){
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIwNmRlOGJmOS1kYmM1LTQzNjUtYWRmYi0yYzBjMmVmM2FkOGYiLCJzdWIiOiJ7XCJwaG9uZVwiOlwiMTQ3NjY2NjU1NTVcIixcInVzZXJuYW1lXCI6XCJhbnRob255XCJ9IiwiaXNzIjoic3lzdGVtIiwiaWF0IjoxNjkwMjQ4MjY1LCJleHAiOjE2OTAyNTAwNjV9.iskJNmm6b6rDFs1oxsinrCdFmul0dd9-4_zswD6eGV0";
Claims claims = jwtUtil.parseToken(token);
System.out.println(claims);
}
運(yùn)行后可得到加密的信息
因?yàn)槲覀兪菍⒁粋€(gè)對(duì)象整體進(jìn)行加密,所以希望在解密的時(shí)候還原為一個(gè)對(duì)象
此時(shí)代碼可這樣寫
@Test
public void testParseJw2t(){
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIwNmRlOGJmOS1kYmM1LTQzNjUtYWRmYi0yYzBjMmVmM2FkOGYiLCJzdWIiOiJ7XCJwaG9uZVwiOlwiMTQ3NjY2NjU1NTVcIixcInVzZXJuYW1lXCI6XCJhbnRob255XCJ9IiwiaXNzIjoic3lzdGVtIiwiaWF0IjoxNjkwMjQ4MjY1LCJleHAiOjE2OTAyNTAwNjV9.iskJNmm6b6rDFs1oxsinrCdFmul0dd9-4_zswD6eGV0";
User user = jwtUtil.parseToken(token,User.class);
System.out.println(user);
}
運(yùn)行后得到一個(gè)對(duì)象
修改登錄等邏輯
現(xiàn)在有了Jwt
簽名驗(yàn)證機(jī)制,可將之前的UUID + redis
登錄邏輯進(jìn)行修改
打開UserServiceImpl
文件
將之前寫的login(User user)
、getUserInfo(String token)
、logout(String token)
這三段函數(shù)全部重寫文章來源:http://www.zghlxwxcb.cn/news/detail-616016.html
-
login(User user)
@Autowired private JwtUtil jwtUtil; @Override public Map<String, Object> login(User user) { // 根據(jù)用戶名查詢 LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(User::getUsername, user.getUsername()); User loginUser = this.baseMapper.selectOne(wrapper); // 結(jié)果不為空,并且密碼與數(shù)據(jù)庫(kù)解密后的密碼匹配,生成token,將用戶信息存入redis if (loginUser != null && passwordEncoder.matches(user.getPassword(), loginUser.getPassword()) // 匹配加密密碼 ) { loginUser.setPassword(null); // 設(shè)置密碼為空,密碼沒必要放入 // 創(chuàng)建jwt String token = jwtUtil.createToken(loginUser); // 返回?cái)?shù)據(jù) Map<String, Object> data = new HashMap<>(); data.put("token",token); return data; } // 結(jié)果不為空,生成token,前后端分離,前端無法使用session,可以使用token return null; }
-
getUserInfo(String token)
@Override public Map<String, Object> getUserInfo(String token) { // 之前已將對(duì)象進(jìn)行序列化處理存入redis,現(xiàn)在從redis中取出需要反序列化處理 // Object obj = redisTemplate.opsForValue().get(token); // 此對(duì)象是map類型,稍后需要序列化為Json字符串 // User loginUser = JSON.parseObject(JSON.toJSONString(obj), User.class); User loginUser = null; try { // 解析Token loginUser = jwtUtil.parseToken(token, User.class); }catch (Exception e) { e.printStackTrace(); } if (loginUser != null) { Map<String,Object> data = new HashMap<>(); data.put("name",loginUser.getUsername()); data.put("avatar",loginUser.getAvatar()); // 先在xml里寫SQL語(yǔ)句 id=getRoleNameByUserId,然后去UserMapper里實(shí)現(xiàn)接口 List<String> roleList = this.baseMapper.getRoleNameByUserId(loginUser.getId()); data.put("roles",roleList); return data; } return null; }
-
logout(String token)
, 注釋以前的代碼,可啥也不用寫!@Override public void logout(String token) { // redisTemplate.delete(token); // 從redis中刪除token }
自此完畢!文章來源地址http://www.zghlxwxcb.cn/news/detail-616016.html
到了這里,關(guān)于SpringBoot + Vue前后端分離項(xiàng)目實(shí)戰(zhàn) || 六:Jwt加密整合配置的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!