SpringBoot 集成jwt 实现token验证

来点直接的:

          github上已经发布了相关的源码:https://github.com/78654Majesty/user 谢谢你的关注和点赞 谢谢!

什么是jwt?

JWT官网: https://jwt.io/
JWT(Java版)的github地址:https://github.com/jwtk/jjwt

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).定义了一种简洁的,自包含的方法用于通信双方之间以JSON对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。

JWT请求流程

image.png

1. 用户使用账号和面发出post请求;
2. 服务器使用私钥创建一个jwt;
3. 服务器返回这个jwt给浏览器;
4. 浏览器将该jwt串在请求头中像服务器发送请求;
5. 服务器验证该jwt;
6. 返回响应的资源给浏览器。

JWT的主要应用场景

身份认证在这种场景下,一旦用户完成了登陆,在接下来的每个请求中包含JWT,可以用来验证用户身份以及对路由,服务和资源的访问权限进行验证。由于它的开销非常小,可以轻松的在不同域名的系统中传递,所有目前在单点登录(SSO)中比较广泛的使用了该技术。 信息交换在通信的双方之间使用JWT对数据进行编码是一种非常安全的方式,由于它的信息是经过签名的,可以确保发送者发送的信息是没有经过伪造的。

优点

1.简洁(Compact): 可以通过URLPOST参数或者在HTTP header发送,因为数据量小,传输速度也很快
2.自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库
3.因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
4.不需要在服务端保存会话信息,特别适用于分布式微服务。

