登录校验功能的实现有许多种方案,包括传统的Cookie 和Session ,以及目前被广泛采用的令牌技术之一JWT ( Json Web Token ).目前主要学习JWT的来完成登录校验,Cookie和Session仅作为了解。
通过Spring提供的拦截器 Interceptor 拦截请求,配合JWT校验来决定是否放行
1.会话追踪方案 1.1Cookie Cookie是Web服务器发送给客户端的一小段信息,客户端请求时,可以读取该信息发送到服务器端。
1.1.1 使用cookie 存入Cookie使用HttpSercletResponse 的addCookie
取出Cookie使用HttpServletRequest 的getCookies ,再进行遍历得到单个cookie
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @RestController public class TestController { @GetMapping("/cookie1") public Result cookie1 (HttpServletResponse response) { response.addCookie(new Cookie ("login_userid" ,"123456" )); return Result.success(); } @GetMapping("/cookie2") public Result cookie2 (HttpServletRequest request) { Cookie[] cookies = request.getCookies(); for (Cookie cookie : cookies) { System.out.println("cookie:" +cookie.getValue()); } return Result.success(); } }
当访问cookie1接口时可以,打开控制台可以看到存储在本地浏览器的cookie
而当前端请求获取cookie,服务器端也可以通过HttpServletRequest 来获取 存储在浏览器中的cookie
1.1.2 优缺点 优点:Cookie是HTTP协议中支持的技术
缺点:
移动端APP无法使用
Cookie不安全,用户可以自己禁用Cookie
Cookie不能跨域
1.2Session Session是基于Cookie来工作的,同一个客户端每次访问服务器时,只要当浏览器在第一次访问服务器时,服务器设置一个id并保存一些信息(例如登陆就保存用户信息,视具体情况),并把这个id通过Cookie存到客户端,客户端每次和服务器交互时只传这个id,就可以实现维持浏览器和服务器的状态,而这个ID通常是NAME为JSESSIONID 的一个Cookie。
在Java中是通过调用HttpServletRequest的getSession方法(使用true作为参数)创建的,存储在服务器的内存中,tomcat默认保存30分钟。
1.2.1 使用Session 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @GetMapping("/session1") public Result session1 (HttpSession session) { session.setAttribute("login_userid_2" ,"654321" ); return Result.success(); } @GetMapping("/session2") public Result session2 (HttpServletRequest request) { HttpSession session = request.getSession(); Object value = session.getAttribute("login_userid_2" ); return Result.success(value); }
当访问session1时可以看到存在一个名为JSESSIONID的Cookie,这个就是此次会话的id
通过这个session可以在服务器中获取其中的属性
1.2.2 优缺点 优点:由于session中的数据存储在服务器中,只有传输一个id,因此数据安全性更高
缺点:
基于Cookie,所以Cookie存在的问题依然存在
Session在高访问量的情况下,对于服务器的存储压力大
无论是Cookie还是Session,在服务器集群环境下需要解决数据共享的问题
1.3 令牌技术 1.3.1 概述 令牌(Token)就是系统的临时密钥,相当于账户名和密码,用来决定是否允许这次请求和判断这次请求时属于哪一个用户的.它允许你不提供密码或其他凭证的前提下,访问网络和系统资源.这些令牌将持续存在于系统中,除非系统重新启动.
JWT 是目前主流的令牌技术,简洁、自包含的格式,用于在通信双方以JSON数据格式安全的传输信息。由于数字签名的存在,这些信息都是可靠的。
组成:
第一部分:Header(头),记录令牌类型、签名算法等。
例如:[“alg”:“HS256”,“type”:”WT”)
第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。
例如:“id”.”1”“username”.”Tom”
第三部分: Signature(签名),防止Token被篡改、确保安全性。
将header、payload,并加入指定秘钥,通过指定签名算法计算而来。
第三部分字符的由来不是Base64编码方式生成的,而是前面指定的签名算法
1.3.2 优缺点 优点:
支持PC端、移动端
解决集群环境下的认证问题
减轻服务器端存储压力
缺点: 需要自己手动实现
2.JWT实现登录校验的过程 用户登录成功后,系统会下发JWT令牌,然后在后续的每次请求中,前端都需要在请求头header中携带JWT令牌到服务端,请求头的名称为 token,值为登录时下发的JWT令牌。
服务端拦截所有需要登录才能访问的请求,通过
request.getParameter(“token”)
request.getHeader(“token”)
public Result xxx(@RequestHeader(“token”) String token )
获得登录时下发的JWT令牌。
如果检测到用户未登录,则会返回错误信息。
3.JWT的使用 3.1 导入依赖 1 2 3 4 5 <dependency > <groupId > io.jsonwebtoken</groupId > <artifactId > jjwt</artifactId > <version > 0.9.1</version > </dependency >
3.2 生成JWT 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @GetMapping("/genJwt") public Result genJwt () { Map<String, Object> claims =new HashMap <>(); claims.put("login_userid_3" ,"123456789" ); String jwt = Jwts.builder() .setClaims(claims) .signWith(SignatureAlgorithm.HS256, "This_is_a_key" ) .setExpiration(new Date (System.currentTimeMillis() + 30 * 60 * 1000 )) .compact(); return Result.success(jwt); }
3.3校验JWT 在此过程中,JWT令牌若发生改变,则校验不会通过,而过期也同样不会通过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @GetMapping("/checkJwt") public Result checkJwt () { String test_jwt = "xxx.xxx.xxx" ; try { Claims claims = Jwts.parser() .setSigningKey("This_is_a_key" ) .parseClaimsJws(test_jwt) .getBody(); return Result.success(claims); }catch (Exception e){ return Result.error("Jwt令牌错误" ); } }
3.4 封装为工具类 为方便使用,封装为工具类以便多次调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import java.util.Date;import java.util.Map;public class JwtUtil { public static final long EXPIRE = 1000 * 60 * 60 ; public static final String SECRET = "this_is_a_secret_key" ; public static String generateJwt (Map<String,Object> claims) { claims.put("login_userid" ,"123456789" ); String jwt = Jwts.builder() .setClaims(claims) .signWith(SignatureAlgorithm.HS256, SECRET) .setExpiration(new Date (System.currentTimeMillis() + EXPIRE)) .compact(); return jwt; } public static Claims parseJwt (String jwt) { Claims claims = Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(jwt) .getBody(); return claims; } public static Boolean checkJwt (String jwt) { if (StringUtils.isBlank(jwt)){ return false ; } try { parseJwt(jwt); return true ; }catch (Exception e){ return false ; } } }
4.Interceptor拦截器 4.1 拦截器概述 Spring MVC 中的拦截器 Interceptor 类似于Servlet 中的过滤器 Filter,它主要用于拦截用户请求并做相应的处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等。
4.2 使用 4.2.1 定义拦截器 首先需要自定义一个类,放在interceptor包下
实现HandlerInterceptor接口 ,然后ctrl+o可以调出重写方法的窗口,重写preHandle()、postHandle()、afterCompletion(),最后在这个类上 加上@Component注解 ,交给IOC容器管理
其中
preHandle()是目标资源方法执行前执行, 返回true放行,返回false拦截
**postHandle()**目标资源方法执行后执行
**afterCompletion()**视图渲染完毕后执行,最后执行
4.2.2 注册拦截器 创建一个配置类,重写addInterceptors()方法,注入刚才定义的拦截器,然后 通过registry添加拦截器以及配置拦截路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import com.sky.interceptor.DemoInterceptor;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;import javax.annotation.Resource;@Configuration public class WebConfig extends WebMvcConfigurationSupport { @Resource @Autowired private DemoInterceptor demoInterceptor; @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(demoInterceptor) .addPathPatterns("/**" ) .excludePathPatterns("/login" ); } }
完成定义和注册后,如以上配置,页面将只能访问 /login ,其他请求将会被拦截
4.2.3 拦截器完成登录校验 在拦截器的preHandle()方法中添加以下代码
通过校验JWT令牌来选择是否放行请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String url = request.getRequestURL().toString(); if (url.contains("login" )){ return true ; } String jwt = request.getHeader("token" ); System.out.println("获取的jwt:" +jwt); if ( JwtUtil.checkJwt(jwt) ){ return true ; }else { return false ; } }