2025年JWT单点登录功能

JWT单点登录功能如题 要使用 JWT 实现单点登录功能 只实现了一个简单的注册 登录功能 目录 思路 注册功能 界面展示以及代码逻辑 MD5 的加密算法 JWT 生成 Token 单点登录 示例 注册拦截器验证 Token 思路 以注册功能为例 前端注册平台 向后端发送用户名密码 后端保存到数据库 并且利用 JWT 生成一串 token 返回给前端 注册拦截器 此后前端每次访问后端接口 都会经过拦截器 拦截器对 token 进行解析

如题,要使用JWT实现单点登录功能,只实现了一个简单的注册、登录功能。

目录

思路

注册功能

界面展示以及代码逻辑

MD5的加密算法

JWT生成Token

单点登录

示例

注册拦截器验证Token

思路

以注册功能为例,前端注册平台,向后端发送用户名密码,后端保存到数据库,并且利用JWT生成一串token返回给前端,注册拦截器,此后前端每次访问后端接口,都会经过拦截器,拦截器对token进行解析,成功则继续逻辑,失败则返回错误信息。失效则需要重新登录。登录功能和注册功能差不多,只是一个查询,一个保存,其他逻辑相同。

注册功能

界面展示以及代码逻辑

前端代码很简单,这里就不详细说前端了。主要就是做了两个输入框以及一个提交按钮,如果哪里写的不正确,欢迎前端大神们指正,代码如下:


         
         
         
         
         
         
         
    
           







后端代码:

    @PostMapping("/register")
public HashMap register(@RequestParam("name") String name,
@RequestParam("password") String password) throws Exception {
//保存用户信息
int res = sysUserService.saveUserInfo(name, password);
HashMap resultMap = new HashMap();
//生成token信息
String token = JWTHS256.buildJWT(name);
resultMap.put("res", res);
//前端返回token信息
resultMap.put("token", token);
return resultMap;
}











数据库数据信息,密码经过两次MD5算法加密

注册成功后将token数据返回给前端了,用于用户进入平台进行操作时使用

中间插一段,这里介绍一下MD5加密以及JWT生成token的过程:

MD5的加密算法

这个并不属于单点登录的必要步骤,但是我从网上搜了一下md5的激活成功教程,感觉激活成功教程起来也是有一定难度的,所以将密码加密保存到数据库还是安全一些。这里是对密码进行两次md5加密保存到数据库

public int saveUserInfo(String name, String pwd) throws Exception {
SysUser sysUser = new SysUser();
sysUser.setName(name);
//MD5加密算法
sysUser.setPassword(MD5.md5EncodeSecondary(pwd));
sysUser.setId(UUID.randomUUID().toString());
return sysUserMapper.save(sysUser);
}

public static String md5EncodeSecondary(String noEncrypt) throws Exception {
return md5Encode(noEncrypt + md5Encode(noEncrypt));
}

public static String md5Encode(String inStr) throws Exception {
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (Exception e) {
System.out.println(e.toString());
e.printStackTrace();
return "";
}

byte[] byteArray = inStr.getBytes("UTF-8");
byte[] md5Bytes = md5.digest(byteArray);
StringBuffer hexValue = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
int val = ((int) md5Bytes[i]) & 0xff;
if (val < 16) {
hexValue.append("0");
}
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
}

































保存到数据库之后,就是要生成前端访问后端程序的凭证了,也就是JWT加密算法生成的token返回给前端,前端访问后端时将token作为凭证访问后端。

JWT生成Token

JWT,JSON Web Token的简称。它是由是三个部分组成:头部,载体,签名。格式如下:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiQUNDT1VOVCI6IuW8oOS4iSIsImlzcyI6InVzZXIiLCJleHAiOjE2MDY2OTMyODd9.gL2kThdumyBydaal6s7-DCf5GV-1FvioRmrK2R1XhcA

头部是用于描述关于JWT的基本信息,例如其类型以及用到算法等。JWT有两种加密算法,一是HS256,对称加密算法;另一种是RS256,非对称加密算法。本示例我用的是HS256算法。

载体是存储自定义数据,一般用于存储用户标识,过期时间等信息,是JWT的核心。因为这些数据是后端知道是谁在登录的凭证,而这些数据是存在于token里面的,由token携带,因此后端几乎不需要保存任何数据。

签名是头部base64加密.载体的base64的加密.前两段加入一个密匙用HS256算法或者其他算法加密形成。

JWT生成token逻辑代码如下:

    @PostMapping("/register")