`

JWT的结构

JWT是由三段信息构成的,将这三段信息文本用.连接一起就构成了JWT字符串。
就像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

JWT包含了三部分:
Header 头部(标题包含了令牌的元数据,并且包含签名和/或加密算法的类型)
Payload 负载 (类似于飞机上承载的物品)
Signature 签名/签证

Header

JWT的头部承载两部分信息:token类型和采用的加密算法。

{ 
  "alg": "HS256",
   "typ": "JWT"
} 

声明类型:这里是jwt
声明加密的算法:通常直接使用 HMAC SHA256

加密算法是单向函数散列算法,常见的有MD5、SHA、HAMC。
MD5(message-digest algorithm 5) (信息-摘要算法)缩写,广泛用于加密和解密技术,常用于文件校验。校验?不管文件多大,经过MD5后都能生成唯一的MD5值
SHA (Secure Hash Algorithm,安全散列算法),数字签名等密码学应用中重要的工具,安全性高于MD5
HMAC (Hash Message Authentication Code),散列消息鉴别码,基于密钥的Hash算法的认证协议。用公开函数和密钥产生一个固定长度的值作为认证标识,用这个标识鉴别消息的完整性。常用于接口签名验证

Payload

载荷就是存放有效信息的地方。
有效信息包含三个部分
1.标准中注册的声明
2.公共的声明
3.私有的声明

标准中注册的声明 (建议但不强制使用) :

iss: jwt签发者
sub: 面向的用户(jwt所面向的用户)
aud: 接收jwt的一方
exp: 过期时间戳(jwt的过期时间,这个过期时间必须要大于签发时间)
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

公共的声明 :

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明 :

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

Signature

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
header (base64后的)
payload (base64后的)
secret

这个部分需要base64加密后的headerbase64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
密钥secret是保存在服务端的,服务端会根据这个密钥进行生成token和进行验证,所以需要保护好。

下面来进行SpringBoot和JWT的集成 使用gradle 

项目结构

在maven仓库https://mvnrepository.com/ 可以查询自己需要的jar或者需要转化的jar

创建springboot web项目 加入redis、tk通用mapper 、谷歌java工具包guava、以及jjwt

plugins {
    id 'org.springframework.boot' version '2.1.3.RELEASE'
    id 'java'
    id 'war'
}

apply plugin: 'io.spring.dependency-management'

group = 'com.security'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
//    mavenCentral()
    maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
    implementation 'org.springframework.boot:spring-boot-starter-web'
//    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.0.0'
    implementation 'com.alibaba:druid-spring-boot-starter:1.1.10'
    implementation group: 'redis.clients', name: 'jedis', version: '3.0.1'
    //java工具包
    implementation group: 'com.google.guava', name: 'guava', version: '27.0.1-jre'
    // https://mvnrepository.com/artifact/tk.mybatis/mapper-spring-boot-starter
    implementation group: 'tk.mybatis', name: 'mapper-spring-boot-starter', version: '2.1.5'
    implementation group: 'tk.mybatis', name: 'mapper', version: '4.1.5'
    implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.0'

    runtimeOnly 'mysql:mysql-connector-java'
    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

需要自定义注解

需要登录才能进行操作的注解@Login

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Login {
    boolean required() default true;
}

@Target:注解的作用目标

@Target(ElementType.TYPE)——接口、类、枚举、注解
@Target(ElementType.FIELD)——字段、枚举的常量
@Target(ElementType.METHOD)——方法
@Target(ElementType.PARAMETER)——方法参数
@Target(ElementType.CONSTRUCTOR) ——构造函数
@Target(ElementType.LOCAL_VARIABLE)——局部变量
@Target(ElementType.ANNOTATION_TYPE)——注解
@Target(ElementType.PACKAGE)——包

@Retention:注解的保留位置

RetentionPolicy.SOURCE:这种类型的Annotations只在源代码级别保留,编译时就会被忽略,在class字节码文件中不包含。
RetentionPolicy.CLASS:这种类型的Annotations编译时被保留,默认的保留策略,在class文件中存在,但JVM将会忽略,运行时无法获得。
RetentionPolicy.RUNTIME:这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。

@Document:说明该注解将被包含在javadoc
@Inherited:说明子类可以继承父类中的该注解


本人对jwt生成token进行了封装 JwtUtil

package com.security.demo.util;

import com.security.demo.entity.CurrentUser;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.security.PrivateKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * Jtw工具类
 *
 * @author fanglingxiao
 */
public class JwtUtil {

    private static Logger logger = LoggerFactory.getLogger(JwtUtil.class);

    private static final String PAYLOAD_UID = "uid";
    private static final String PAYLOAD_USERNAME = "userName";
    private static final String PAYLOAD_REALNAME = "realName";
    private static final String PAYLOAD_EXPIREIN = "expireIn";
    private static final String PAYLOAD_DEVICE_ID = "deviceId";

    public JwtUtil() {
    }

    /**
     * 加载Properties
     *
     * @return Properties
     */
    private static Properties getProperties() {
        Properties properties = new Properties();
        try {
            properties.load(JwtUtil.class.getResourceAsStream("/application.properties"));
        } catch (IOException e) {
            logger.error("getProperties IOException", e);
        }
        return properties;
    }

    /**
     * 创建jwt
     *
     * @param id  (JWT ID):是JWT的唯一标识
     * @param subject   WT的主体
     * @param ttlMillIn 过期的时间戳
     * @return String String
     */
        public static String createJWT(String id, CurrentUser currentUserVo, String subject, long ttlMillIn) {
        //指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.RS256;
        //生成JWT的时间
        Date iat = new Date();
        //创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
        Map<String, Object> claims = new HashMap<>(16);
        claims.put(PAYLOAD_UID, currentUserVo.getUid());
        claims.put(PAYLOAD_USERNAME, currentUserVo.getUserName());
        claims.put(PAYLOAD_REALNAME, currentUserVo.getRealName());
        claims.put(PAYLOAD_DEVICE_ID,currentUserVo.getDeviceId());
        claims.put(PAYLOAD_EXPIREIN, ttlMillIn);
        //生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取。它就是你服务端的私钥。
        PrivateKey key = RSACryptographyUtil.getPrivateKey(getProperties().getProperty("token.privateKey"));
        //下面就是在为payload添加各种标准声明和私有声明了
        JwtBuilder builder = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", signatureAlgorithm.getValue())
                //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
                //.setId(id)
                //iat: jwt的签发时间
                .setIssuedAt(iat)
                //sub(Subject):代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roleid之类的,作为用户的唯一标志。
                //.setSubject(subject)
                //设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, key);
        if (ttlMillIn >= 0) {
            //设置过期时间Expiration(默认24小时)
            builder.setExpiration(new Date(iat.getTime() + 24 * 60 * 60 * 1000));
        }
        //压缩为xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx这样的jwt
        return builder.compact();
    }

    /**
     * 解析jwt
     *
     * @param jsonWebToken jsonWebToken
     * @return Claims
     */
    public static Claims parseJwt(String jsonWebToken) {
        if (jsonWebToken != null && jsonWebToken.startsWith("Bearer ")) {
            Claims claims;
            try {
                claims = Jwts.parser().setSigningKey(RSACryptographyUtil.getPublicKey(getProperties()
                        .getProperty("token.publicKey"))).parseClaimsJws(jsonWebToken.substring(7)).getBody();
                return claims;
            } catch (Exception e) {
                logger.error("ParseJwt have a exception ",e);
                throw new RuntimeException("TOKEN解析错误");
            }
        } else {
            logger.error("JsonWebToken is not conform to the regulation");
            throw new RuntimeException("TOKEN解析错误");
        }
    }

    public static String getUid(String token) {
        Claims claims = parseJwt(token);
        return (String) claims.get(PAYLOAD_UID);
    }

    public static String getUserName(String token) {
        Claims claims = parseJwt(token);
        return (String) claims.get(PAYLOAD_USERNAME);
    }

    public static String getRealName(String token) {
        Claims claims = parseJwt(token);
        return (String) claims.get(PAYLOAD_REALNAME);
    }

    public static String getDeviceId(String token) {
        Claims claims = parseJwt(token);
        return (String) claims.get(PAYLOAD_DEVICE_ID);
    }

    public static long getExpireIn(String token) {
        Claims claims = parseJwt(token);
        return (long) claims.get(PAYLOAD_EXPIREIN);
    }

    /**
     * 测试
     * Bearer :票据
     */
    public static void main(String[] args) {

        String jwtToken = "Bearer " + createJWT("1", new CurrentUser(), "{\"uId\":\"123\"}", System.currentTimeMillis() + 1000);
        logger.info("jwt={}", jwtToken);

        String uId = getUid(jwtToken);
        logger.info("uId={}", uId);
    }
}

以及使用RSA的公私钥Util 

package com.security.demo.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * RSA工具类
 *
 * @author fanglingxiao
 */
public class RSACryptographyUtil {

    private static Logger logger = LoggerFactory.getLogger(RSACryptographyUtil.class);

    /**
     * 将base64编码后的公钥字符串转成PublicKey实例.
     *
     * @author fanglingxiao
     * @date 2019-03-18
     * @param publicKey publicKey
     * @return PublicKey
     */
    public static PublicKey getPublicKey(String publicKey) {
        try {
            byte[] keyBytes = Base64.getDecoder().decode(publicKey.getBytes());
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            return keyFactory.generatePublic(keySpec);
        } catch (Exception e) {
            logger.error("获取PublicKey实例失败:", e);
        }
        return null;
    }

    /**
     * 将base64编码后的私钥字符串转成PrivateKey实例.
     *
     * @author fanglingxiao
     * @date 2019-03-18
     * @param privateKey privateKey
     * @return PublicKey
     */
    public static PrivateKey getPrivateKey(String privateKey) {
        try {
            byte[] keyBytes = Base64.getDecoder().decode(privateKey.getBytes());
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            return keyFactory.generatePrivate(keySpec);
        } catch (Exception e) {
            logger.error("获取PrivateKey实例:", e);
        }
        return null;
    }
}

工具类完成书写 那就需要从网关开始 拦截器、controller、service、dao。。。

跟着我一起学java

创建工程就不说了,用springboot脚手架 勾选web mybatis mysql redis 

数据库就创建简单的user表

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(255) COLLATE utf8mb4_bin NOT NULL,
  `real_name` varchar(255) COLLATE utf8mb4_bin NOT NULL,
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_user_name` (`user_name`) USING BTREE COMMENT '用户名唯一索引'
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

