SpringSecurity詳解整合JWT實(shí)現(xiàn)全過程
Token
Token和Sessionid的思想一樣。Session是存在服務(wù)器端JVM中,Token存在Redis中。
解決分布式Session數(shù)據(jù)一致性問題:Spring-Session
傳統(tǒng)的Token,例如:用戶登錄成功生成對(duì)應(yīng)的令牌,key:為令牌, value:userid,隱藏了數(shù)據(jù)真實(shí)性 ,同時(shí)將該token存放到redis中,返回對(duì)應(yīng)的真實(shí)令牌給客戶端存放??蛻舳嗣看卧L問后端請(qǐng)求的時(shí)候,會(huì)傳遞該token在請(qǐng)求中,服務(wù)器端接收到該token之后,從redis中查詢?nèi)绻嬖诘那闆r下,則說明在有效期內(nèi),如果在Redis中不存在的情況下,則說明過期或者token錯(cuò)誤。
Token使用:
- 驗(yàn)證賬號(hào)密碼成功
- 生成一個(gè)令牌UUID
- 將該令牌存放到redis中,key為令牌,value值對(duì)應(yīng)存放userid
- 最終返回令牌給客戶端
Token驗(yàn)證回話信息
- 在請(qǐng)求頭中傳遞該令牌
- 從Redis中驗(yàn)證該令牌是否有效期
- 獲取value內(nèi)容
- 根據(jù)userid查詢用戶信息,返回給客戶端
Token存放數(shù)據(jù)優(yōu)缺點(diǎn):
缺點(diǎn):
- 必須依賴服務(wù)器,占用服務(wù)器端資源
- 效率非常低
優(yōu)點(diǎn):
- 可以隱藏?cái)?shù)據(jù)真實(shí)性
- 適用于分布式/微服務(wù)
- 安全性高 JWT
Jwt
JSON WEB Token JWT的聲明一般被用來在身份提供者和服務(wù)提供者間傳遞被認(rèn)證的用戶身份信息,以便于從資源服務(wù)器獲取資源,也可以增加一些額外的其它業(yè)務(wù)邏輯所必須的聲明信息,該token也可直接被用于認(rèn)證,也可被加密。
組成
第一部分:header (頭部)
描述加密算法
HS256:屬于驗(yàn)證簽名
RSA256:屬于非對(duì)稱加密
第二部分:playload(載荷)
攜帶存放的數(shù)據(jù) 用戶名稱、用戶頭像之類,需要注意銘感數(shù)據(jù)
標(biāo)準(zhǔn)中注冊(cè)的聲明 (建議但不強(qiáng)制使用) :
- iss: jwt簽發(fā)者
- sub: jwt所面向的用戶
- aud: 接收jwt的一方
- exp: jwt的過期時(shí)間,這個(gè)過期時(shí)間必須要大于簽發(fā)時(shí)間
- nbf: 定義在什么時(shí)間之前,該jwt都是不可用的.
- iat: jwt的簽發(fā)時(shí)間
- jti: jwt的唯一身份標(biāo)識(shí),主要用來作為一次性token,從而回避重放攻擊。
第三部分:secret (存放在服務(wù)器端)
簽名值,Base64(header .playload) +秘鑰
JWT和Token的區(qū)別
1、token對(duì)應(yīng)的數(shù)據(jù)存放在redis中
2、JWT對(duì)應(yīng)存放的數(shù)據(jù)(payload中)客戶端
優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
1、JWT數(shù)據(jù)存放在客戶端,不依賴于服務(wù)器端,減輕服務(wù)器端壓力
2、效率比傳統(tǒng)的token驗(yàn)證還要高
缺點(diǎn)
1、jwt一旦生成之后后期無法修改
2、無法銷毀一個(gè)jwt
3、建議不要放敏感數(shù)據(jù),userid、手機(jī)號(hào)
4、后端無法統(tǒng)計(jì) 生成JWT
手寫JWT
public class Test001 {
private static final String SIGN_KEY = "kaicoSignKey";
public static void main(String[] args) throws UnsupportedEncodingException {
//手寫jwt 封裝三個(gè)部分:header、payload、sign簽名
//定義header
JSONObject header = new JSONObject();
header.put("alg", "HS256");
//payload
JSONObject payload = new JSONObject();
payload.put("name", "kaico");
String headerEncode = Base64.getEncoder().encodeToString(header.toJSONString().getBytes());
String payloadJSONString = payload.toJSONString();
String payloadEncode = Base64.getEncoder().encodeToString(payloadJSONString.getBytes());
//sign簽名值 實(shí)際上就是 md5
String sign = DigestUtils.md5DigestAsHex((payload + SIGN_KEY).getBytes());
String jwt = headerEncode + "." + payloadEncode + "." + sign;
System.out.println(jwt);
//解密
String payloadEncodeStr = jwt.split("\\.")[1];
String payloadDecoder = new String(Base64.getDecoder().decode(payloadEncodeStr), "UTF-8");
String newSign = DigestUtils.md5DigestAsHex((payloadDecoder + SIGN_KEY).getBytes());
System.out.println(newSign.equals(jwt.split("\\.")[2]));
}
}使用工具類新建JWT
public class Test003 {
private static final String SIGN_KEY = "kaicoSignKey";
public static void main(String[] args) {
long now = System.currentTimeMillis();
//設(shè)置過期時(shí)間 (測(cè)試使用1秒鐘)
Long exp = now + 1 * 1000;
JwtBuilder jwtBuilder = Jwts.builder()
//payload值
.claim("userImg", "sssss")
//簽名值
.signWith(SignatureAlgorithm.HS256, SIGN_KEY)
.setExpiration(new Date(exp));
//輸出JWT的內(nèi)容
String jwt = jwtBuilder.compact();
System.out.println(jwt);
//解密
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Claims body = Jwts.parser().setSigningKey(SIGN_KEY).parseClaimsJws(jwt).getBody();
System.out.println(body.get("userImg"));
}
}Springboot整合JWT

