【使用JWT+SpringSecurity实现的登录、认证、授权】

JWT

一、生成令牌,解析令牌(对称盐)

package com.fzy.demo;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.Date;

public class Demo01 {
//    public static void main(String[] args) {
//
//        //失效时间
//        Calendar instance = Calendar.getInstance();
//        instance.add(Calendar.MINUTE,100);
//
//        Date expireTime = instance.getTime();
//
//        //颁发jwt令牌
//        String jwtToken = JWT.create()
                .withHeader()
//                .withClaim("id", "1")
//                .withClaim("username", "jack")
//                .withClaim("age", 18)
//                .withExpiresAt(expireTime)
//                .sign(Algorithm.HMAC256("abc"));
//
//        System.out.println(jwtToken);
//    }

    public static void main(String[] args) {
        //解析令牌
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("abc")).build();

        DecodedJWT verify = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEiLCJleHAiOjE2NTkxNzgxNTcsImFnZSI6MTgsInVzZXJuYW1lIjoiamFjayJ9.whcL4pRCpfy-SrykkdskbjpDbnZSiFzkrHzLHyETQIg");

        //获取payload(载荷)信息
        String id = verify.getClaim("id").asString();
        String username = verify.getClaim("username").asString();
        Integer age = verify.getClaim("age").asInt();
        System.out.println("id = " + id);

    }
}

二、基于jwt的非对称加密 登录&认证流程:

  1. 客户端提交登录请求到服务端,服务端校验登录请求,如果用户合法,那么颁发令牌,用户不合法,跳到登录页面。
  2. 返回jwt令牌,存入到localstorage,
  3. 访问受限资源,把令牌取出,放入到请求头中,
  4. 受限资源拿到令牌,解析令牌,如果合法,那么返回结果,不合法,那就拒绝访问

0.前端发送登录请求

<script>
  	  //配置请求地址
      axios.defaults.baseURL = "http://localhost:8080"
	  //配置拦截器信息
      axios.interceptors.response.use(res=>{
      	return res.data;
    })

    //创建Vue实例,得到 ViewModel
    var vm = new Vue({
      el: '#app',
      data:{
        user:{
          account:"",
          password:"",
        }
      },
      methods: {
        login(){
          axios.post("/user/login",this.user).then(res=>{
            console.log(res);

            //将令牌存储localstorage
            localStorage.setItem("token",res.data)
          })
        }
      },
      created(){}
    });
</script>

1.后端接收登录请求

UserController.java

package com.fzy.controller;
import com.fzy.entity.SysUser;
import com.fzy.interceptor.AccessInterceptor;
import com.fzy.service.UserService;
import com.fzy.vo.ResultVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("user")
@CrossOrigin		//配置跨域
public class UserController {

    @Autowired
    private AccessInterceptor accessInterceptor;

    @Autowired
    private UserService userService;


// 一、用户登录(传入用户信息)
    @RequestMapping("login")
    public ResultVo<String> login(@RequestBody SysUser sysUser){

        return userService.login(sysUser);
    }

//四、用户访问认证(携带token)
    @RequestMapping("one")
    public SysUser one(String userId){
        //(略,改为拦截器实现)获取jwt令牌,解析jwt令牌

//七、获取用户信息
        SysUser sysUser = accessInterceptor.getLocal().get();
        return sysUser;
    }
}

2.验证登录请求,并生成token返回前端

UserServiceImpl.java

package com.fzy.service;
import com.fzy.entity.SysUser;
import com.fzy.mapper.UserMapper;
import com.fzy.util.JwtUtils;
import com.fzy.util.RsaUtils;
import com.fzy.vo.ResultVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.ResourceUtils;
import java.security.PrivateKey;

@Service
public class UserServiceImpl implements UserService{

    @Autowired(required = false)
    private UserMapper userMapper;

