> 文档中心 > Springboot +JWT实现登录认证,密码加密及Token校验全过程(附源码)

Springboot +JWT实现登录认证,密码加密及Token校验全过程(附源码)

JWT实现登录认证

  • 简介
    • 环境
    • 1. 依赖
    • 2. token生成及校验
    • 3. 登录
    • 4. 编写拦截器进行token校验
    • 5. 源码下载

简介

  • 通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。JWT的认证流程如下:

  • 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口,这个过程一般是一个POST请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探

  • 后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Header分别进行Base64编码拼接后签名,形成一个JWT Token,形成的JWT Token就是一个如同lll.zzz.xxx的字符串

  • 后端将JWT Token字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token即可

  • 前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中(解决XSS和XSRF问题)

  • 后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等

  • 验证通过后,后端解析出JWT Token中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果

Springboot +JWT实现登录认证,密码加密及Token校验全过程(附源码)
本博客项目源码地址:

  • 项目源码github地址
  • 项目源码国内gitee地址

环境

本教程使用jdk11,其他环境自行测试
Springboot +JWT实现登录认证,密码加密及Token校验全过程(附源码)

api测试工具postman{{localhost}}请自行更改为自己的地址,如 localhost:9999
Springboot +JWT实现登录认证,密码加密及Token校验全过程(附源码)

1. 依赖

  • 1.1 pom导入依赖
<dependency>     <groupId>org.projectlombok</groupId>     <artifactId>lombok</artifactId>     <optional>true</optional> </dependency> <dependency>     <groupId>io.springfox</groupId>     <artifactId>springfox-boot-starter</artifactId>     <version>3.0.0</version> </dependency>  <dependency>     <groupId>javax.xml.bind</groupId>     <artifactId>jaxb-api</artifactId> </dependency>  <dependency>     <groupId>io.jsonwebtoken</groupId>     <artifactId>jjwt</artifactId>     <version>0.9.1</version> </dependency>  <dependency>     <groupId>com.auth0</groupId>     <artifactId>java-jwt</artifactId>     <version>3.18.3</version> </dependency>

2. token生成及校验

  • 2.1 封装用户

存储用户的基本信息

import lombok.Data;import lombok.experimental.Accessors;/ * @author l */@Data@Accessors(chain = true)public class JwtUser {    private boolean valid;    private String userId;    private String role;    public JwtUser() { this.valid = false;    }}
  • 2.2 编写JWT提供者

主要关注 createToken 和 checkToken 两个方法

  • createToken 生成token
  • checkToken 校验token

createToken

import io.jsonwebtoken.*;import io.swagger.annotations.ApiModel;import io.swagger.annotations.ApiModelProperty;import lombok.extern.slf4j.Slf4j;import java.nio.charset.StandardCharsets;import java.util.Base64;import java.util.Date;/ * date: 2021-01-05 08:48 * description token管理 * * @author qiDing */@Slf4j@ApiModel("token提供者")public class TokenProvider {    @ApiModelProperty("盐")    private static final String SALT_KEY = "links";    @ApiModelProperty("令牌有效期毫秒")    private static final long TOKEN_VALIDITY = 86400000;    @ApiModelProperty("权限密钥")    private static final String AUTHORITIES_KEY = "auth";    @ApiModelProperty("Base64 密钥")    private final static String SECRET_KEY =  Base64.getEncoder().encodeToString(SALT_KEY.getBytes(StandardCharsets.UTF_8));    /     * 生成token     * @param userId 用户id     * @param clientId 用于区别客户端,如移动端,网页端,此处可根据自己业务自定义     * @param role 角色权限     */    public static String createToken(String userId, String clientId, String role) { Date validity = new Date((new Date()).getTime() + TOKEN_VALIDITY); return Jwts.builder()  // 代表这个JWT的主体,即它的所有人  .setSubject(String.valueOf(userId))  // 代表这个JWT的签发主体  .setIssuer("")  // 是一个时间戳,代表这个JWT的签发时间;  .setIssuedAt(new Date())  // 代表这个JWT的接收对象  .setAudience(clientId)  .claim("role", role)  .claim("userId", userId)  .signWith(SignatureAlgorithm.HS512, SECRET_KEY)  .setExpiration(validity)  .compact();    }    /     * 校验token     */    public static JwtUser checkToken(String token) { if (validateToken(token)) {     Claims claims = Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();     String audience = claims.getAudience();     String userId = claims.get("userId", String.class);     String role = claims.get("role", String.class);     JwtUser jwtUser = new JwtUser().setUserId(userId).setRole(role).setValid(true);     log.info("===token有效{},客户端{}", jwtUser, audience);     return jwtUser; } log.error("*token无效*"); return new JwtUser();    }    private static boolean validateToken(String authToken) { try {     Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(authToken);     return true; } catch (Exception e) {     log.error("无效的token:" + authToken); } return false;    }}

3. 登录

  • 3.1 密码加密

对密码进行md5加密

import org.springframework.util.DigestUtils;/ * 密码加密工具类 * * @author liangQiDing */public class PasswordEncoder {    /     * 密码加密     * @param rawPassword 登录时传入的密码     */    public static String encode(CharSequence rawPassword) { return DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes());    }    /     * 密码对比     * @param rawPassword 登录时传入的密码     * @param encodedPassword 数据库保存的加密过的密码     */    public static boolean matches(CharSequence rawPassword, String encodedPassword) { return encodedPassword.equals(DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes()));    }}
  • 3.2 登录接口编写
