JWT单点登录功能

JWT单点登录功能如题,要使用SpringBoot+JWT+VUE+Node.js实现单点登录功能

大家好,又见面了,我是你们的朋友全栈君。

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

目录

思路

注册功能

界面展示以及代码逻辑

MD5的加密算法

JWT生成Token

单点登录

示例

注册拦截器验证Token



思路

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

注册功能

界面展示以及代码逻辑

 

JWT单点登录功能

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

<template>
  <div>
    <h1>******用户注册******</h1>
    姓名: <input type="text" name="name" v-model="name"/>
    <br>
    <br>
    密码: <input type="password" name="pwd" v-model="pwd"/>
    <br>
    <label style="color: red;font-size:14px;">{
  
  {message}}</label>
    <br>
    <input type="submit" class="submit" @click="register()">
    <br><br>
    </div>
</template>

<script>
  export default {
    name: 'Register',
    data(){
      return {
        name: '',
        pwd: '',
        message: ''
      }
    },
    methods:{
      register: function() {
        var me = this;
        var url = "/demo/register?name="+ this.name + "&password=" + this.pwd;
        this.$ajax({
          method: 'post',
          url: url,
          contentType: "application/json;charset=utf-8",
          dataType: "text"
        }).then(res =>{
          var data = res.data;
          var token = data.token;
          localStorage.JWT_TOKEN = token;
          this.$router.push({
            path: '/success',
            name: 'Success',
            params: {
              "name": data.user.name,
            }})
        }).catch(function (error) {
          me.message = error.response.data.message;
        });
      }
    }
  }
</script>

<style>
  input{
    border: 1px solid #ccc;
    padding: 7px 0px;
    border-radius: 3px;
    padding-left:5px;
    -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
    box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
    -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
    -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
    transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s
  }
  input:focus{
    border-color: #66afe9;
    outline: 0;
    -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);
    box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)
  }
  .submit {
    width: 100px;
    background-color: #66afe9;
  }
</style>

后端代码:


    @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算法加密

JWT单点登录功能

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

 JWT单点登录功能

 中间插一段,这里介绍一下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<String> crit, URI jku, JWK jwk, URI x5u, Base64URL x5t, Base64URL x5t256, List<Base64> x5c, String kid, Map<String, Object> 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<String, Object> 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<String> 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就形成了。

 

单点登录

示例

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

JWT单点登录功能

 

注册拦截器验证Token

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

前端token存放:

JWT单点登录功能

传值 

JWT单点登录功能

后端拦截器:

JWT单点登录功能

拦截配置类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;
}


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

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

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/143264.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • Python 二进制,十进制,十六进制转换「建议收藏」

    Python 二进制,十进制,十六进制转换「建议收藏」十六进制到十进制使用int()函数,第一个参数是字符串’0Xff’,第二个参数是说明,这个字符串是几进制的数。 转化的结果是一个十进制数。>>>int(‘0xf’,16) 15二进制到十进制>>>int(‘10100111110′,2)   1342八进制到十进制>>>int(’17’,8)  15其实可以

  • vue.js跨域_vueaxios跨域请求

    vue.js跨域_vueaxios跨域请求最近公司能的项目使用前后端分离,前端开发请求接口数据的时候碰到了跨域问题,解决方案如下一、SimpleCORSFilter工具类在项目Util类中新建类SimpleCORSFilter实现Filter接口packagecom.how2java.tmall.util;importjava.io.IOException;importjavax.servlet.Filter…

  • sqrt mysql_详解MySQL中的SQRT函数的使用方法_MySQL

    sqrt mysql_详解MySQL中的SQRT函数的使用方法_MySQLMySQL的SQRT函数是用来计算出任何数量的平方根。可以使用SELECT语句找出方检定根的任意数如下:mysql>selectSQRT(16);+———-+|SQRT(16)|+———-+|4.000000|+———-+1rowinset(0.00sec)所看到的浮点值,因为内部MySQL将处理浮点数据类型的平方根。可以使用SQRT…

  • 探索Java的日志世界

    探索Java的日志世界本文的思维导图一、主题打开日志的大门,探索的Java日志世界二、目标了解常用的日志框架掌握日志框架的选择和使用以及开发规范了解日志框架中的一些设计思想三、内容1、日志及日志框架简介1.1 、日志简介1.1.1 、 什么是日志?1)基本字义是指工作日志 ,详细介绍一个过程和经历的记录。 日志(汉语词汇)…

  • C++之tellg和seekg

    C++之tellg和seekglong pos = file.tellg(); // 得到文件指针当前指向的文件位置。file.seekg(0,ios::beg); //让文件指针定位到文件开头file.seekg(0,ios::end); //让文件指针定位到文件末尾file.seekg(10,ios::cur); //让文件指针从当前位置向文件末方向移动10个字节file.seekg

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号