Spring Boot+Spring Security+JWT 实现token验证

  • 什么是JWT?
    • JWT的工作流程
    • JWT的主要应用场景
    • JWT的结构
  • SpringBoot+Spring Security和JWT的集成实现token验证
    • 引入JWT依赖
    • JWT的生成和解析工具类
    • Spring Security配置
    • 登录生成token
    • token的校验
    • 演示
    • 总结

通常情况下,把API直接暴露出去是风险很大的,不说别的,直接被机器攻击就喝一壶的。那么一般来说,对API要划分出一定的权限级别,然后做一个用户的鉴权,依据鉴权结果给予用户开放对应的API。目前,比较主流的方案有几种:

用户名和密码鉴权,使用Session保存用户鉴权结果。
使用OAuth进行鉴权(其实OAuth也是一种基于Token的鉴权,只是没有规定Token的生成方式)
自行采用Token进行鉴权
第一种就不介绍了,由于依赖Session来维护状态,也不太适合移动时代,新的项目就不要采用了。第二种OAuth的方案和JWT都是基于Token的,但OAuth其实对于不做开放平台的公司有些过于复杂。我们主要介绍第三种:JWT。

什么是JWT?

JWT是Json Web Token的缩写。它是基于 RFC 7519 标准定义的一种可以安全传输的 小巧 和 自包含 的JSON对象。由于数据是使用数字签名的,所以是可信任的和安全的。JWT可以使用HMAC算法对secret进行加密或者使用RSA的公钥私钥对来进行签名。

JWT的工作流程

下面是一个JWT的工作流程图。模拟一下实际的流程是这样的(假设受保护的API在/protected中)

1.用户导航到登录页,输入用户名、密码,进行登录
2.服务器验证登录鉴权,如果改用户合法,根据用户的信息和服务器的规则生成JWT Token
3.服务器将该token以json形式返回(不一定要json形式,这里说的是一种常见的做法)
4.用户得到token,存在localStorage、cookie或其它数据存储形式中。
5.以后用户请求/protected中的API时,在请求的header中加入Authorization: Bearer xxxx(token)。此处注意token之前有一个7字符长度的 Bearer
6.服务器端对此token进行检验,如果合法就解析其中内容,根据其拥有的权限和自己的业务逻辑给出对应的响应结果。
7.用户取得结果

JWT的主要应用场景

身份认证在这种场景下,一旦用户完成了登陆,在接下来的每个请求中包含JWT,可以用来验证用户身份以及对路由,服务和资源的访问权限进行验证。由于它的开销非常小,可以轻松的在不同域名的系统中传递,所有目前在单点登录(SSO)中比较广泛的使用了该技术。 信息交换在通信的双方之间使用JWT对数据进行编码是一种非常安全的方式,由于它的信息是经过签名的,可以确保发送者发送的信息是没有经过伪造的。

优点
1.简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
2.自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库
3.因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
4.不需要在服务端保存会话信息,特别适用于分布式微服务。

JWT的结构

JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了JWT字符串。
就像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

JWT包含了三部分:
Header 头部(标题包含了令牌的元数据,并且包含签名和/或加密算法的类型)
Payload 负载 (类似于飞机上承载的物品)
Signature 签名/签证

Header
JWT的头部承载两部分信息:token类型和采用的加密算法。

{ "alg": "HS256","typ": "JWT"
}

声明类型:这里是jwt
声明加密的算法:通常直接使用 HMAC SHA256

加密算法是单向函数散列算法,常见的有MD5SHAHAMC
MD5(message-digest algorithm 5) (信息-摘要算法)缩写,广泛用于加密和解密技术,常用于文件校验。校验?不管文件多大,经过MD5后都能生成唯一的MD5值
SHA (Secure Hash Algorithm,安全散列算法),数字签名等密码学应用中重要的工具,安全性高于MD5
HMAC (Hash Message Authentication Code),散列消息鉴别码,基于密钥的Hash算法的认证协议。用公开函数和密钥产生一个固定长度的值作为认证标识,用这个标识鉴别消息的完整性。常用于接口签名验证