public HashMap register(@RequestParam("name") String name,
@RequestParam("password") String password) throws Exception {
//保存用户信息
int res = sysUserService.saveUserInfo(name, password);
HashMap resultMap = new HashMap();
//JWT生成token信息
String token = JWTHS256.buildJWT(name);
resultMap.put("res", res);
resultMap.put("token", token);
return resultMap;
}


/
* 生成token
/
public static String buildJWT(String account) {

try {
/
* 创建一个32-byte的密钥
*/
MACSigner macSigner = new MACSigner(SECRET);
/
* 建立payload载体
*/
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
.subject("user")
.issuer("user")
.expirationTime(new Date(System.currentTimeMillis()))
.claim("ACCOUNT", account)
.build();
/
* 建立签名,
这里创建SignedJWT 对象时,传了两个参数,一个是头部信息,一个是载体
*/
SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claimsSet);
signedJWT.sign(macSigner);
String token = signedJWT.serialize();
return token;
} catch (KeyLengthException e) {
e.printStackTrace();
} catch (JOSEException e) {
e.printStackTrace();
}
return null;
}














































头部以及载体加密

//创建签名,传参,一个是头部,一个是载体  
SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claimsSet);
=====================================================================================

//1.头部生成逻辑
//初始化一个头部对象JWSHeader
new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claimsSet)
public JWSHeader(JWSAlgorithm alg) {
this(alg, (JOSEObjectType)null, (String)null, (Set)null, (URI)null, (JWK)null, (URI)null, (Base64URL)null, (Base64URL)null, (List)null, (String)null, (Map)null, (Base64URL)null);
}
public JWSHeader(JWSAlgorithm alg, JOSEObjectType typ, String cty, Set crit, URI jku, JWK jwk, URI x5u, Base64URL x5t, Base64URL x5t256, List x5c, String kid, Map customParams, Base64URL parsedBase64URL) {
super(alg, typ, cty, crit, jku, jwk, x5u, x5t, x5t256, x5c, kid, customParams, parsedBase64URL);
if (alg.getName().equals(Algorithm.NONE.getName())) {
throw new IllegalArgumentException("The JWS algorithm \"alg\" cannot be \"none\"");
}
}
=====================================================================================

//2.载体生成逻辑:
//载体生成可以使用的方法
public static class Builder {
private final Map claims = new LinkedHashMap();

public Builder() {
}

public Builder(JWTClaimsSet jwtClaimsSet) {
this.claims.putAll(jwtClaimsSet.claims);
}
//JWT签发者
public JWTClaimsSet.Builder issuer(String iss) {
this.claims.put("iss", iss);
return this;
}
//JWT面向的用户
public JWTClaimsSet.Builder subject(String sub) {
this.claims.put("sub", sub);
return this;
}
//接收JWT的用户,列表
public JWTClaimsSet.Builder audience(List aud) {
this.claims.put("aud", aud);
return this;
}
//接收JWT的用户,单个信息
public JWTClaimsSet.Builder audience(String aud) {
if (aud == null) {
this.claims.put("aud", (Object)null);
} else {
this.claims.put("aud", Collections.singletonList(aud));
}

return this;
}
// jwt的过期时间,这个过期时间必须要大于签发时间
public JWTClaimsSet.Builder expirationTime(Date exp) {
this.claims.put("exp", exp);
return this;
}
//定义在什么时间之前,该jwt都是不可用的
public JWTClaimsSet.Builder notBeforeTime(Date nbf) {
this.claims.put("nbf", nbf);
return this;
}
// jwt的签发时间
public JWTClaimsSet.Builder issueTime(Date iat) {
this.claims.put("iat", iat);
return this;
}
//jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
public JWTClaimsSet.Builder jwtID(String jti) {
this.claims.put("jti", jti);
return this;
}
//自定义声明
public JWTClaimsSet.Builder claim(String name, Object value) {
this.claims.put(name, value);
return this;
}

public JWTClaimsSet build() {
return new JWTClaimsSet(this.claims, (JWTClaimsSet)null);
}
}
//本次示例中载体使用;
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
.subject("user")
.issuer("user")
.expirationTime(new Date(System.currentTimeMillis()))
.claim("ACCOUNT", account)
.build();
可以根据项目需要使用这些注册信息
=====================================================================================

//3.对生成JWSHeader、claimsSet载体对象进行base64加密

public SignedJWT(JWSHeader header, JWTClaimsSet claimsSet) {
super(header, new Payload(claimsSet.toJSONObject()));
}

