一、实现大致流程
在浏览器发起请求来执行登录操作,此时会访问登录的接口,如果访问的不是登录接口,为了安全,使其跳转到登录页面.如果登录成功之后,我们需要生成一个jwt令牌,将生成的 jwt令牌返回给前端。
前端拿到jwt令牌之后,会将jwt令牌存储起来。在后续的每一次请求中都会将jwt令牌携带到服务端。
服务端统一拦截请求之后,先来判断一下这次请求有没有把令牌带过来,如果没有带过来,直接拒绝访问,如果带过来了,还要校验一下令牌是否是有效。如果有效,就直接放行进行请求的处理。
二、JWT令牌
1.JWT是什么?
JWT (全称:Json Web Token)是一个开放标准(RFC 7519),它定义了一种简洁的、自包含的方式,用于作为 JSON 对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
简洁:是指jwt就是一个简单的字符串。可以在请求参数或者是请求头当中直接传递。 自包含:指的是jwt令牌,看似是一个随机的字符串,但是我们是可以根据自身的需求在jwt令牌中存储自定义的数据内容。如:可以直接在jwt令牌中存储用户的相关信息。
简单点说就是一种认证机制,让后台知道该请求是来自于受信的客户端。JWT 主要用于用户登录鉴权
2.JWT令牌的组成
JWT的组成: (JWT令牌由三个部分组成,三个部分之间使用英文的点来分割)
第一部分:Header(头), 记录令牌类型、签名算法等。 例如:{“alg”:”HS256”,”type”:”JWT”}
第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。 例如:{“id”:”1”,”username”:”Tom”}
第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。
签名的目的就是为了防jwt令牌被篡改,而正是因为jwt令牌最后一个部分数字签名的存在,所以整个jwt 令牌是非常安全可靠的。一旦jwt令牌当中任何一个部分、任何一个字符被篡改了,整个令牌在校验的时候都会失败,所以它是非常安全可靠的。

3,JWT令牌在登录认证的应用
在浏览器发起请求来执行登录操作,此时会访问登录的接口,如果登录成功之后,我们需要生成一个jwt令牌,将生成的 jwt令牌返回给前端。
前端拿到jwt令牌之后,会将jwt令牌存储起来。在后续的每一次请求中都会将jwt令牌携带到服务端。
服务端统一拦截请求之后,先来判断一下这次请求有没有把令牌带过来,如果没有带过来,直接拒绝访问,如果带过来了,还要校验一下令牌是否是有效。如果有效,就直接放行进行请求的处理。
4,JWT令牌鉴权登录实现步骤
1,引入JWT依赖
1 2 3 4 5 6
| <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
|
2,创建JWT工具类
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
| public class JwtUtils { private static String signKey = "xxx"; private static Long expire = 43200000L;
public static String generateJwt(Map<String, Object> claims){ String jwt = Jwts.builder() .addClaims(claims) .signWith(SignatureAlgorithm.HS256, signKey) .setExpiration(new Date(System.currentTimeMillis() + expire)) .compact(); return jwt; }
public static Claims parseJWT(String jwt){ Claims claims = Jwts.parser() .setSigningKey(signKey) .parseClaimsJws(jwt) .getBody(); return claims; } }
|
3,三层架构实现登录成功,生成JWT令牌并返回
controller层
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
|
@RestController @Slf4j @RequestMapping("/user") public class UserController { @Autowired private UserService userService;
@PostMapping("/login") public Result login(@RequestBody User user){ try { log.info("登录{}",user); String jwt = userService.login(user); return Result.success(true,"登录成功!!",jwt); } catch (Exception e) { e.printStackTrace(); return Result.error(false,"登录失败!!"); } } }
|
Service层
接口
1 2 3 4 5 6
| public interface UserService {
String login(User user); }
|
实现类
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
| @Service @Slf4j public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper;
@Override public String login(User user) { if(user == null || user.getUsername() == null || "".equals(user.getUsername()) || user.getPassword() == null || "".equals(user.getPassword())){ throw new RuntimeException("用户不存在!!"); } User dbUser = userMapper.findByUsername(user.getUsername()); if (!user.getPassword().equals(dbUser.getPassword())){ throw new RuntimeException("用户名或密码错误!!"); } HashMap<String, Object> claims = new HashMap<>(); claims.put("id",dbUser.getId()); claims.put("username",dbUser.getUsername()); String jwt = JwtUtils.generateJwt(claims); return jwt; } }
|
Mapper层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public interface UserService { /** * 用户登录 */ String login(User user); }
==========xml===============
<select id="findByUsername" resultType="xxxxxxx"> select id,birthday,gender,username,password, remark,station,telephone from t_user where username = #{username}; </select>
|
三、拦截器Interceptor
1,什么是拦截器?
拦截器的主要是:基于Java的反射机制,属于**面向切面编程(AOP)**的一种运用,就是在Service或者一个方法前调用一个方法,或者在方法后调用一个方法,甚至在抛出异常的时候做业务逻辑的操作。
拦截器的作用类似于Servlet 中的Filter(过滤器),都可以用于对处理器进行预处理和后处理。在Spring MVC 与Spring Boot 中使用拦截器一般是实现HandlerInterceptor 接口。
拦截器的作用:拦截请求,在指定方法调用前后,根据业务需要执行预先设定的代码。
2,拦截器Interceptor鉴权登录实现步骤
1,定义拦截器
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
| @Component @Slf4j public class LoginCheckInterceptor implements HandlerInterceptor{ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("token"); response.setContentType("application/json;charset = UTF-8"); ObjectMapper mapper = new ObjectMapper(); if (token == null || "".equals(token)){ log.info("token不存在"); Result error = Result.error(false, "NOT_LOGIN"); String result = mapper.writeValueAsString(error); response.getWriter().write(result); return false; } try { Claims claims = JwtUtils.parseJWT(token); } catch (Exception e) { log.info("请求头不正确!!"); Result error = Result.error(false, "NOT_LOGIN"); String result = mapper.writeValueAsString(error); response.getWriter().write(result); return false; } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle ... "); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion .... "); } }
|
注意:
- preHandle方法:目标资源方法执行前执行。 返回true:放行 返回false:不放行
- postHandle方法:目标资源方法执行后执行
- afterCompletion方法:视图渲染完毕后执行,最后执行
2,注册配置拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private LoginCheckInterceptor loginCheckInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginCheckInterceptor) .addPathPatterns("/**") .excludePathPatterns("/user/login"); } }
|
WebMvcConfigurer接口中的核心方法之一addInterceptors(InterceptorRegistry registry)方法表示添加拦截器。主要用于进行用户登录状态的拦截,日志的拦截等。
- addInterceptor:需要一个实现HandlerInterceptor接口的拦截器实例
- addPathPatterns:用于设置拦截器的过滤路径规则;addPathPatterns(“/**”)对所有请求都拦截
- excludePathPatterns:用于设置不需要拦截的过滤规则,静态资源啥的