JWT用于长时间保存用户认证信息

编程入门 行业动态 更新时间:2024-10-10 19:24:35

JWT用于<a href=https://www.elefans.com/category/jswz/34/1770298.html style=长时间保存用户认证信息"/>

JWT用于长时间保存用户认证信息

登录的判断标准

在Spring Security框架中,对于通过认证登录的判断标准是,在SecurityContext(Security上下文)中是否存在authentication对象(认证信息),如果存在,Spring Security框架会根据Authentication对象识别用户的身份、权限等。如果不存在,则视为“未登录”。

默认情况下,Spring Security框架是基于Session来处理(读写)用户的信息。

Session

Http是无状态的协议,无法保存用户的信息,即:某客户端第1次访问了服务器,可能产生了某些数据,当此客户端再次访问服务端时,服务端无法识别出这个客户端时此前曾经来访的客户端。

为了识别客户端的身份,当客户端第一次向服务端发送请求时,服务器端将向客户端相应一个JSESSIONID数据,其本质是一个UUID数据。在后续客户端访问时,会自动携带JSESSIONID,以至于让服务器能够识别客户端的身份。同时,服务器端还有一个Map结构的数据,此数据是使用JSESSION作为key。所以,每个客户端在服务器端都有一个与之对应的value值,也就是Session数据。

UUID是全球唯一的,从设计上来说,它能够保证同一时空中的唯一性

由于Session的运作机制,存在以下的缺点:

  • 默认不适用于集群和分布式系统:Session是内存中的数据,默认情况下,Session只存在于与客户端交互的那台服务器上,如果使用了集群,客户端每次请求的服务器都不是同一台服务器,则无法有效的识别客户端身份
  • 不适合长时间保存数据:因为Session是内存中的数据,所有来访的客户端都有Session数据,所以必须存在Session清除机制,如果长时间不清除,随着来访的客户端越来越多,内存占用越来越大,服务器将无法存储着大量的数据,通常,会将Session设置为15分钟或最多30分钟清除。

Token

Token:票据、令牌

目前主流识别用户身份的做法是使用Token机制,Token可以理解为“票据”,例如:现实生活中的“公交卡”,某客户端第1次请求服务器,或执行登录请求,则可视为“充值公交卡”的行为,当客户端成功登录,相当于成功充值了公交卡,客户端的后续访问应该携带Token,相当于乘坐公交车需要携带公交卡,则服务器端可以识别客户端的身份,相当于公交车及工作人员可以识别携带了购买凭证的乘车人。

Token是包含可识别的有效信息,对于需要获取信息的一方而言,只需要具备读取Token信息的能力即可。

Token并不需要占用较多的内存空间,是可以长时间的保存用户信息。

JWT

JWT:JSON Web Token

JWT是一种使用JSON格式来组织数据的Token。

添加JWT依赖

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt<artifactId><version>0.9.1</version>
</dependency>

生成JWT

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.jupiter.api.Test;import java.util.Date;
import java.util.HashMap;
import java.util.Map;public class JWT{String secretKey = "asdsag1wewq2qeq"; //通用密匙,用于识别客户端传过来的jwt是否是属于该服务器的Date date = new Date(System.currentTimeMillis() + 5 * 24 * 60 * 60 * 1000);//设置jwt过期的时长。如果加上的数值超过21亿的话,应在任何数字中加L,避免即可Map<String, Object> claims = new HashMap<>();//填充需要装入jwt的数据claims.put("id",222);claims.put("username","aiaiai");string jwt = Jwts.builer()//Header.setHeaderParam("alg","HS256").setHeaderParam("typ","JWT")//Payload.setClaims(claims)//Signature.setExpiration(date).signwith(SignatureAlgorithm.HS256,secretKey)pact();System.out.println(jwt);
}

解析JWT

public class parse(){String jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6OTUyNywiZXhwIjoxNjY3ODc5MDM2LCJ1c2VybmFtZSI6ImxpdWNhbmdzb25nIn0.gMlHQiSbbWnf5cIBi0p4V9bz05QHRaq3rNC8e_4yfpE"Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();Long id = claims.get("id",Long.class);String username = claim.get("username",String.class);System.out.println("id = " + id);System.out.printli("username = " + username);
}

JWT解析时的错误

  • JWT已过期,会抛出ExpiredJwtException

io.jsonwebtoken.ExpiredJwtException: JWT expired at 2022-11-08T11:24:49Z. Current time: 2022-11-08T11:38:01Z, a difference of 792152 milliseconds.  Allowed clock skew: 0 milliseconds.

  • JWT数据有误,会抛出MalformedJwtException