public JWSObject(JWSHeader header, Payload payload) {
if (header == null) {
throw new IllegalArgumentException("The JWS header must not be null");
} else {
this.header = header;
if (payload == null) {
throw new IllegalArgumentException("The payload must not be null");
} else {
this.setPayload(payload);
//base64加密,并且设置公共变量-signingInputString,提供后面使用
this.signingInputString = composeSigningInput(header.toBase64URL(), payload.toBase64URL());
this.signature = null;
//设置当前状态为未签名状态,避免重复设置签名
this.state = JWSObject.State.UNSIGNED;
}
}
}
=====================================================================================

//4.组成前两段
private static String composeSigningInput(Base64URL firstPart, Base64URL secondPart) {
return firstPart.toString() + '.' + secondPart.toString();
}

























































































































签名生成

接下来就是对前两段加密生成第三段内容,主要用HMAC加密算法,感觉有些看不懂,欢迎大神指点一下加密算法的逻辑,这里主要介绍签名生成逻辑。

 /
* 建立签名
*/
SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claimsSet);
signedJWT.sign(macSigner);

//签名生成
public synchronized void sign(JWSSigner signer) throws JOSEException {
//验证状态是否是未签名状态
this.ensureUnsignedState();
//检查当前头部信息是否在缓存中
this.ensureJWSSignerSupport(signer);

try {
//加密,sign方法根据生成密钥的类选择,这里我使用的是MACSigner类,因此使用MACSigner的sign方法
this.signature = signer.sign(this.getHeader(), this.getSigningInput());
} catch (JOSEException var3) {
throw var3;
} catch (Exception var4) {
throw new JOSEException(var4.getMessage(), var4);
}
//设置签名状态为已签名
this.state = JWSObject.State.SIGNED;
}

public Base64URL sign(JWSHeader header, byte[] signingInput) throws JOSEException {
//获取加密算法最小长度
int minRequiredLength = getMinRequiredSecretLength(header.getAlgorithm());
if (this.getSecret().length < ByteUtils.byteLength(minRequiredLength)) {
throw new KeyLengthException("The secret length for " + header.getAlgorithm() + " must be at least " + minRequiredLength + " bits");
} else {
//获取加密算法
String jcaAlg = getJCAAlgorithmName(header.getAlgorithm());
byte[] hmac = HMAC.compute(jcaAlg, this.getSecret(), signingInput, this.getJCAContext().getProvider());
//将经过HMAC加密后的数值进行二次加密,获得签名
return Base64URL.encode(hmac);
}
}




































这样JWT生成的token就形成了。

单点登录

示例

实现一个单点登录功能,获取用户信息。

注册拦截器验证Token

后端返回给前端token之后,前端每次访问后端,将token信息放在头信息中,后端创建拦截器,拦截前端传给后端的参数,并且解析,比对token信息是否正确。

前端token存放:

传值

后端拦截器:

拦截配置类WebConfigurer:

package com.zxy.system.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/
* 配置拦截器
*/
@Configuration
public class WebConfigurer implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;

/
* 拦截静态资源
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
}

/
* 注册拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/")
//过滤掉不需要拦截的接口
.excludePathPatterns("/login", "/register","/error");
}
}


































拦截器LoginInterceptor:

package com.zxy.system.config;

import com.zxy.system.util.JWTHS256;
import org.assertj.core.util.Strings;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object obj) throws IOException {
String token = httpServletRequest.getHeader("authorization");

//无token、token失效、token无权限
if (Strings.isNullOrEmpty(token) || !JWTHS256.validToken(token)) {
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setContentType("text/html; charset=utf-8");
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
return true;
}
}


//验证token
public static boolean validToken(String token) {
try {
SignedJWT jwt = SignedJWT.parse(token);
JWSVerifier verifier = new MACVerifier(SECRET);

//校验是否有效
if (!jwt.verify(verifier)) {
//异常抛错
return false;
}

//校验超时
Date expirationTime = jwt.getJWTClaimsSet().getExpirationTime();
Date now = new Date();
if (DateUtil.timeDifference(now, expirationTime) > EXPIRE_TIME) {
//超时抛错
return false;
}

Object account = jwt.getJWTClaimsSet().getClaim("ACCOUNT");
//是否有openuid
if (Objects.isNull(account)) {
throw ResultException.of(-3, "账号为空");
}
return true;
} catch (ParseException e) {
e.printStackTrace();
} catch (JOSEException e) {
e.printStackTrace();
}
return false;
}






























































单点登录功能就介绍完了。如果有问题或者有更好的建议,欢迎留言评论!

今天的文章 2025年JWT单点登录功能分享到此就结束了,感谢您的阅读。
编程小号
上一篇 2025-06-08 22:40
下一篇 2025-04-22 10:57

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/hz/110442.html