生成好了 书写properties文件

server.port=8082

spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.url=jdbc:mysql://127.0.0.1:3306/fang?serverTimezone=GMT%2B8
spring.datasource.druid.username=root
spring.datasource.druid.password=root

spring.application.name=fang-gtw

token.publicKey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAM4xQ0/nzoyyc4I21OyHYWkJXp3zqjKT5CBNc8IL8j1o1ER6UqdL+b52mQ/wZ30yOOka5Z6rKealHVBZIncRosUCAwEAAQ==
token.privateKey=MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAzjFDT+fOjLJzgjbU7IdhaQlenfOqMpPkIE1zwgvyPWjURHpSp0v5vnaZD/BnfTI46Rrlnqsp5qUdUFkidxGixQIDAQABAkB7LhL4eZN7mmBHOlqZs4cOKPrNgfP7oz51zc+J7XkaFaIt0MYp69OVushedliv0K4ulUFwJP2WAnZ7x1O9TSFBAiEA/2CP7ovIYCycJOQapXKBOaYcHhceaKmiAd8h+ATCBPcCIQDOsf5Y1+r6ilTegdbcrdWFmIzZspxAzNyGr+VbljBzIwIgGC/OEfq9NJalBz1vZKIOqSYZXaQ05hbC2EiI0BOrSIsCIQCUkPei4D+zqa5q6KSJpiEtj4LeDzWklhuEa/XNqNxWRQIhAPD5yAvCL+fzPz0YVCRAJE2idOxVpFb5mxkraVlKgLnQ

