后台管理系统的通用权限解决方案(九)SpringBoot整合jjwt实现登录认证鉴权
- 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 {
@Bean
public 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 {
@Autowired
private 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框架的使用