登錄流程
1、驗(yàn)證賬號(hào)密碼
2、賬號(hào)密碼驗(yàn)證成功,生成JWT返回給客戶端(移動(dòng)app、瀏覽器、微信小程序)
3、客戶端請(qǐng)求服務(wù)端,服務(wù)端驗(yàn)證JWT
1. base64解密jwt獲取payload中的數(shù)據(jù)
2. 獲取roles權(quán)限列表注冊(cè)到SpringSecurity框架中
代碼整合
在上次整合SpringSecurity的基礎(chǔ)上
新增兩個(gè)過濾器
package com.kaico.jwt.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kaico.jwt.entity.UserEntity;
import com.kaico.jwt.utils.JwtUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
/**
* @Author kaico
* @Description //TODO
* @Date 19:26 2022/7/25
*/
public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter {
/**
* 獲取授權(quán)管理
*/
private AuthenticationManager authenticationManager;
public JWTLoginFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
/**
* 后端登陸接口
*/
super.setFilterProcessesUrl("/auth/login");
}
@Override
public Authentication attemptAuthentication(HttpServletRequest req,
HttpServletResponse res) {
try {
UserEntity user = new ObjectMapper()
.readValue(req.getInputStream(), UserEntity.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
user.getUsername(),
user.getPassword(),
new ArrayList<>())
);
} catch (IOException e) {
logger.error(e.getMessage());
return null;
}
}
@Override
/**
* 用戶登陸成功之后驗(yàn)證
*/
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
UserEntity userEntity = (UserEntity) authResult.getPrincipal();
String jwtToken = JwtUtils.generateJsonWebToken(userEntity);
response.addHeader("token", jwtToken);
}
/**
* 賬號(hào)或者密碼錯(cuò)誤
* @param request
* @param response
* @param failed
* @throws IOException
* @throws ServletException
*/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.getWriter().print("賬號(hào)或者密碼錯(cuò)誤");
}
}package com.kaico.jwt.filter;
import com.kaico.jwt.utils.JwtUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/**
* @Author kaico
* @Description //TODO
* @Date 19:37 2022/7/25
*/
public class JWTValidationFilter extends BasicAuthenticationFilter {
public JWTValidationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
/**
* 過濾請(qǐng)求驗(yàn)證
*
* @param request
* @param response
* @param chain
* @throws IOException
* @throws ServletException
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(setAuthentication(request.getHeader("token")));
super.doFilterInternal(request, response, chain);
}
/**
* 驗(yàn)證token 并且驗(yàn)證權(quán)限
* @param token
* @return
*/
private UsernamePasswordAuthenticationToken setAuthentication(String token) {
String username = JwtUtils.getUsername(token);
if (username == null) {
return null;
}
//解析權(quán)限列表
List<SimpleGrantedAuthority> userRoleList = JwtUtils.getUserRole(token);
return new UsernamePasswordAuthenticationToken(username, null, userRoleList);
}
}JWT工具類
package com.kaico.jwt.utils;
import com.alibaba.fastjson.JSONArray;
import com.kaico.jwt.entity.UserEntity;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class JwtUtils {
public static final String TOKEN_HEADER = "token";
public static final String TOKEN_PREFIX = "Bearer ";
private static final String SUBJECT = "kaico";
//JWT有效期
private static final long EXPIRITION = 1000 * 24 * 60 * 60 * 7;
private static final String APPSECRET_KEY = "kaico_secret";
private static final String ROLE_CLAIMS = "roles";
public static String generateJsonWebToken(UserEntity user) {
String token = Jwts
.builder()
.setSubject(SUBJECT)
.claim(ROLE_CLAIMS, user.getAuthorities())
.claim("username", user.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRITION))
.signWith(SignatureAlgorithm.HS256, APPSECRET_KEY).compact();
return token;
}
/**
* 生成token
*
* @param username
* @param role
* @return
*/
public static String createToken(String username, String role) {
Map<String, Object> map = new HashMap<>();
map.put(ROLE_CLAIMS, role);
String token = Jwts
.builder()
.setSubject(username)
.setClaims(map)
.claim("username", username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRITION))
.signWith(SignatureAlgorithm.HS256, APPSECRET_KEY).compact();
return token;
}
public static Claims checkJWT(String token) {
try {
final Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
return claims;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 獲取用戶名
*
* @param token
* @return
*/
public static String getUsername(String token) {
Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
return claims.get("username").toString();
}
/**
* 獲取用戶角色
*
* @param token
* @return
*/
public static List<SimpleGrantedAuthority> getUserRole(String token) {
Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
List roles = (List) claims.get(ROLE_CLAIMS);
String json = JSONArray.toJSONString(roles);
List<SimpleGrantedAuthority>
grantedAuthorityList =
JSONArray.parseArray(json, SimpleGrantedAuthority.class);
return grantedAuthorityList;
}
/**
* 是否過期
*
* @param token
* @return
*/
public static boolean isExpiration(String token) {
Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
return claims.getExpiration().before(new Date());
}
}修改SecurityConfig配置類
@Override
protected void configure(HttpSecurity http) throws Exception {
List<PermissionEntity> allPermission = permissionMapper.findAllPermission();
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry
expressionInterceptUrlRegistry = http.authorizeRequests();
allPermission.forEach((permission) -> {
expressionInterceptUrlRegistry.antMatchers(permission.getUrl()).
hasAnyAuthority(permission.getPermTag());
});
// 配置前后令牌登陸
expressionInterceptUrlRegistry.antMatchers("/auth/login").permitAll()
.antMatchers("/**").fullyAuthenticated()
.and()
//配置過濾器
.addFilter(new JWTValidationFilter(authenticationManager()))
.addFilter(new JWTLoginFilter(authenticationManager())).csrf().disable()
//提出session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}測(cè)試:
1、post方式請(qǐng)求登錄接口:localhost:8080/auth/login
請(qǐng)求參數(shù)json,返回請(qǐng)求頭中帶有token
{
"username":"kaico_add",
"password":"kaico"
}

2、再次請(qǐng)求其他接口時(shí),請(qǐng)求頭上帶上token,

存在的問題
Jwt如何實(shí)現(xiàn)注銷?
- 客戶端清除緩存,比如:瀏覽器cookie清除(但是服務(wù)器還是存在)
- 權(quán)限發(fā)生變化的情況下,管理員同志用戶重新登錄或者提示權(quán)限不足,請(qǐng)聯(lián)系管理員開放權(quán)限。
- 建議將時(shí)間設(shè)置稍微短一點(diǎn)
- 使用黑名單過濾,后期對(duì)服務(wù)器端壓力大
JWT是否安全?
安全機(jī)制肯定有
就算黑客篡改payload中的權(quán)限列表,必須先獲取到服務(wù)器端秘鑰,自己生產(chǎn)JWT才行。
JWT中存放userid
可以單獨(dú)對(duì)userid做對(duì)稱加密之后再存在payload中,解密的秘鑰在服務(wù)器端,也是安全的。
以上就是SpringSecurity詳解整合JWT實(shí)現(xiàn)全過程的詳細(xì)內(nèi)容,更多關(guān)于SpringSecurity JWT的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- SpringSecurity+Redis+Jwt實(shí)現(xiàn)用戶認(rèn)證授權(quán)
- springboot+springsecurity+mybatis+JWT+Redis?實(shí)現(xiàn)前后端離實(shí)戰(zhàn)教程
- SpringBoot3.0+SpringSecurity6.0+JWT的實(shí)現(xiàn)
- SpringSecurity整合JWT的使用示例
- SpringBoot整合SpringSecurity和JWT和Redis實(shí)現(xiàn)統(tǒng)一鑒權(quán)認(rèn)證
- SpringBoot+SpringSecurity+jwt實(shí)現(xiàn)驗(yàn)證
- mall整合SpringSecurity及JWT認(rèn)證授權(quán)實(shí)戰(zhàn)下
- mall整合SpringSecurity及JWT實(shí)現(xiàn)認(rèn)證授權(quán)實(shí)戰(zhàn)
- Java SpringSecurity+JWT實(shí)現(xiàn)登錄認(rèn)證
- springSecurity+jwt使用小結(jié)
相關(guān)文章
SpringBoot +Vue開發(fā)考試系統(tǒng)的教程
這篇文章主要介紹了SpringBoot +Vue開發(fā)考試系統(tǒng),支持多種題型:選擇題、多選題、判斷題、填空題、綜合題以及數(shù)學(xué)公式。支持在線考試,教師在線批改試卷。本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2020-05-05
SpringMVC詳解如何映射請(qǐng)求數(shù)據(jù)
這篇文章主要給大家介紹了關(guān)于SpringMvc映射請(qǐng)求數(shù)據(jù)的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-06-06
一文深入理解Java中的java.lang.reflect.InvocationTargetException錯(cuò)誤
這篇文章主要給大家介紹了關(guān)于Java中java.lang.reflect.InvocationTargetException錯(cuò)誤的相關(guān)資料,java.lang.reflect.InvocationTargetException是Java中的一個(gè)異常類,它通常是由反射調(diào)用方法時(shí)拋出的異常,需要的朋友可以參考下2024-03-03
java實(shí)現(xiàn)幸運(yùn)抽獎(jiǎng)功能
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)幸運(yùn)抽獎(jiǎng)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
idea快速找到項(xiàng)目中對(duì)應(yīng)的類圖文詳解(包括源碼)
用IDEA開發(fā)Java項(xiàng)目時(shí)經(jīng)常會(huì)使用到各種快捷鍵,其中搜索是最常用的之一,下面這篇文章主要給大家介紹了關(guān)于idea如何快速找到項(xiàng)目中對(duì)應(yīng)的類(包括源碼)的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06
在SpringBoot項(xiàng)目中解決依賴沖突問題的方法
在SpringBoot項(xiàng)目中,依賴沖突是一個(gè)常見的問題,特別是當(dāng)項(xiàng)目引入多個(gè)第三方庫(kù)或框架時(shí),依賴沖突可能導(dǎo)致編譯錯(cuò)誤、運(yùn)行時(shí)異常或不可預(yù)測(cè)的行為,本文給大家介紹了如何在SpringBoot項(xiàng)目中解決以來沖突問題的方法,需要的朋友可以參考下2024-01-01
mybatis-plus如何修改日志只打印SQL語句不打印查詢結(jié)果
這篇文章主要介紹了mybatis-plus如何修改日志只打印SQL語句不打印查詢結(jié)果問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06
springboot?@Validated的概念及示例實(shí)戰(zhàn)
這篇文章主要介紹了springboot?@Validated的概念以及實(shí)戰(zhàn),使用?@Validated?注解,Spring?Boot?應(yīng)用可以有效地實(shí)現(xiàn)輸入驗(yàn)證,提高數(shù)據(jù)的準(zhǔn)確性和應(yīng)用的安全性,本文結(jié)合實(shí)例給大家講解的非常詳細(xì),需要的朋友可以參考下2024-04-04