#配置.xml文件路径
mybatis.mapper-locations=classpath*:mapper/*.xml
#配置模型路径
mybatis.type-aliases-package=com.security.demo.entity
mybatis.configuration.map-underscore-to-camel-case=true

# Redis数据库索引(默认为0)
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=1s
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=0
#spring.redis.timeout=0

在创建的时候有个问题需要提醒一下:

  第一个就是使用tk的通用mapper 需要把springboot-mybatis-start jar修改成tk的启动jar包

  第二个就是mapperScan的包也是一样 使用tk的包

个人觉得这样挺麻烦的 使用逆向生成对应的service dao model mapper 还是很舒服的,具体如何操作 我其他的博客也有 ,自行百度也是ok的 很简单的配置一下就行了 。或者使用mybatis-plus更是完美!!!

接下来重点来了 就是过滤器的书写

package com.security.demo.auth;

import com.alibaba.druid.support.json.JSONUtils;
import com.google.common.collect.Maps;
import com.security.demo.util.JwtUtil;
import com.security.demo.util.ResultApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * 登陆拦截器
 * ctrl + o 接口重写快捷键
 * @author fanglingxiao
 * @date 2019/3/26
 */
public class LoginInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取请求头的参数
        String token = request.getHeader("Authorization");
        if (!(handler instanceof HandlerMethod)){
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        //当前的方法是否包含Login注解
        if (!method.isAnnotationPresent(Login.class)){
            return true;
        }
        Login login = method.getAnnotation(Login.class);
        if (!login.required()){
            return false;
        }
        //验证token
        if (!checkToken(token)){
            //token有误
            ResultApi res = new ResultApi();
            res.setResCode(-1);
            res.setResMsg("请重新登录");
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().append(JSONUtils.toJSONString(res));
            return false;
        }
        String userName = JwtUtil.getUserName(token);
        String realName = JwtUtil.getRealName(token);

        Map<String,Object> currentUser = Maps.newHashMap();
        currentUser.put("userName",userName);
        currentUser.put("realName",realName);
        request.setAttribute("currentUser",currentUser);
        return true;
    }

    private boolean checkToken(String token){
        // 执行认证
        if (null == token) {
            throw new RuntimeException("无token,请重新登录");
        }
        if (!token.startsWith("Bearer ")) {
            throw new RuntimeException("token格式有误,请重新登录");
        }
        String userName = JwtUtil.getUserName(token);
        if (StringUtils.isEmpty(userName)){
            return false;
        }
        //获取redis中的token信息
        String tokenInRedis = redisTemplate.opsForValue().get(userName);
        if (StringUtils.isEmpty(tokenInRedis)){
            //如果redis挂了可以保证正常执行
            return true;
        }
        String realName = JwtUtil.getRealName(token);
        long expireIn = JwtUtil.getExpireIn(token);

        if (StringUtils.isEmpty(realName)){
            return false;
        }
        String userNameInRedis = JwtUtil.getUserName(tokenInRedis);
        String realNameInRedis = JwtUtil.getRealName(tokenInRedis);
        long expireInRedis = JwtUtil.getExpireIn(tokenInRedis);

        if (StringUtils.isEmpty(userNameInRedis) || StringUtils.isEmpty(realNameInRedis)){
            return false;
        }
        //判断token是否过期
        if (expireIn != expireInRedis){
            return false;
        }
        //判断token过期时间
        return expireIn >= System.currentTimeMillis();
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

过滤器写好之后 该如何让他运行呢?那就需要写个配置类

package com.security.demo.config;

import com.security.demo.auth.LoginInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author fanglingxiao
 * @date 2019/3/26
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor()).addPathPatterns("/**");
    }

    @Bean
    public LoginInterceptor loginInterceptor(){
        return new LoginInterceptor();
    }
}