    @Override
    public ResultVo<String> login(SysUser sysUser) {

// 二、验证登录信息(进行密码比较)
        if(sysUser == null){
            return new ResultVo<String>(false,"非法参数");
        }

        //获取用户信息
        SysUser userDB = userMapper.findUserByUsername(sysUser.getAccount());
        if(userDB == null){
            return new ResultVo<>(false,"用户名或者密码错误");
        }

        //比较密码
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        if(!encoder.matches(sysUser.getPassword(),userDB.getPassword())){
            return new ResultVo<>(false,"用户名或者密码错误");
        }


        try {
// 三、验证通过,颁发令牌(把私钥存入token返回前端)
            //加载私钥
            PrivateKey privateKey = RsaUtils.getPrivateKey(ResourceUtils.getFile("classpath:pri").getPath());

            userDB.setPassword("");
            String jwtToken = JwtUtils.generateTokenExpireInMinutes(userDB, privateKey, 30);
            return new ResultVo<>(true,"success",jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return new ResultVo<>(false,"用户名或者密码错误");
        }
    }
}

3.前端登录携带token 进行验证

AccessInterceptor.java (在拦截器配置 验证流程)

package com.fzy.interceptor;
import cn.hutool.json.JSONUtil;
import com.fzy.entity.SysUser;
import com.fzy.exception.JWTException;
import com.fzy.util.JwtUtils;
import com.fzy.util.RsaUtils;
import com.fzy.vo.ResultVo;
import org.springframework.stereotype.Component;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.security.PublicKey;

@Component
public class AccessInterceptor implements HandlerInterceptor {

    private ThreadLocal<SysUser> local = new ThreadLocal<>();

    public ThreadLocal<SysUser> getLocal() {
        return local;
    }

    public void setLocal(ThreadLocal<SysUser> local) {
        this.local = local;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 五、全局校验jwt令牌(从请求头中获取token)
        String token = request.getHeader("token");
        if (StringUtils.isEmpty(token)) {

            response.setCharacterEncoding("utf-8");
            response.setContentType("application/json");
            ResultVo<Object> resultVo = new ResultVo<>(false,"令牌不能为空");
            response.getWriter().write(JSONUtil.toJsonStr(resultVo));
            return false;
        }
// 六,校验token合法,加载公钥,生成用户信息(把用户信息放入threadLocal中,或者放到上下文中)
        //合法
        //加载公钥
        PublicKey publicKey = RsaUtils.getPublicKey(ResourceUtils.getFile("classpath:pub").getPath());
        try {
            SysUser infoFromToken = (SysUser) JwtUtils.getInfoFromToken(token, publicKey, SysUser.class);

            //threadLocal
            local.set(infoFromToken);
            return true;

        } catch (JWTException e) {
            e.printStackTrace();

            response.setCharacterEncoding("utf-8");
            response.setContentType("application/json");
            ResultVo<Object> resultVo = new ResultVo<>(false,"invalid token");
            response.getWriter().write(JSONUtil.toJsonStr(resultVo));
            return false;
        }
    }

    @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 {

// 八、清空 threadLocal
        local.remove();
    }
}

4.配置登录放过页

AccessInterceptorConfig.java

package com.fzy.interceptor;

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.WebMvcConfigurer;

@Configuration
public class AccessInterceptorConfig implements WebMvcConfigurer {

    @Autowired
    private AccessInterceptor accessInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(accessInterceptor)
                .excludePathPatterns("/user/login");
    }
}

5.(略)工具类,以及依赖

JwtUtils.java (生成token以及校验token相关方法)
package com.fzy.util;
import cn.hutool.json.JSONUtil;
import com.fzy.exception.JWTException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.*;
/**
 * 生成token以及校验token相关方法
 */
public class JwtUtils {

    private static final String JWT_PAYLOAD_USER_KEY = "user";

    /**
     * 私钥加密token
     * @param userInfo   载荷中的数据
     * @param privateKey 私钥
     * @param expire     过期时间,单位分钟
     * @return JWT
     */
    public static String generateTokenExpireInMinutes(Object userInfo, PrivateKey privateKey, int expire) {
        //计算过期时间
        Calendar c = Calendar.getInstance();
        c.add(Calendar.MINUTE,expire);

        return Jwts.builder()
                .claim(JWT_PAYLOAD_USER_KEY, JSONUtil.toJsonStr(userInfo))
                .setId(new String(Base64.getEncoder().encode(UUID.randomUUID().toString().getBytes())))
                .setExpiration(c.getTime())
                .signWith(privateKey, SignatureAlgorithm.RS256)
                .compact();
    }

