SpringSecurity詳解整合JWT實現(xiàn)全過程
Token
Token和Sessionid的思想一樣。Session是存在服務器端JVM中,Token存在Redis中。
解決分布式Session數(shù)據(jù)一致性問題:Spring-Session
傳統(tǒng)的Token,例如:用戶登錄成功生成對應的令牌,key:為令牌, value:userid,隱藏了數(shù)據(jù)真實性 ,同時將該token存放到redis中,返回對應的真實令牌給客戶端存放。客戶端每次訪問后端請求的時候,會傳遞該token在請求中,服務器端接收到該token之后,從redis中查詢如果存在的情況下,則說明在有效期內,如果在Redis中不存在的情況下,則說明過期或者token錯誤。
Token使用:
- 驗證賬號密碼成功
- 生成一個令牌UUID
- 將該令牌存放到redis中,key為令牌,value值對應存放userid
- 最終返回令牌給客戶端
Token驗證回話信息
- 在請求頭中傳遞該令牌
- 從Redis中驗證該令牌是否有效期
- 獲取value內容
- 根據(jù)userid查詢用戶信息,返回給客戶端
Token存放數(shù)據(jù)優(yōu)缺點:
缺點:
- 必須依賴服務器,占用服務器端資源
- 效率非常低
優(yōu)點:
- 可以隱藏數(shù)據(jù)真實性
- 適用于分布式/微服務
- 安全性高 JWT
Jwt
JSON WEB Token JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便于從資源服務器獲取資源,也可以增加一些額外的其它業(yè)務邏輯所必須的聲明信息,該token也可直接被用于認證,也可被加密。
組成
第一部分:header (頭部)
描述加密算法
HS256:屬于驗證簽名
RSA256:屬于非對稱加密
第二部分:playload(載荷)
攜帶存放的數(shù)據(jù) 用戶名稱、用戶頭像之類,需要注意銘感數(shù)據(jù)
標準中注冊的聲明 (建議但不強制使用) :
- iss: jwt簽發(fā)者
- sub: jwt所面向的用戶
- aud: 接收jwt的一方
- exp: jwt的過期時間,這個過期時間必須要大于簽發(fā)時間
- nbf: 定義在什么時間之前,該jwt都是不可用的.
- iat: jwt的簽發(fā)時間
- jti: jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。
第三部分:secret (存放在服務器端)
簽名值,Base64(header .playload) +秘鑰
JWT和Token的區(qū)別
1、token對應的數(shù)據(jù)存放在redis中
2、JWT對應存放的數(shù)據(jù)(payload中)客戶端
優(yōu)缺點
優(yōu)點:
1、JWT數(shù)據(jù)存放在客戶端,不依賴于服務器端,減輕服務器端壓力
2、效率比傳統(tǒng)的token驗證還要高
缺點
1、jwt一旦生成之后后期無法修改
2、無法銷毀一個jwt
3、建議不要放敏感數(shù)據(jù),userid、手機號
4、后端無法統(tǒng)計 生成JWT
手寫JWT
public class Test001 {
private static final String SIGN_KEY = "kaicoSignKey";
public static void main(String[] args) throws UnsupportedEncodingException {
//手寫jwt 封裝三個部分: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簽名值 實際上就是 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();
//設置過期時間 (測試使用1秒鐘)
Long exp = now + 1 * 1000;
JwtBuilder jwtBuilder = Jwts.builder()
//payload值
.claim("userImg", "sssss")
//簽名值
.signWith(SignatureAlgorithm.HS256, SIGN_KEY)
.setExpiration(new Date(exp));
//輸出JWT的內容
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、驗證賬號密碼
2、賬號密碼驗證成功,生成JWT返回給客戶端(移動app、瀏覽器、微信小程序)
3、客戶端請求服務端,服務端驗證JWT
1. base64解密jwt獲取payload中的數(shù)據(jù)
2. 獲取roles權限列表注冊到SpringSecurity框架中
代碼整合
在上次整合SpringSecurity的基礎上
新增兩個過濾器
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 {
/**
* 獲取授權管理
*/
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
/**
* 用戶登陸成功之后驗證
*/
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);
}
/**
* 賬號或者密碼錯誤
* @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("賬號或者密碼錯誤");
}
}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);
}
/**
* 過濾請求驗證
*
* @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);
}
/**
* 驗證token 并且驗證權限
* @param token
* @return
*/
private UsernamePasswordAuthenticationToken setAuthentication(String token) {
String username = JwtUtils.getUsername(token);
if (username == null) {
return null;
}
//解析權限列表
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);
}測試:
1、post方式請求登錄接口:localhost:8080/auth/login
請求參數(shù)json,返回請求頭中帶有token
{
"username":"kaico_add",
"password":"kaico"
}

