一、pom.xml引入依赖
<dependency>
<groupid>io.jsonwebtoken</groupid>
<artifactid>jjwt</artifactid>
<version>0.9.1</version>
</dependency>
<dependency>
<groupid>javax.xml.bind</groupid>
<artifactid>jaxb-api</artifactid>
<version>2.3.1</version>
</dependency>二、创建工具类jwtutils
public class jwtutils {
private static final hashmap<string, string> _audiences = new hashmap<>();
public static string createjwt(userproviderdto user) {
// 指定签名的时候使用的签名算法,也就是header那部分
signaturealgorithm signaturealgorithm = signaturealgorithm.hs256;
hashmap<string, object> claims = new hashmap<>();
claims.put(const.jwt_claims_user, json.tojsonstring(user));
// 设置jwt的body
jwtbuilder builder = jwts.builder()
.setclaims(claims)
.setsubject(user.getusername())
.setaudience(updateaudience(user.getusername()))
.setissuedat(new date())
// 设置签名使用的签名算法和签名使用的秘钥
.signwith(signaturealgorithm, const.jwt_secret_key.getbytes(standardcharsets.utf_8))
// 设置过期时间
.setexpiration(timeutils.gettodayend());
return builder.compact();
}
/**
* token解密
*/
public static claims parsejwt(string token) {
try {
// 得到defaultjwtparser
return jwts.parser()
// 设置签名的秘钥
.setsigningkey(const.jwt_secret_key.getbytes(standardcharsets.utf_8))
// 设置需要解析的jwt
.parseclaimsjws(token)
.getbody();
}
catch (exception ex) {
return null;
}
}
/**
* 更新当前登录账号的最新状态
* @param userno 用户名
* @return 状态标志
*/
public static string updateaudience(string userno) {
if (logwingstringutils.isnullorempty(userno))
return "";
var audience = userno + timeutils.getcurrenttimestamp();
_audiences.put(userno, audience);
return audience;
}
/**
* 判断当前token是否最新的,不是需重新登录
* @return boolean
*/
public static boolean isnewestaudience(string userno, string audience) {
if (logwingstringutils.isnullorempty(userno) || logwingstringutils.isnullorempty(audience))
return false;
if (!_audiences.containskey(userno))
return false;
return _audiences.get(userno).equals(audience);
}
/**
* 获取token
* @param request 当前http请求
* @return token
*/
public static string gettoken(httpservletrequest request) {
string token = request.getheader("authorization");
if (logwingstringutils.isnullorempty(token)) {
return "";
}
token = token.replace("bearer ", "");
if (logwingstringutils.isnullorempty(token)) {
return "";
}
return token;
}
}三、添加全局拦截器,校验token是否有效
public class jwtinterceptor implements handlerinterceptor {
//主要功能拦截请求验证token
@override
public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) throws exception {
string token = jwtutils.gettoken(request);
if (logwingstringutils.isnullorempty(token)) {
throw new invaliduserexception();
}
var claims = jwtutils.parsejwt(token);
if (claims == null) {
throw new invaliduserexception();
}
date createtime = claims.getissuedat();
if (createtime == null || createtime.compareto(timeutils.gettodaystart()) < 0) {
throw new invaliduserexception();
}
string audience = claims.getaudience();
if (!jwtutils.isnewestaudience(claims.getsubject(), audience)) {
throw new invaliduserexception();
}
return true;
}
}四、将拦截器添加到webconfig配置中
拦截请求进行token校验
@configuration
public class webconfig implements webmvcconfigurer {
@override
public void addinterceptors(interceptorregistry registry) {
registry.addinterceptor(new jwtinterceptor())
//拦截的路径
.addpathpatterns("/**")
//不需要的拦截请求
.excludepathpatterns(
"/api/oauth/login",
"/api/oauth/logout",
"/api/getdate",
"/swagger-ui/**",
"/v3/**");
}
}五、登录接口增加生成token
返回前端,账号密码校验完成之后生成token,将用户基本信息存入token
public loginoutputdto login(loginuserinputdto input) {
admin loginuser = adminrepository.getloginuserbyusername(input.getusername());
if (loginuser == null)
return new loginoutputdto(false, "用户名不存在,请重新输入。");
var inputpassword = rsautils.decryptbyrsa(input.getencryptedpassword());
var encryptpassword = md5utils.encryptmd5(md5utils.encryptmd5(inputpassword) + loginuser.getsalt());
if (!encryptpassword.equals(loginuser.getpassword()))
return new loginoutputdto(false, "密码有误,请重新输入。");
userproviderdto userprovider = getuserprovider(loginuser);
string token = jwtutils.createjwt(userprovider);
loginoutputdto output = new loginoutputdto(true, "登录成功。");
output.setaccesstoken(token);
return output;
}
private userproviderdto getuserprovider(admin loginuser) {
userproviderdto userprovider = new userproviderdto();
userprovider.setid(loginuser.getid());
userprovider.setusername(loginuser.getusername());
userprovider.setstatus(loginuser.getstatus());
userprovider.settype(loginuser.gettype());
return userprovider;
}
}六、定义userprovider
提供从token解析出当前用户信息的接口方法
@service
public class userprovider implements iuserprovider {
@autowired
private httpservletrequest httpservletrequest;
@override
public userproviderdto getcurrentuser() {
string token = jwtutils.gettoken(httpservletrequest);
if (logwingstringutils.isnullorempty(token)) {
throw new runtimeexception(const.un_login_tips);
}
claims claims = jwtutils.parsejwt(token);
if (claims == null) {
throw new runtimeexception(const.un_login_tips);
}
date createtime = claims.getissuedat();
if (createtime == null || createtime.compareto(timeutils.gettodaystart()) < 0) {
throw new runtimeexception(const.un_login_tips);
}
string user = (string) claims.get(const.jwt_claims_user);
return json.parseobject(user, userproviderdto.class);
}
}七、定义controller接口
登录接口
@postmapping("/api/oauth/login")
@responsebody
@operation(summary = "登录授权接口", description = "登录授权接口")
public loginoutputdto login(@valid @requestbody loginuserinputdto input) throws nosuchalgorithmexception {
return loginservice.login(input);
}退出接口
退出时更新当前账号的audience,用来避免退出后用旧的token仍然可以通过接口token是否有效的拦截(因为jwt具有无状态性,生成token之后改token只能等设置的过期时间到了之后才能自动销毁,否则该token在过期时间之前仍然有效),具体方法见jwtutils中的updateaudience和isnewestaudience方法
@postmapping("/api/oauth/logout")
@responsebody
@operation(summary = "退出接口")
public responseoutputdto logout() {
var user = userprovider.getcurrentuser();
jwtutils.updateaudience(user.getusername());
return new responseoutputdto();
}八、自定义异常invaliduserexception
并拦截全局异常invaliduserexception,捕捉拦截器抛出的异常,返回给前端
public class invaliduserexception extends exception {
public invaliduserexception() {
super(const.un_login_tips);
}
}@controlleradvice
@slf4j
public class systemexceptionhandler {
@exceptionhandler(invaliduserexception.class)
@responsebody
public loginoutputdto doexception(invaliduserexception ex) {
return new loginoutputdto(false, ex.getmessage());
}
@exceptionhandler(exception.class)
@responsebody
public responseoutputdto doexception(exception ex) {
if (ex.getmessage().contains("未登录")) {
return new responseoutputdto(false, ex.getmessage());
}
log.error(ex.getmessage(), ex);
return new responseoutputdto(false, "程序发生错误,请联系客服。");
}
}总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论