    /**
     * 私钥加密token
     * @param userInfo   载荷中的数据
     * @param privateKey 私钥
     * @param expire     过期时间,单位秒
     * @return JWT
     */
    public static String generateTokenExpireInSeconds(Object userInfo, PrivateKey privateKey, int expire) {
        //计算过期时间
        Calendar c = Calendar.getInstance();
        c.add(Calendar.SECOND,expire);

        return Jwts.builder()  //{"user":"{id:12,name:jack}"}
                .claim(JWT_PAYLOAD_USER_KEY, JSONUtil.toJsonStr(userInfo))
                .setId(new String(Base64.getEncoder().encode(UUID.randomUUID().toString().getBytes())))
                .setExpiration(c.getTime())
                .signWith(privateKey, SignatureAlgorithm.RS256)
                .compact();
    }

    /**
     * 获取token中的用户信息
     *
     * @param token     用户请求中的令牌
     * @param publicKey 公钥
     * @return 用户信息
     */
    public static  Object getInfoFromToken(String token, PublicKey publicKey, Class userType) throws JWTException {
        //解析token
       try {
           Jws<Claims> claimsJws = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);

           Claims body = claimsJws.getBody();
           String userInfoJson = body.get(JWT_PAYLOAD_USER_KEY).toString();
           return JSONUtil.toBean(userInfoJson, userType);
       }catch (Exception e){
           throw new JWTException("令牌不合法");
       }
    }
}
RsaUtils.java (获取,读取公钥,私钥方法)
package com.fzy.util;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class RsaUtils {
    private static final int DEFAULT_KEY_SIZE = 2048;

    /**
     * @param filename 公钥保存路径,相对于classpath
     * @return 公钥对象
     * @throws Exception
     */
    public static PublicKey getPublicKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPublicKey(bytes);
    }

    /**
     * 从文件中读取密钥
     *
     * @param filename 私钥保存路径,相对于classpath
     * @return 私钥对象
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPrivateKey(bytes);
    }

    /**
     * 获取公钥
     * @param bytes 公钥的字节形式
     * @return
     * @throws Exception
     */
    public static PublicKey getPublicKey(byte[] bytes) throws Exception {
        bytes = Base64.getDecoder().decode(bytes);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePublic(spec);
    }

    /**
     * 获取密钥
     * @param bytes 私钥的字节形式
     * @return
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(byte[] bytes) throws NoSuchAlgorithmException,
            InvalidKeySpecException {
        bytes = Base64.getDecoder().decode(bytes);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePrivate(spec);
    }

    /**
     * 根据密文,生存rsa公钥和私钥,并写入指定文件
     *
     * @param publicKeyFilename  公钥文件路径
     * @param privateKeyFilename 私钥文件路径
     * @param secret             生成密钥的密文
     */
    public static void generateKey(String publicKeyFilename, String privateKeyFilename, String
            secret, int keySize) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        SecureRandom secureRandom = new SecureRandom(secret.getBytes());
        keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        // 获取公钥并写出
        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        publicKeyBytes = Base64.getEncoder().encode(publicKeyBytes);
        writeFile(publicKeyFilename, publicKeyBytes);
        // 获取私钥并写出
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        privateKeyBytes = Base64.getEncoder().encode(privateKeyBytes);
        writeFile(privateKeyFilename, privateKeyBytes);
    }

    private static byte[] readFile(String fileName) throws Exception {
        return Files.readAllBytes(new File(fileName).toPath());
    }

    private static void writeFile(String destPath, byte[] bytes) throws IOException {
        File dest = new File(destPath);
        if (!dest.exists()) {
            dest.createNewFile();
        }
        Files.write(dest.toPath(), bytes);
    }
}
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>

    <groupId>com.fzy</groupId>
    <artifactId>login</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <mysql.version>5.1.47</mysql.version>
    </properties>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.7.RELEASE</version>
    </parent>

    <dependencies>
<!--        jwt,对称盐-->
<!--        <dependency>-->
<!--            <groupId>com.auth0</groupId>-->
<!--            <artifactId>java-jwt</artifactId>-->
<!--            <version>3.4.0</version>-->
<!--        </dependency>-->

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.10.7</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.10.7</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.10.7</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.16</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

</project>
pri (私钥)

MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCqvzbDiomsr8R6wxUIlbcE6uZOAmYt6iX5Qo9EJxuNfg1nttLcshOGxztKrUsY/uovkCj+snktJnxtIHwPV6USSMO/ZsH2FM+a/yYlxs0vJS+OClZzTMgw70w8e+TmsQRBAwFGBjaigmtevFneT32MVj08tLVjCZmDW+BM8vCwgtB8lXG52Gb4C3iYC7ow/6n6Nl17OuYF//8HLGyCytyqeb/LPrSZednzopoS5QtNW2nGyHoeIB/CiVBiXFPqvQI1PNL/tDan++jFrtPnLxS6d6+0WpZ4Xv/c/TG4AdvVozCkd5vsTBpj06h9YMSEhaSqNpAT7LlppLiKQig8AEvJAgMBAAECggEBAIuAPhX0khbsY/KIgB7LcwQphpAllaXdr7i+kJ519STc38bkr9JwC1QKYn7Ypop9eofxmAy/dBFXaEEgUTuXt94AZS1znvSWfCd+XoRusCTclMXP3GHHUafpWrfv4Uw5Q+h5tAip+uk69M4pxKLE/yYxL0bxvWB0719LKS61LcCHSm/0tcSy9Pl6INti+55Pt0ervK24mfu5wLbdid6sA7BjGFaIzp6fuZiJnaSIVhG7POcwsYO1413VTuqw6LfVTgKR8QGR4RVMdvBiQcPWKFVtsRRWR7G9k4xPxiq7ruhPLYtkR17mL+0tUXBSzxq4cZsHBcwK0ZWzN3Shqr/uiyECgYEA1McXlNff1e/BYpPCynvn83Hg/5XCUcht8pqDAAdK08mtEF6UdUqCuFA3cDOpvs+eKkJ5IvhR8FSxUS0UbChrmknu7OMNxPstfMwfTZBiKJh6O6h9l3bdQhfTUg7yXRrGraZbeX7OVm2BClO6eSch7Gkyq3nFeNuulZSJGGMaFRcCgYEAzW5t4rphPu3TT9xpQu5MDI6rGZ12GCPtBX+adwTcs1UezV2W6C4eEVfSyeBGJifnBb/WM+Y9EsNJ4ST82Dn4VaMDmjWTHZubJeEwu9H2XUGEP+dDGfilZvlvF6US5C9qO/O8WJKuTxh9/6ujKSTSUJ79YwALpdXO6CI9QMxY8h8CgYBT77oIuGQPZAYYmguEjv6AVZMZn/1I/9UHmBZHY5kMFQnVZEoSPvN0PAiylV7H6+UL7K8WsXi5Yt+UE0F0LTnNYo8DGcqrwx49ldRfZ66hLw1BDYWFw5ki4n01aLoJKm/nvrFlKzXAeHAoH8F2244RUapwZJgWxiqHzVGLskRuvwKBgGpqgvfJLqjnj+g1uD1YrgJvQUjinZNBwP1xLXXsCdvIA03E5nBdu2umf+XdjNklIHKE/pQh948ppLLBc00bMn5CFJKkoBHdUpBbx9/zktiaIMTtqn/gouXl3lt0QoX73d8ykmWbjjog6NwxcDLXBS+IPKA1HNOKH0V2vx46/PRnAoGBAMX36M8caj/nijh07a5Pv6l9mG2eXEgtlKWy3UQLVJuPZR/6dgrMipv6PgmzrNawP+4QPfcnxWltggoXRudJZDm7iEwW7y0NUtm1ndfDU0AQf1FpwoRsZpp6cVEjP5SbtJSsy5+YZqRdL3FKMZovnJLSV5cs5ovZc8A6SAD56vYr

pub(公钥)

MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqr82w4qJrK/EesMVCJW3BOrmTgJmLeol+UKPRCcbjX4NZ7bS3LIThsc7Sq1LGP7qL5Ao/rJ5LSZ8bSB8D1elEkjDv2bB9hTPmv8mJcbNLyUvjgpWc0zIMO9MPHvk5rEEQQMBRgY2ooJrXrxZ3k99jFY9PLS1YwmZg1vgTPLwsILQfJVxudhm+At4mAu6MP+p+jZdezrmBf//Byxsgsrcqnm/yz60mXnZ86KaEuULTVtpxsh6HiAfwolQYlxT6r0CNTzS/7Q2p/voxa7T5y8UunevtFqWeF7/3P0xuAHb1aMwpHeb7EwaY9OofWDEhIWkqjaQE+y5aaS4ikIoPABLyQIDAQAB

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值