Payload
载荷就是存放有效信息的地方。
有效信息包含三个部分
1.标准中注册的声明
2.公共的声明
3.私有的声明

标准中注册的声明 (建议但不强制使用) :
iss: jwt签发者
sub: 面向的用户(jwt所面向的用户)
aud: 接收jwt的一方
exp: 过期时间戳(jwt的过期时间,这个过期时间必须要大于签发时间)
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

公共的声明 :
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明 :
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

Signature
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
header (base64后的)
payload (base64后的)
secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
密钥secret是保存在服务端的,服务端会根据这个密钥进行生成token和进行验证,所以需要保护好。

SpringBoot+Spring Security和JWT的集成实现token验证

引入JWT依赖

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

JWT的生成和解析工具类

import io.jsonwebtoken.*;import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/*** @author: xxm* @description:* @date: 2020/5/28 15:53*/
public class JwtUtil {/**过期时间---24 hour*/private static final int EXPIRATION_TIME = 60*60*24;/**自己设定的秘钥*/private static final String SECRET = "023bdc63c3c5a4587*9ee6581508b9d03ad39a74fc0c9a9cce604743367c9646b";/**前缀*/public static final String TOKEN_PREFIX = "Bearer ";/**表头授权*/public static final String AUTHORIZATION = "Authorization";/**** @author: xxm* 功能描述:创建Token* @date: 2020/5/28 16:09* @param: * @return: */public static String generateToken(String userName) {Calendar calendar = Calendar.getInstance();Date now = calendar.getTime();// 设置签发时间calendar.setTime(new Date());// 设置过期时间// 添加秒钟calendar.add(Calendar.SECOND, EXPIRATION_TIME);Date time = calendar.getTime();HashMap<String, Object> map = new HashMap<>();//you can put any data in the mapmap.put("userName", userName);String jwt = Jwts.builder().setClaims(map)//签发时间.setIssuedAt(now)//过期时间.setExpiration(time).signWith(SignatureAlgorithm.HS256, SECRET).compact();//jwt前面一般都会加Bearerreturn TOKEN_PREFIX + jwt;}/**** @author: xxm* 功能描述: 解密Token* @date: 2020/5/28 16:18* @param: * @return: */public static String validateToken(String token) {try {// parse the token.Map<String, Object> body = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token.replace(TOKEN_PREFIX, "")).getBody();String userName = body.get("userName").toString();return userName;}catch (ExpiredJwtException e) {throw e;} catch (UnsupportedJwtException e) {throw e;} catch (MalformedJwtException e) {throw e;} catch (SignatureException e) {throw e;} catch (IllegalArgumentException e) {throw e;} catch (Exception e){throw e;}}
}

Spring Security配置

Spring Security是一个基于Spring的通用安全框架,里面内容太多了,本文的主要目的也不是展开讲这个框架,而是如何利用Spring Security和JWT一起来完成API保护。所以关于Spring Secruity的基础内容或展开内容,请自行去官网学习(官网)。

import com.ggny.emep.interfac.springsecurityconfig.filter.JWTAuthenticationFilter;
import com.ggny.emep.interfac.springsecurityconfig.service.CustomUserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;import javax.sql.DataSource;/**** @author: xxm* 功能描述: SpringSecurity的配置* @date: 2020/5/28 15:14*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {/*** 需要放行的URL*/public static final String[] AUTH_WHITELIST = {"/user/login"// other public endpoints of your API may be appended to this array};@Autowiredprivate DataSource dataSource;@Beanpublic JdbcTokenRepositoryImpl tokenRepository() {JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();tokenRepository.setDataSource(dataSource);//tokenRepository.setCreateTableOnStartup(true); // 启动创建表,创建成功后注释掉return tokenRepository;}@BeanUserDetailsService customUserService() { // 注册UserDetailsService 的beanreturn new CustomUserServiceImpl();}@Overridepublic void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(customUserService()).passwordEncoder(new BCryptPasswordEncoder());}/*** 配置请求拦截*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.cors().and()//由于使用的是JWT,我们这里不需要csrf.csrf().disable()//基于token,所以不需要session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()//可以匿名访问的链接.antMatchers(AUTH_WHITELIST).permitAll()//其他所有请求需要身份认证.anyRequest().authenticated().and()//.addFilter(new JWTLoginFilter(authenticationManager())).addFilter(new JWTAuthenticationFilter(authenticationManager()));}

这是标准的SpringSecurity配置内容,就不在详细说明。注意其中的

.addFilter(new JwtAuthenticationFilter(authenticationManager()))

这行,将我们定义的JWT方法加入SpringSecurity的处理流程中。

重点就是配置请求拦截,由于我做的是一个接口服务,所以暂时除了登录验证,其他接口链接全部都要拦截,通过JWTAuthenticationFilter 过滤器来实现token的验证

登录生成token

验证用户名密码正确后,生成一个token,并将token返回给客户端

/*** 方法名:作用:登陆校验密码* 输入 username password  用户名,密码* 输出:code: 状态码   1 为认证成功 0 为用户不存在 -1 为密码不一致 -2 表示程序错误*       success:  true or false 执行成功或失败*       result:只在认证成功时返回,包含用户的全部信息*       messsage:***/@ResponseBody@RequestMapping(value = "/login", method = RequestMethod.POST)public String toLogin(SysUser user) {JSONObject json=new JSONObject();BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();try {SysUser user1 = userControllerClient.getUserInfoByLoginName(user.getUsername());if (user1!=null) {String dbPassWord = user1.getPassword();if (bCryptPasswordEncoder.matches(user.getPassword(),dbPassWord)) {//创建tokenString token = JwtUtil.generateToken(user.getUsername());json.put("success", true);json.put("code", 1);//json.put("result", user1);json.put("time", DateUtil.dateToString(new Date()));json.put("message", "登陆成功");json.put(JwtUtil.AUTHORIZATION,token);} else {json.put("success", false);json.put("code", -1);json.put("message", "登陆失败,密码错误");}}else {json.put("success", false);json.put("code", 0);json.put("message", "无此用户信息");}} catch (Exception e) {json.put("code", -2);json.put("success", false);json.put("message", e.getMessage());}return JSON.toJSONString(json);}

授权验证

用户一旦登录成功后,会拿到token,后续的请求都会带着这个token,服务端会验证token的合法性。

创建JWTAuthenticationFilter类,我们在这个类中实现token的校验功能。

token的校验

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ggny.emep.interfac.common.JwtUtil;
import com.ggny.emep.interfac.springsecurityconfig.config.SpringSecurityConfig;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
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.ArrayList;
import java.util.Arrays;/*** token的校验* 该类继承自BasicAuthenticationFilter,在doFilterInternal方法中,* 从http头的Authorization 项读取token数据,然后用Jwts包提供的方法校验token的合法性。* 如果校验通过,就认为这是一个取得授权的合法请求* @author xxm*/
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {private final Logger logger = LoggerFactory.getLogger(this.getClass());public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {super(authenticationManager);}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {String url = request.getRequestURI();String header = request.getHeader(JwtUtil.AUTHORIZATION);JSONObject json=new JSONObject();//跳过不需要验证的路径if(null != SpringSecurityConfig.AUTH_WHITELIST&&Arrays.asList(SpringSecurityConfig.AUTH_WHITELIST).contains(url)){chain.doFilter(request, response);return;}if (StringUtils.isBlank(header) || !header.startsWith(JwtUtil.TOKEN_PREFIX)) {json.put("codeCheck", false);json.put("msg", "Token为空");response.setCharacterEncoding("UTF-8");response.getWriter().write(JSON.toJSONString(json));return;}try {UsernamePasswordAuthenticationToken authentication = getAuthentication(request,response);SecurityContextHolder.getContext().setAuthentication(authentication);chain.doFilter(request, response);}catch (ExpiredJwtException e) {//json.put("status", "-2");json.put("codeCheck", false);json.put("msg", "Token已过期");response.setCharacterEncoding("UTF-8");response.getWriter().write(JSON.toJSONString(json));logger.error("Token已过期: {} " + e);} catch (UnsupportedJwtException e) {//json.put("status", "-3");json.put("codeCheck", false);json.put("msg", "Token格式错误");response.setCharacterEncoding("UTF-8");response.getWriter().write(JSON.toJSONString(json));logger.error("Token格式错误: {} " + e);} catch (MalformedJwtException e) {//json.put("status", "-4");json.put("codeCheck", false);json.put("msg", "Token没有被正确构造");response.setCharacterEncoding("UTF-8");response.getWriter().write(JSON.toJSONString(json));logger.error("Token没有被正确构造: {} " + e);} catch (SignatureException e) {//json.put("status", "-5");json.put("codeCheck", false);json.put("msg", "Token签名失败");response.setCharacterEncoding("UTF-8");response.getWriter().write(JSON.toJSONString(json));logger.error("签名失败: {} " + e);} catch (IllegalArgumentException e) {//json.put("status", "-6");json.put("codeCheck", false);json.put("msg", "Token非法参数异常");response.setCharacterEncoding("UTF-8");response.getWriter().write(JSON.toJSONString(json));logger.error("非法参数异常: {} " + e);}catch (Exception e){//json.put("status", "-9");json.put("codeCheck", false);json.put("msg", "Invalid Token");response.setCharacterEncoding("UTF-8");response.getWriter().write(JSON.toJSONString(json));logger.error("Invalid Token " + e.getMessage());}}private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request,HttpServletResponse response)  {String token = request.getHeader(JwtUtil.AUTHORIZATION);if (token != null) {String userName="";try {// 解密TokenuserName = JwtUtil.validateToken(token);if (StringUtils.isNotBlank(userName)) {return new UsernamePasswordAuthenticationToken(userName, null, new ArrayList<>());}}catch (ExpiredJwtException e) {throw e;//throw new TokenException("Token已过期");} catch (UnsupportedJwtException e) {throw e;//throw new TokenException("Token格式错误");} catch (MalformedJwtException e) {throw e;//throw new TokenException("Token没有被正确构造");} catch (SignatureException e) {throw e;//throw new TokenException("签名失败");} catch (IllegalArgumentException e) {throw e;//throw new TokenException("非法参数异常");}catch (Exception e){throw e;//throw new IllegalStateException("Invalid Token. "+e.getMessage());}return null;}return null;}}

该类继承自BasicAuthenticationFilter,在doFilterInternal方法中,从http头的Authorization 项读取token数据,然后用Jwts包提供的方法校验token的合法性。如果校验通过,就认为这是一个取得授权的合法请求。
这其中也包括了,token验证异常处理的返回信息

演示

  1. 当我们没有登录,也没有token时,是访问不了接口的
  2. 只有访问登录接口,并登陆成功,接口会返回给客户端token值

    3.客户端下次访问接口时,需要在表头中带着token值访问,才能通过验证,访问接口

总结

经过以上的配置,我们就完成了简单的访问权限控制和Token认证了,并添加了简单的异常处理,让我们的应用具有更好、更完善的错误提示和更安全的访问控制。

Spring Boot+Spring Security+JWT 实现token验证相关推荐

  1. springboot jwt token前后端分离_基于Spring Boot+Spring Security+JWT+Vue前后端分离的开源项目...

    一.前言 最近整合Spring Boot+Spring Security+JWT+Vue 完成了一套前后端分离的基础项目,这里把它开源出来分享给有需要的小伙伴们 功能很简单,单点登录,前后端动态权限配 ...

  2. Spring Boot + Spring Security + JWT + 微信小程序登录

    Spring Boot + Spring Security + JWT + 微信小程序登录整合教程 参考文章 文章目录 整合思想 整合步骤 1. AuthenticationToken 2. Auth ...

  3. Spring boot 使用QQ邮箱进行一个验证登入

    Spring boot 使用QQ邮箱进行一个验证登入 QQ邮箱开启权限 在QQ邮箱设置->账户里面,往下拉找到这个开启,手机号验证成功后会有一串英文字符串是待会儿要用到的密码. prom.xml ...

  4. SpringBoot集成JWT实现token验证

    Jwt全称是:json web token,以JSON对象的形式安全的传递信息.它将用户信息加密到token里,服务器不保存任何用户信息.服务器通过使用保存的密钥验证token的正确性,只要正确即通过 ...

  5. Spring Boot+Spring Cloud实现itoken项目

    itoken项目简介 开发环境 操作系统: Windows 10 Enterprise 开发工具: Intellij IDEA 数据库: MySql 5.7.22 Java SDK: Oracle J ...

  6. Spring Boot Spring MVC 异常处理的N种方法

    默认行为 根据Spring Boot官方文档的说法: For machine clients it will produce a JSON response with details of the e ...

  7. spring boot + spring batch 读数据库文件写入文本文件读文本文件写入数据库

    好久没有写博客,换了一家新公司,原来的公司用的是spring,现在这家公司用的是spring boot.然后,项目组布置了一个任务,关于两个数据库之间的表同步,我首先想到的就是spring batch ...

  8. jwt token 附加用户信息_SpringBoot+JWT实现token验证并将用户信息存储到@注解内

    springboot集成jwt实现token验证 1.引入jwt依赖 io.jsonwebtoken jjwt 0.9.0 com.auth0 java-jwt 3.9.0 2.自定义两个注解 /** ...

  9. VUE+SpringBoot+JWT实现token验证,SSO单点登录

    Session的产生: 在说session是啥之前,我们先来说说为什么会出现session会话,它出现的机理是什么?我们知道,我们用浏览器打开一个网页,用到的是HTTP协议,htpp协议是无状态的,什 ...

最新文章

  1. 转载 一个渣硕iOS春招总结 | 掘金技术征文
  2. php get 返回源码,php源码 fsockopen获取网页内容实例详解
  3. java中 8进制 10进制 2进制 16进制 相互转换
  4. Kali Linux 从入门到精通(八)-主动信息收集
  5. 删除Github仓库某一次commit信息/历史
  6. 华为怎么删掉android,华为手机怎么卸载软件 华为手机卸载应用软件教程
  7. 大厂面试必考的假设检验
  8. Cisco Packet Tracer交换机划分VLAN
  9. 用excel制作,出入库信息管理表,动态库存表
  10. 怎么把qlv格式转成mp4
  11. Laraval-admin 自定义form组件
  12. 0201电脑桌的制作过程(使用3DsMAX2016)
  13. python中self的个人理解
  14. FPGA 24 工程模块 红外遥控(NEC协议)解码
  15. python如何查看函数功能_python如何查看类的函数
  16. C语言花样霓虹灯程序,LM4229显示屏的单片机按键控制多种花样霓虹灯设计报告与源码...
  17. 红外成像与微光成像的区别
  18. 基于柯蒂斯1232e叉车控制器的智能改造方案
  19. HPE服务器DL380gen9/ Gen10 固件远程升级方法
  20. openwrt 格式化_初始Openwrt

热门文章

  1. 手机静音html还有声音,为什么手机静音了还有声音
  2. sql语句 创建 查询 索引语句
  3. 原码、反码与补码,怎么计算?
  4. Mac版ideaDebug调试快捷键
  5. Mac OS X查看ip地址
  6. IDEA 初次使用菜鸟教程
  7. Shell菜单脚本输出带颜色字体
  8. Kill杀死进程方法大全
  9. 微软小冰智能聊天是如何实现的?
  10. 计算机视觉——运用pycharm建构的物体识别软件