2、再次請求其他接口時,請求頭上帶上token,

存在的問題
Jwt如何實現(xiàn)注銷?
- 客戶端清除緩存,比如:瀏覽器cookie清除(但是服務器還是存在)
- 權限發(fā)生變化的情況下,管理員同志用戶重新登錄或者提示權限不足,請聯(lián)系管理員開放權限。
- 建議將時間設置稍微短一點
- 使用黑名單過濾,后期對服務器端壓力大
JWT是否安全?
安全機制肯定有
就算黑客篡改payload中的權限列表,必須先獲取到服務器端秘鑰,自己生產JWT才行。
JWT中存放userid
可以單獨對userid做對稱加密之后再存在payload中,解密的秘鑰在服務器端,也是安全的。
以上就是SpringSecurity詳解整合JWT實現(xiàn)全過程的詳細內容,更多關于SpringSecurity JWT的資料請關注腳本之家其它相關文章!
- SpringSecurity+Redis+Jwt實現(xiàn)用戶認證授權
- springboot+springsecurity+mybatis+JWT+Redis?實現(xiàn)前后端離實戰(zhàn)教程
- SpringBoot3.0+SpringSecurity6.0+JWT的實現(xiàn)
- SpringSecurity整合JWT的使用示例
- SpringBoot整合SpringSecurity和JWT和Redis實現(xiàn)統(tǒng)一鑒權認證
- SpringBoot+SpringSecurity+jwt實現(xiàn)驗證
- mall整合SpringSecurity及JWT認證授權實戰(zhàn)下
- mall整合SpringSecurity及JWT實現(xiàn)認證授權實戰(zhàn)
- Java SpringSecurity+JWT實現(xiàn)登錄認證
- springSecurity+jwt使用小結
相關文章
SpringBoot +Vue開發(fā)考試系統(tǒng)的教程
這篇文章主要介紹了SpringBoot +Vue開發(fā)考試系統(tǒng),支持多種題型:選擇題、多選題、判斷題、填空題、綜合題以及數(shù)學公式。支持在線考試,教師在線批改試卷。本文通過實例代碼給大家介紹的非常詳細,需要的朋友可以參考下2020-05-05
一文深入理解Java中的java.lang.reflect.InvocationTargetException錯誤
這篇文章主要給大家介紹了關于Java中java.lang.reflect.InvocationTargetException錯誤的相關資料,java.lang.reflect.InvocationTargetException是Java中的一個異常類,它通常是由反射調用方法時拋出的異常,需要的朋友可以參考下2024-03-03
mybatis-plus如何修改日志只打印SQL語句不打印查詢結果
這篇文章主要介紹了mybatis-plus如何修改日志只打印SQL語句不打印查詢結果問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06
springboot?@Validated的概念及示例實戰(zhàn)
這篇文章主要介紹了springboot?@Validated的概念以及實戰(zhàn),使用?@Validated?注解,Spring?Boot?應用可以有效地實現(xiàn)輸入驗證,提高數(shù)據(jù)的準確性和應用的安全性,本文結合實例給大家講解的非常詳細,需要的朋友可以參考下2024-04-04