这样基础操作就完成了,接下来就是网关服务层的书写

package com.security.demo.controller;

import com.security.demo.auth.Login;
import com.security.demo.entity.CurrentUser;
import com.security.demo.entity.User;
import com.security.demo.service.impl.UserServiceImpl;
import com.security.demo.util.ResultApi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;

/**
 * @author fanglingxiao
 * @date 2019/3/26
 */
@RestController
@RequestMapping("**/user")
public class UserController {

    @Autowired
    private UserServiceImpl userService;

    private static final Logger logger = LoggerFactory.getLogger(UserController.class);

    @PostMapping("register")
    public ResultApi<String> register(@Valid @RequestBody CurrentUser model){
        ResultApi<String> res = new ResultApi<>();

        if (StringUtils.isEmpty(model.getPassword()) || StringUtils.isEmpty(model.getUserName())
        || StringUtils.isEmpty(model.getRealName())){
            res.setResCode(-1);
            res.setResMsg("注册有误!请输入有效参数");
        }
        userService.register(model);
        res.setResCode(200);
        res.setResMsg("注册成功!");
        return res;
    }

    @PostMapping("login")
    public ResultApi<String> login(@Valid @RequestBody CurrentUser model){
        logger.info("login params {}",model);

        String token = userService.login(model);

        ResultApi<String> res = new ResultApi<>();
        if (StringUtils.isEmpty(token)){
            res.setResCode(-1);
            res.setResMsg("用户不存在!请重新登陆");
            return res;
        }
        res.setResCode(200);
        res.setResMsg("登陆成功!");
        res.setDate(token);
        return res;
    }

    @PostMapping("queryUserList")
    @Login
    public ResultApi<List<User>> queryUserList(HttpServletRequest request){
        List<User> users = userService.queryUserList();
        ResultApi<List<User>> res = new ResultApi<>();
        res.setDate(users);
        Map<String,Object> currentUser = (Map<String, Object>) request.getAttribute("currentUser");
        String userName = (String) currentUser.get("userName");
        return res;
    }

}

对应的service

package com.security.demo.service.impl;

