- 1)创建maven工程
jjwt-login-demo
,并配置其pom.xml文件如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.2.RELEASE</version><relativePath/></parent><groupId>org.example</groupId><artifactId>jjwt-login-demo</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.1.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies>
</project>
- 2)创建
resources/keys
目录,将通过RSA算法生成的公钥和私钥复制到此目录下
- 3)在
resources
目录下创建application.yml文件,配置jjwt相关配置
# jjwt相关配置
auth:token:expire: 3600 #令牌失效时间,单位秒priKey: keys/pri.key #私钥地址pubKey: keys/pub.key #公钥地址
- 4)创建属性类
AuthTokenProperties
,用于接收application.yml文件中的配置
package com.itweid.jjwt.config;import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;@Data
@NoArgsConstructor
@ConfigurationProperties(prefix = "auth.token")
public class AuthTokenProperties {/*** 过期时间。默认2小时=7200秒*/private Integer expire = 7200;/*** 私钥*/private String priKey;/*** 公钥*/private String pubKey;
}
- 5)定义实体类
LoginUser
和Token
,LoginUser
对象保存当前登录用户的信息,这些信息被加密后则使用Token
对象保存
package com.itweid.jjwt.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements Serializable {/*** id*/private Long userId;/*** 账号*/private String account;/*** 姓名*/private String name;/*** 手机*/private String telephone;
}
package com.itweid.jjwt.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;@Data
@NoArgsConstructor
@AllArgsConstructor
public class Token implements Serializable {private static final long serialVersionUID = -8482946147572784305L;/*** token*/private String token;/*** 有效时间:单位:秒*/private Integer expire;
}
- 6)定义一个异常类
BaseException
,用于接收token生成和解析时的异常
package com.itweid.jjwt.exception;import lombok.Getter;@Getter
public class BaseException extends RuntimeException {protected int code;protected String message;public BaseException(int code, String message) {this.code = code;this.message = message;}
}
- 7)定义RSA帮助类
RsaKeyHelper
,封装获取公钥和密钥的方法
package com.itweid.jjwt.utils;import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;/*** RSA帮助类*/
public class RsaKeyHelper {/*** 获取公钥* @param filename 公钥文件地址* @return PublicKey* @throws IOException* @throws NoSuchAlgorithmException* @throws InvalidKeySpecException*/public PublicKey getPublicKey(String filename) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(filename);try (DataInputStream dis = new DataInputStream(resourceAsStream)) {byte[] keyBytes = new byte[resourceAsStream.available()];dis.readFully(keyBytes);X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);KeyFactory kf = KeyFactory.getInstance("RSA");return kf.generatePublic(spec);}}/*** 获取密钥* @param filename 密钥文件地址* @return PrivateKey* @throws IOException* @throws NoSuchAlgorithmException* @throws InvalidKeySpecException*/public PrivateKey getPrivateKey(String filename) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(filename);try (DataInputStream dis = new DataInputStream(resourceAsStream)) {byte[] keyBytes = new byte[resourceAsStream.available()];dis.readFully(keyBytes);PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);KeyFactory kf = KeyFactory.getInstance("RSA");return kf.generatePrivate(spec);}}
}
- 8)定义JWT帮助类
JwtHelper
,封装生成token、从token中获取登录用户信息的方法
package com.itweid.jjwt.utils;import com.itweid.jjwt.exception.BaseException;
import com.itweid.jjwt.pojo.LoginUser;
import com.itweid.jjwt.pojo.Token;
import io.jsonwebtoken.*;import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;public class JwtHelper {private static final RsaKeyHelper RSA_KEY_HELPER = new RsaKeyHelper();private static final String JWT_KEY_ACCOUNT = "account";private static final String JWT_KEY_NAME = "name";private static final String JWT_KEY_TELEPHONE = "telephone";/*** 生成token* @param loginUser* @param priKeyPath* @param expire* @return* @throws BaseException*/public static Token generateToken(LoginUser loginUser, String priKeyPath, int expire) throws BaseException {JwtBuilder jwtBuilder = Jwts.builder()//设置主题.setSubject(String.valueOf(loginUser.getUserId())).claim(JWT_KEY_ACCOUNT, loginUser.getAccount()).claim(JWT_KEY_NAME, loginUser.getName()).claim(JWT_KEY_TELEPHONE, loginUser.getTelephone());return generateToken(jwtBuilder, priKeyPath, expire);}/*** 生成token* @param builder* @param priKeyPath* @param expire* @return Token* @throws BaseException*/protected static Token generateToken(JwtBuilder builder, String priKeyPath, int expire) throws BaseException {try {// 获取过期时间ZonedDateTime zdt = LocalDateTime.now().plusSeconds(expire).atZone(ZoneId.systemDefault());// 返回的字符串便是我们的jwt串了String compactJws = builder.setExpiration(Date.from(zdt.toInstant()))//设置算法(必须).signWith(SignatureAlgorithm.RS256, RSA_KEY_HELPER.getPrivateKey(priKeyPath)).compact();return new Token(compactJws, expire);} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {throw new BaseException(1, "生成token失败");}}/*** 获取token中的用户信息* @param token* @param pubKeyPath* @return LoginUser* @throws BaseException*/public static LoginUser getLoginUserFromToken(String token, String pubKeyPath) throws BaseException {Jws<Claims> claimsJws = parserToken(token, pubKeyPath);Claims body = claimsJws.getBody();String userId = body.getSubject();String account = (String) body.get(JWT_KEY_ACCOUNT);String name = (String) body.get(JWT_KEY_NAME);String telephone = (String) body.get(JWT_KEY_TELEPHONE);return new LoginUser(Long.parseLong(userId), account, name, telephone);}/*** 使用公钥解析token* @param token* @param pubKeyPath 公钥路径* @return Jws<Claims>* @throws BaseException*/private static Jws<Claims> parserToken(String token, String pubKeyPath) throws BaseException {try {return Jwts.parser().setSigningKey(RSA_KEY_HELPER.getPublicKey(pubKeyPath)).parseClaimsJws(token);} catch (ExpiredJwtException ex) {//过期throw new BaseException(2, "token已过期");} catch (SignatureException ex) {//签名错误throw new BaseException(3, "签名错误");} catch (IllegalArgumentException ex) {//token为空throw new BaseException(4, "token为空");} catch (Exception e) {// 其他错误throw new BaseException(5, "未知错误," + e.getMessage());}}
}
- 9)定义token工具类
TokenUtils
,读取application.yml配置文件中的配置生成token或解析token
package com.itweid.jjwt.utils;import com.itweid.jjwt.config.AuthTokenProperties;
import com.itweid.jjwt.exception.BaseException;
import com.itweid.jjwt.pojo.LoginUser;
import com.itweid.jjwt.pojo.Token;
import lombok.AllArgsConstructor;/*** token工具类*/
@AllArgsConstructor
public class TokenUtils {private AuthTokenProperties authTokenProperties;/*** 生成token* @param loginUser* @param expire* @return Token* @throws BaseException*/public Token generateLoginUserToken(LoginUser loginUser, Integer expire) throws BaseException {if (expire == null || expire <= 0) {expire = authTokenProperties.getExpire();}return JwtHelper.generateToken(loginUser, authTokenProperties.getPriKey(), expire);}/*** 解析token* @param token* @return* @throws BaseException*/public LoginUser getLoginUserFromToken(String token) throws BaseException {return JwtHelper.getLoginUserFromToken(token, authTokenProperties.getPubKey());}
}
- 10)创建配置类
AuthTokenConfiguration
,使用@Bean
注解注入TokenUtils
package com.itweid.jjwt.config;import com.itweid.jjwt.utils.TokenUtils;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;@EnableConfigurationProperties(value = {AuthTokenProperties.class,
})
public class AuthTokenConfiguration {@Beanpublic TokenUtils getTokenUtils(AuthTokenProperties authTokenProperties) {return new TokenUtils(authTokenProperties);}
}
- 11)定义注解
@EnableAuthToken
,用于启动认证服务
package com.itweid.jjwt.config;import org.springframework.context.annotation.Import;import java.lang.annotation.*;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(AuthTokenConfiguration.class)
@Documented
@Inherited
public @interface EnableAuthToken {
}
- 12)创建
UserController
类,实现登录功能
package com.itweid.jjwt.controller;import com.itweid.jjwt.pojo.LoginUser;
import com.itweid.jjwt.pojo.Token;
import com.itweid.jjwt.utils.TokenUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate TokenUtils tokenUtils;//用户登录功能,如果登录成功则签发jwt令牌给客户端@GetMapping("/login")public Token login(){String userName = "admin";String password = "admin123";//查询数据库进行用户名密码校验...//如果校验通过,则为客户端生成jwt令牌LoginUser loginUser = new LoginUser();loginUser.setUserId(1L);loginUser.setAccount(userName);loginUser.setName(userName);loginUser.setTelephone("18922102545");Token token = tokenUtils.generateLoginUserToken(loginUser, null);//实际应该是在过滤器中进行jwt令牌的解析LoginUser userInfo = tokenUtils.getLoginUserFromToken(token.getToken());System.out.println(userInfo);return token;}
}
- 13)创建启动类
JjwtApp
,添加@EnableAuthToken
注解
package com.itweid.jjwt;import com.itweid.jjwt.config.EnableAuthToken;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@EnableAuthToken
public class JjwtApp {public static void main(String[] args) {SpringApplication.run(JjwtApp.class, args);}
}
- 14)启动项目,在浏览器访问
http://127.0.0.1:8080/user/login
可见,jwt令牌已经生成,且可以成功解析。后续调用服务的其他请求,只需要将该令牌通过请求头传递到后端服务即可。
…
本节完,更多内容查阅:后台管理系统的通用权限解决方案
延伸阅读:后台管理系统的通用权限解决方案(八)认证机制介绍、JWT介绍与jjwt框架的使用