import com.example.jwt_dome.config.PasswordEncoder;import com.example.jwt_dome.jwt.AuthStorage;import com.example.jwt_dome.jwt.JwtUser;import com.example.jwt_dome.jwt.TokenProvider;import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;/ * @author liangQiDing */@RestController@Api("token测试服务器")public class TokenController {    /     * 模拟数据库数据 账号 admin  密码 123456     */    private final static HashMap<String, String> USER = new HashMap<>() { {     put("admin", "e10adc3949ba59abbe56e057f20f883e"); }    };    @GetMapping("/login")    @ApiOperation("登陆示例(账号admin,密码123456)")    public String login(String username, String password) { if (PasswordEncoder.matches(password, USER.get(username))) {     // 模拟一个用户的数据 用户id为1  登录端为网页web  角色是admin     return TokenProvider.createToken("1", "web", "admin"); } return "error";    }    @GetMapping("/token/validate")    @ApiOperation("token校验")    public JwtUser tokenValidate(String token) { return TokenProvider.checkToken(token);    }}
  • 3.3 token获取测试
    Springboot +JWT实现登录认证,密码加密及Token校验全过程(附源码)

  • 3.4 token校验测试
    Springboot +JWT实现登录认证,密码加密及Token校验全过程(附源码)

4. 编写拦截器进行token校验

  • 4.1 存储授权信息

用于在我们授权通过后,在请求中获取用户的信息

/ * 存储本次请求的授权信息,适用于各种业务场景,包括分布式部署 * * @author lqd */public class AuthStorage {    @ApiModelProperty("请求头token的下标")    public static final String TOKEN_KEY = "token";    /     * 模拟session     */    private static final HashMap<String, JwtUser> JWT_USER = new HashMap<String, JwtUser>();    /     * 全局获取用户     */    public static JwtUser getUser() { HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); return JWT_USER.get(request.getHeader(TOKEN_KEY));    }    /     * 设置用户     */    public static void setUser(String token, JwtUser user) { JWT_USER.put(token, user);    }    /     * 清除授权     */    public static void clearUser() { HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); JWT_USER.remove(request.getHeader(TOKEN_KEY));    }}
  • 4.2 配置拦截器

在请求响应前,校验token,校验通过后存储用户信息。

import com.example.jwt_dome.jwt.AuthStorage;import com.example.jwt_dome.jwt.JwtUser;import com.example.jwt_dome.jwt.TokenProvider;import lombok.extern.slf4j.Slf4j;import org.springframework.util.StringUtils;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;/ * 拦截器 * * @author lqd */@Slf4jpublic class AuthInterceptor implements HandlerInterceptor {    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader(AuthStorage.TOKEN_KEY); if (StringUtils.hasLength(token)) {     JwtUser jwtUser = TokenProvider.checkToken(token);     // 是否认证通过     if (jwtUser.isValid()) {  // 保存授权信息  AuthStorage.setUser(token, jwtUser);  return true;     } } response.setContentType("text/html;charset=utf-8"); response.getWriter().write("请先登录!"); return false;    }    @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object      handler, Exception ex) throws Exception { // 请求完成清除授权信息 AuthStorage.clearUser(); HandlerInterceptor.super.afterCompletion(request, response, handler, ex);    }}
  • 4.3 配置拦截路径
import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/ * 配置拦截器路径 * * @author lqd */@Configurationpublic class WebMvcConfig implements WebMvcConfigurer {    @Override    public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor())  // 拦截的路径  .addPathPatterns("/")  // 开放的路径  .excludePathPatterns("/login/", "/token/validate");    }}
  • 4.4 拦截测试

在controller层添加测试接口

    @GetMapping("/get/Info")    @ApiOperation("模拟拦截")    public String getInfo() { // 从全局环境中获取用户id JwtUser user = AuthStorage.getUser(); return "用户:"+user.getUserId() + ",请求成功";    }

普通访问
Springboot +JWT实现登录认证,密码加密及Token校验全过程(附源码)

请求头添加token后再访问
Springboot +JWT实现登录认证,密码加密及Token校验全过程(附源码)

5. 源码下载

  • Springboot开发脚手架,集合各种常用框架使用案例,完善的文档,致力于让开发者快速搭建基础环境并让应用跑起来。
  • 项目源码国内gitee地址
  • 项目源码github地址

牙刷城