SpringSecurity詳解整合JWT實(shí)現(xiàn)全過程
Token
Token和Sessionid的思想一樣。Session是存在服務(wù)器端JVM中,Token存在Redis中。
解決分布式Session數(shù)據(jù)一致性問題:Spring-Session
傳統(tǒng)的Token,例如:用戶登錄成功生成對應(yīng)的令牌,key:為令牌, value:userid,隱藏了數(shù)據(jù)真實(shí)性 ,同時(shí)將該token存放到redis中,返回對應(yīng)的真實(shí)令牌給客戶端存放。客戶端每次訪問后端請求的時(shí)候,會(huì)傳遞該token在請求中,服務(wù)器端接收到該token之后,從redis中查詢?nèi)绻嬖诘那闆r下,則說明在有效期內(nèi),如果在Redis中不存在的情況下,則說明過期或者token錯(cuò)誤。
Token使用:
- 驗(yàn)證賬號密碼成功
- 生成一個(gè)令牌UUID
- 將該令牌存放到redis中,key為令牌,value值對應(yīng)存放userid
- 最終返回令牌給客戶端
Token驗(yàn)證回話信息
- 在請求頭中傳遞該令牌
- 從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:屬于非對稱加密
第二部分:playload(載荷)
攜帶存放的數(shù)據(jù) 用戶名稱、用戶頭像之類,需要注意銘感數(shù)據(jù)
標(biāo)準(zhǔn)中注冊的聲明 (建議但不強(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對應(yīng)的數(shù)據(jù)存放在redis中
2、JWT對應(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ī)號
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í)間 (測試使用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)證賬號密碼
2、賬號密碼驗(yàn)證成功,生成JWT返回給客戶端(移動(dòng)app、瀏覽器、微信小程序)
3、客戶端請求服務(wù)端,服務(wù)端驗(yàn)證JWT
1. base64解密jwt獲取payload中的數(shù)據(jù)
2. 獲取roles權(quán)限列表注冊到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); } /** * 賬號或者密碼錯(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("賬號或者密碼錯(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); } /** * 過濾請求驗(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); }
測試:
1、post方式請求登錄接口:localhost:8080/auth/login
請求參數(shù)json,返回請求頭中帶有token
{
"username":"kaico_add",
"password":"kaico"
}
2、再次請求其他接口時(shí),請求頭上帶上token,
存在的問題
Jwt如何實(shí)現(xiàn)注銷?
- 客戶端清除緩存,比如:瀏覽器cookie清除(但是服務(wù)器還是存在)
- 權(quán)限發(fā)生變化的情況下,管理員同志用戶重新登錄或者提示權(quán)限不足,請聯(lián)系管理員開放權(quán)限。
- 建議將時(shí)間設(shè)置稍微短一點(diǎn)
- 使用黑名單過濾,后期對服務(wù)器端壓力大
JWT是否安全?
安全機(jī)制肯定有
就算黑客篡改payload中的權(quán)限列表,必須先獲取到服務(wù)器端秘鑰,自己生產(chǎn)JWT才行。
JWT中存放userid
可以單獨(dú)對userid做對稱加密之后再存在payload中,解密的秘鑰在服務(wù)器端,也是安全的。
以上就是SpringSecurity詳解整合JWT實(shí)現(xiàn)全過程的詳細(xì)內(nèi)容,更多關(guān)于SpringSecurity JWT的資料請關(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一文深入理解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-03java實(shí)現(xiàn)幸運(yùn)抽獎(jiǎng)功能
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)幸運(yùn)抽獎(jiǎng)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03idea快速找到項(xiàng)目中對應(yīng)的類圖文詳解(包括源碼)
用IDEA開發(fā)Java項(xiàng)目時(shí)經(jīng)常會(huì)使用到各種快捷鍵,其中搜索是最常用的之一,下面這篇文章主要給大家介紹了關(guān)于idea如何快速找到項(xiàng)目中對應(yīng)的類(包括源碼)的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06在SpringBoot項(xiàng)目中解決依賴沖突問題的方法
在SpringBoot項(xiàng)目中,依賴沖突是一個(gè)常見的問題,特別是當(dāng)項(xiàng)目引入多個(gè)第三方庫或框架時(shí),依賴沖突可能導(dǎo)致編譯錯(cuò)誤、運(yùn)行時(shí)異常或不可預(yù)測的行為,本文給大家介紹了如何在SpringBoot項(xiàng)目中解決以來沖突問題的方法,需要的朋友可以參考下2024-01-01mybatis-plus如何修改日志只打印SQL語句不打印查詢結(jié)果
這篇文章主要介紹了mybatis-plus如何修改日志只打印SQL語句不打印查詢結(jié)果問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06springboot?@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