io.jsonwebtoken.MalformedJwtException: Unable to read JSON value: {"alg":"HS7#�$�uB'

  • JWT签名不匹配,会抛出SignatureException

 io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.

登录成功时返回JWT

在处理登录时,如果用户登录成功,应该向客户端返回JWT数据,这样客户端在下次访问请求时,可以携带JWT来访问。

在Spring Security框架中,AuthenticationManager调用authenticate()方法时,如果通过认证,会返回Authentication接口类型的对象,本质上是UsernamePasswordAuthenticationToken类型,此类型中的principal属性就是通过认证的用户信息,也是UserDetailsService中的loadUserByUsername()方法返回的结果:

UsernamePasswordAuthenticationToken [
    Principal=org.springframework.security.core.userdetails.User [
        Username=root, 
        Password=[PROTECTED], 
        Enabled=true, 
        AccountNonExpired=true, 
        credentialsNonExpired=true, 
        AccountNonLocked=true, 
        Granted Authorities=[暂时给出的假的权限标识]
    ], 
    Credentials=[PROTECTED], 
    Authenticated=true, 
    Details=null, 
    Granted Authorities=[暂时给出的假的权限标识]

所以,可以在处理认证的代码后再添加读取认证结果、生成JWT代码:

@override
public String login(AdminLoginDTO adminLoginDTO){log.debug("开始处理管理员登录的业务,参数{}", adminLoginDTO);//执行认证Authentication authentication = new UsernamePasswordAuthenticationToken(adminLoginDTO.getUsername(),adminLoginDTO.getPassword());//创建用户认证对象,传入用户名和密码Authentication authenticationResult = authenticationManager.authenticate(authentication);//进行验证的流程log.debug("认证通过,返回的结果为:{}", authenticationResult);//从认证结果中获取所需的数据,将用于生成JWTObject principal = authenticationResult.getPrincipal();log.debug("认证结果中的用户类型:{}" + principal.getClass().getName());User user = (User) principal;String username = user.getUsername();//生成JWT数据时,填充装JWT中的数据Map<String, Object> claims = new HashMap<>();claims.put("username", username);String secretKey = "asda2qwe2asdqwe6qw";Date date = new Date(System.currentTimeMillis() + 5 * 24 * 60 * 60 * 1000L);String jwt = Jwt.builder().setHeaderparam("alg","H256").setHeaderparam("typ","JWT").setClaims(claims).setExpiration(date)).signWith(SignatureAlgorithm.HS256,secretKey)pact();log.debut("生成的JWT:{}",jwt);return jwt;
}

识别客户端的身份

Spring Security框架是依据SecurityContext中的认证信息来判定当前是否已经通过了认证,所以,客户端应该在得到JWT之后,携带JWT向服务端提交请求,而服务器端应该尝试解析此JWT,并从中获取用户信息,用于创建认证对象,最后,将认证对象存入到SecurityContext中,剩下的就交给框架处理。

由于不同的请求都需要识别客户端的身份(即解析JWT、创建认证对象、将认证对象存入SecurityContext),所以,应该统一进行处理。同时该处理还应该在Spring Security的过滤器之前执行。所以此项应该采用自定义过滤器来处理

public class JwtAuthorizationFilter extend OncePerRequestFilter(){public static final int JWT_MIN_LENGTH = 129;//设置jwt最小的长度,用于判断jwt是否有效public JwtAuthorizationFilter(){log.info("创建过滤器对象:JwtAuthorizationFilter");}@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain) throws ServletException,IOException{log.debug(JwtAuthorizationFilter开始执行过滤...);//获取客户端携带的JWTString jwt = request.getHeader("Authorization");log.debug = ("获取客户端携带的JWT:{}", jwt);//检查是否获取到了基本有效的JWTif(!StringUtils.hasText(jwt) || jwt.length() < JWT_MIN_LENGTH){//对于无效的JWT,直接放行,交由后续的组件进行处理log.debug("获取到的JWT被视为无效,当前过滤器将放行...");filterChain.doFilter(request,response);return;}//尝试解析JWTlog.debug("获取到的JWT被视为有效,准备解析JWT...");String secretKey = "asda2qwe2asdqwe6qw";Claim claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();//获取jwt中管理员的信息String username = claims.get("username", String.class);//处理权限信息List<GrantedAuthority> authorities = new ArrayList<>();GrantedAuthority authority = new SimpleGrantedAuthority("这是一个假的权限");authorities.add(authority);//创建Authentication对象Authentication authentication = new UsernamePasswordAuthenticationToken(username,null,authorities);//将Authentication对象存入到SecurityContextlog.debug("向SecurityContext中存入认证信息:{}",authentication);SecurityContextHolder.getContext().setAuthentication(authentication);//过滤器链继续向后传递log.debug("JWT过滤器执行完毕,放行");filterChain.doFilter(request,response);}
}

为了确保过滤器,在Spring Security的过滤器之前执行,应该在SecurityConfiguration中,先自动装配过滤器对象,并补充配置。

@Autowired

private JwtAuthorizaitonFilter jwtAuthorizationFilter;

//将JWT过滤器添加到Spring Security框架的过滤器链中

http.addFilterBefore(jwtAuthorizationFilter,

                                     UsernamePasswordAuthenticationFilter.class);

简单的登录处理已经完成。

目前还未完成如下操作:

  • 生成解析的JWT中的secretKey不应该定义在2个类中
  • 解析JWT时出现的异常,未处理
  • 认证信息中不包含id
  • 认证信息中的权限还没有数据
  • 还未结合前端

更多推荐

JWT用于长时间保存用户认证信息

本文发布于:2024-03-06 19:45:55,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1716214.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:长时间   用户   信息   JWT

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!