import com.security.demo.dao.UserDao;
import com.security.demo.entity.CurrentUser;
import com.security.demo.entity.User;
import com.security.demo.service.UserService;
import com.security.demo.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author fanglingxiao
 * @date 2019/3/26
 */
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    public String login(CurrentUser model) {
        String userName = model.getUserName();
        String password = model.getPassword();
        User user = userDao.selectUserByName(userName);
        if (null == user){
            return "";
        }
        if (!StringUtils.isEmpty(password) && password.equals(user.getPassword())){
            //create token
            CurrentUser c = new CurrentUser();
            c.setUserName(user.getUserName());
            c.setRealName(user.getRealName());
            String jwtToken = JwtUtil.createJWT("1", c, "{\"uId\":\"123\"}", System.currentTimeMillis() + 1000*60*60*24);
            redisTemplate.opsForValue().set(user.getUserName(),"Bearer "+jwtToken,1, TimeUnit.DAYS);
            return jwtToken;
        }
        return "";
    }

    public void register(CurrentUser model) {
        String password = model.getPassword();
        String realName = model.getRealName();
        String userName = model.getUserName();
        User user = new User();
        user.setPassword(password);
        user.setUserName(userName);
        user.setRealName(realName);

        userDao.insert(user);
    }

    public List<User> queryUserList() {
        return userDao.selectAll();
    }
}

对应的dao

package com.security.demo.dao;

import com.security.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
import tk.mybatis.mapper.common.BaseMapper;

/**
 * @author fanglingxiao
 * @createTime 2019/3/26
 */
@Mapper
public interface UserDao extends BaseMapper<User> {
    /**
     * 使用用户名查询用户信息
     * @param userName username
     * @return User
     */
    User selectUserByName(String userName);
}

对应的user.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.security.demo.dao.UserDao">
  <resultMap id="BaseResultMap" type="com.security.demo.entity.User">
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="user_name" jdbcType="VARCHAR" property="userName" />
    <result column="real_name" jdbcType="VARCHAR" property="realName" />
    <result column="password" jdbcType="VARCHAR" property="password" />
  </resultMap>

  <select id="selectUserByName" resultType="com.security.demo.entity.User">
    select * from user where user_name=#{userName}
  </select>
</mapper>

这样就操作完成了

继续加油把 长期关注我哦

相关的代码已经在github上 地址:https://github.com/78654Majesty/user

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
为了实现JWT验证token,可以按照以下步骤: 1.在服务端生成JWT,并在其中包含所需的信息(如用户ID、过期时间等)。 2.将JWT发送给客户端,并存储在客户端的本地存储或Cookie中。 3.客户端在每个请求的Authorization标头中将JWT发送回服务器。 4.服务端验证JWT是否有效,并检查其中包含的信息是否与请求匹配。 5.如果JWT有效,则服务端响应请求,否则返回错误响应。 下面是一个Java实现JWT验证token的示例代码: ``` import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.util.Date; public class JWTUtil { //设置token过期时间为30分钟 private static final long EXPIRATION_TIME = 30 * 60 * 1000; //设置加密密钥 private static final String SECRET_KEY = "secret_key"; //生成token public static String generateToken(String subject) { Date now = new Date(); Date expirationDate = new Date(now.getTime() + EXPIRATION_TIME); String jwt = Jwts.builder() .setSubject(subject) .setIssuedAt(now) .setExpiration(expirationDate) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); return jwt; } //解析token public static String parseToken(String jwt) { String subject = null; try { Claims claims = Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(jwt).getBody(); subject = claims.getSubject(); } catch (Exception e) { //token验证失败 } return subject; } } ``` 在这个示例代码中,我们使用了JWT库来生成和解析JWT,同时使用了HS256算法来对JWT进行签名和密钥验证。在generateToken方法中,我们设置了令牌的主题、签发时间和过期时间,并使用密钥对令牌进行签名。在parseToken方法中,我们验证了令牌的签名,并从其主题中提取用户ID。可以根据需要进行修改来适应具体的应用场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值