SpringBoot集成Jwt實現用戶登錄

前言

最近正在搭建一個SpringBoot+Vue的一套後臺管理系統的模板,對於用戶登錄的功能使用了JWT來實現的,自己在學習SpringCloud微服務時使用的就是JWT,通過Cookie來傳遞token,實現用戶的登錄狀態。

以下就是自己在SpringBoot中集成JWT過程。

1、加入maven依賴

		<!-- jwt依賴 -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.6.0</version>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>

2、實現配置類

配置類中的公鑰私鑰可以自己網上找

#jwt配置
config:
  jwt:
    # 加密密鑰
    secret: iwqjhda8232bjgh432[cicada-smile]
    # token有效時長
    expire: 3600
    # header 名稱
    cookieName: token
    pubKeyPath: D:\googleDownload\RSA簽名驗籤工具windows_V1.4\RSA密鑰\rsa.pub # 公鑰地址
    priKeyPath: D:\googleDownload\RSA簽名驗籤工具windows_V1.4\RSA密鑰\rsa.pri # 私鑰地址

3、實現工具類

  • JwtProperties 工具類

@Data
@Component
public class JwtProperties {

    @Value("${config.jwt.secret}")
    private String secret; // 密鑰

    @Value("${config.jwt.pubKeyPath}")
    private String pubKeyPath;// 公鑰

    @Value("${config.jwt.priKeyPath}")
    private String priKeyPath;// 私鑰

    @Value("${config.jwt.cookieName}")
    private String cookieName;  //cookie的名稱

    @Value("${config.jwt.expire}")
    private int expire;// token過期時間

    private PublicKey publicKey; // 公鑰

    private PrivateKey privateKey; // 私鑰

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

    /**
     * @PostContruct:在構造方法執行之後執行該方法
     */
    @PostConstruct
    public void init() {
        try {
            File pubKey = new File(pubKeyPath);
            File priKey = new File(priKeyPath);
            if (!pubKey.exists() || !priKey.exists()) {
                // 生成公鑰和私鑰
                RsaUtils.generateKey(pubKeyPath, priKeyPath, secret);
            }
            // 獲取公鑰和私鑰
            this.publicKey = RsaUtils.getPublicKey(pubKeyPath);
            this.privateKey = RsaUtils.getPrivateKey(priKeyPath);
        } catch (Exception e) {
            logger.error("初始化公鑰和私鑰失敗!", e);
            throw new RuntimeException();
        }
    }

}
  • RsaUtils工具類,用戶實現公鑰私鑰的解密

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * @author chenmo
 * @date 2018/9/30
 */
public class RsaUtils {

    /**
     * 從文件中讀取公鑰
     *
     * @param fileName
     * @return
     * @throws Exception
     */
    public static PublicKey getPublicKey(String fileName) throws Exception {
        byte[] bytes = readFile(fileName);
        return getPublicKey(bytes);
    }

    /**
     * 從文件中讀取私鑰
     * @param fileName
     * @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 {
        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 Exception {
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePrivate(spec);
    }

    /**
     * 讀取文件
     *
     * @param fileName
     * @return
     * @throws IOException
     */
    public static byte[] readFile(String fileName) throws IOException {
        return Files.readAllBytes(new File(fileName).toPath());

    }

    /**
     * 根據密文,生存rsa公鑰和私鑰,並寫入指定文件
     *
     * @param publicKeyFilename  公鑰文件路徑
     * @param privateKeyFilename 私鑰文件路徑
     * @param secret             生成密鑰的密文
     * @throws IOException
     * @throws NoSuchAlgorithmException
     */
    public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        SecureRandom secureRandom = new SecureRandom(secret.getBytes());
        keyPairGenerator.initialize(1024, secureRandom);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        // 獲取公鑰並寫出
        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        writeFile(publicKeyFilename, publicKeyBytes);
        // 獲取私鑰並寫出
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        writeFile(privateKeyFilename, privateKeyBytes);
    }

    /**
     * 向目標路徑寫入文件
     *
     * @param destPath
     * @param bytes
     * @throws IOException
     */
    public static void writeFile(String destPath, byte[] bytes) throws IOException {
        File dest = new File(destPath);
        if (!dest.exists()) {
            dest.createNewFile();
        }
        Files.write(dest.toPath(), bytes);
    }
}
  • CodecUtils 加密工具類,用戶加密密碼

/**
 * 密碼中生成鹽的方法
 */
public class CodecUtils {

    /**
     * 將密碼加上鹽後再經過md5加密
     *
     * @param data
     * @param salt
     * @return
     */
    public static String md5Hex(String data, String salt) {
        if (StringUtils.isBlank(salt)) {
            salt = data.hashCode() + "";
        }
        return DigestUtils.md5Hex(salt + DigestUtils.md5Hex(data));
    }

    public static String shaHex(String data, String salt) {
        if (StringUtils.isBlank(salt)) {
            salt = data.hashCode() + "";
        }
        return DigestUtils.sha512Hex(salt + DigestUtils.sha512Hex(data));
    }

    /**
     * 生成鹽
     *
     * @return
     */
    public static String generateSalt() {
        return StringUtils.replace(UUID.randomUUID().toString(), "-", "");
    }
}
  • JwtUtils工具類,用於解析JWT中的token

import com.modules.application.system.entity.UserInfo;
import com.modules.application.system.paramVo.UserInfoVO;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;

import java.security.PrivateKey;
import java.security.PublicKey;

public class JwtUtils {
    /**
     * 私鑰加密token
     *
     * @param userInfo      載荷中的數據
     * @param privateKey    私鑰
     * @param expireMinutes 過期時間,單位秒
     * @return
     * @throws Exception
     */
    public static String generateToken(UserInfoVO userInfo, PrivateKey privateKey, int expireMinutes) throws Exception {
        return Jwts.builder()
                .claim(JwtConstans.JWT_KEY_ID, userInfo.getId())
                .claim(JwtConstans.JWT_KEY_USER_NAME, userInfo.getUsername())
                .setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())
                .signWith(SignatureAlgorithm.RS256, privateKey)
                .compact();
    }

    /**
     * 私鑰加密token
     *
     * @param userInfo      載荷中的數據
     * @param privateKey    私鑰字節數組
     * @param expireMinutes 過期時間,單位秒
     * @return
     * @throws Exception
     */
    public static String generateToken(UserInfo userInfo, byte[] privateKey, int expireMinutes) throws Exception {
        return Jwts.builder()
                .claim(JwtConstans.JWT_KEY_ID, userInfo.getId())
                .claim(JwtConstans.JWT_KEY_USER_NAME, userInfo.getUsername())
                .setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())
                .signWith(SignatureAlgorithm.RS256, RsaUtils.getPrivateKey(privateKey))
                .compact();
    }

    /**
     * 公鑰解析token
     *
     * @param token     用戶請求中的token
     * @param publicKey 公鑰
     * @return
     * @throws Exception
     */
    private static Jws<Claims> parserToken(String token, PublicKey publicKey) {
        return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
    }

    /**
     * 公鑰解析token
     *
     * @param token     用戶請求中的token
     * @param publicKey 公鑰字節數組
     * @return
     * @throws Exception
     */
    private static Jws<Claims> parserToken(String token, byte[] publicKey) throws Exception {
        return Jwts.parser().setSigningKey(RsaUtils.getPublicKey(publicKey))
                .parseClaimsJws(token);
    }

    /**
     * 獲取token中的用戶信息
     *
     * @param token     用戶請求中的令牌
     * @param publicKey 公鑰
     * @return 用戶信息
     * @throws Exception
     */
    public static UserInfoVO getInfoFromToken(String token, PublicKey publicKey) throws Exception {
        Jws<Claims> claimsJws = parserToken(token, publicKey);
        Claims body = claimsJws.getBody();
        return new UserInfoVO(
                ObjectUtils.toLong(body.get(JwtConstans.JWT_KEY_ID)),
                ObjectUtils.toString(body.get(JwtConstans.JWT_KEY_USER_NAME))
        );
    }

    /**
     * 獲取token中的用戶信息
     *
     * @param token     用戶請求中的令牌
     * @param publicKey 公鑰
     * @return 用戶信息
     * @throws Exception
     */
    public static UserInfoVO getInfoFromToken(String token, byte[] publicKey) throws Exception {
        Jws<Claims> claimsJws = parserToken(token, publicKey);
        Claims body = claimsJws.getBody();
        return new UserInfoVO(
                ObjectUtils.toLong(body.get(JwtConstans.JWT_KEY_ID)),
                ObjectUtils.toString(body.get(JwtConstans.JWT_KEY_USER_NAME))
        );
    }
}
  • JWT配置信息

public abstract class JwtConstans {
    public static final String JWT_KEY_ID = "id";
    public static final String JWT_KEY_USER_NAME = "username";
}
/**
 * 從jwt解析得到的數據是Object類型,轉換爲具體類型可能出現空指針,
 * 這個工具類進行了一些轉換
 */
public class ObjectUtils {

    public static String toString(Object obj) {
        if (obj == null) {
            return null;
        }
        return obj.toString();
    }

    public static Long toLong(Object obj) {
        if (obj == null) {
            return 0L;
        }
        if (obj instanceof Double || obj instanceof Float) {
            return Long.valueOf(StringUtils.substringBefore(obj.toString(), "."));
        }
        if (obj instanceof Number) {
            return Long.valueOf(obj.toString());
        }
        if (obj instanceof String) {
            return Long.valueOf(obj.toString());
        } else {
            return 0L;
        }
    }

    public static Integer toInt(Object obj) {
        return toLong(obj).intValue();
    }
}
  • JWT配置類

獲取配置文件中的數據

@ConfigurationProperties(prefix = "config.jwt")
@Component
@Data
public class JwtConfig {
    private JwtProperties jwtProperties;
    /*
     * 根據身份ID標識,生成Token
     */
    public String getToken (String identityId){
        Date nowDate = new Date();
        //過期時間
        Date expireDate = new Date(nowDate.getTime() + expire * 1000);
        return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setSubject(identityId)
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /*
     * 獲取 Token 中註冊信息
     */
    public Claims getTokenClaim (String token) {
        try {
            return Jwts.parser().setSigningKey(RsaUtils.getPublicKey(pubKeyPath)).parseClaimsJws(token).getBody();
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }
    /*
     * Token 是否過期驗證
     */
    public boolean isTokenExpired (Date expirationTime) {
        return expirationTime.before(new Date());
    }
    private String secret;
    private long expire;
    private String pubKeyPath;
    private String header;
}

4、實現用戶登錄

controller

import com.modules.application.common.oauth.Result;
import com.modules.application.common.oauth.ResultStatusCode;
import com.modules.application.common.properties.JwtProperties;
import com.modules.application.common.utils.CookieUtils;
import com.modules.application.common.utils.StringUtils;
import com.modules.application.common.web.BaseController;
import com.modules.application.system.paramVo.UserInfoVO;
import com.modules.application.system.service.UserInfoService;
import com.modules.application.system.utils.JwtUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@RestController
@RequestMapping("/user")
@Api(value = "LoginController", tags = {"LoginController"}, description = "註冊/登錄")
public class LoginController extends BaseController {

    @Resource
    private UserInfoService userInfoService;
    @Resource
    private JwtProperties jwtProperties;


    /**
     * 根據用戶名及密碼判斷用戶是否登錄
     * @param username
     * @param password
     * @return
     */
    @PostMapping("/login")
    @ApiOperation(value = "通過用戶名和密碼登錄")
    @ApiResponses({
            @ApiResponse(code = 200, message = "返回數據", response = Result.class)
    })
    public Result login( HttpServletRequest request, HttpServletResponse response,@RequestParam("username")String username, @RequestParam("password")String password){
        String token = this.userInfoService.login(username, password);
        if (StringUtils.isBlank(token)){
            return new Result(ResultStatusCode.NOT_EXIST_USER_OR_ERROR_PWD);
        }
        //使用工具類,設置cookie並返回給瀏覽器,需要cookie名稱,cookie的值,過期時間,配置的是分,默認使用的秒,注意乘以60
        CookieUtils.setCookie(request, response, jwtProperties.getCookieName(), token, jwtProperties.getExpire() * 60);
        return new Result(ResultStatusCode.OK,"登錄成功!");
    }


    /**
     *解析瀏覽器中的cookie,獲取當前登錄用戶
     * @param request
     * @param response
     * @return
     */
    @GetMapping("/getUserInfoByToken")
    @ApiOperation(value = "通過token獲取用戶信息")
    @ApiResponses({
            @ApiResponse(code = 200, message = "返回數據", response = UserInfoVO.class)
    })
    public Result getUserInfoByToken(HttpServletRequest request, HttpServletResponse response){
        try {
            //通過request獲取token
            String token= CookieUtils.getCookie(request, jwtProperties.getCookieName());
            //通過jwt獲取用戶
            UserInfoVO userInfo = JwtUtils.getInfoFromToken(token,jwtProperties.getPublicKey());
            if (userInfo==null){
                return new Result(ResultStatusCode.NOT_EXIST_USER_OR_ERROR_PWD);
            }
            //刷新jwt中的有效時間
            token = JwtUtils.generateToken(userInfo,jwtProperties.getPrivateKey(),jwtProperties.getExpire());
            //刷新cookie中的有效時間
            CookieUtils.setCookie(request,response,jwtProperties.getCookieName(),token,jwtProperties.getExpire()*60);
            return new Result(ResultStatusCode.OK,userInfo);
        }catch (Exception e){
            e.printStackTrace();
        }
        return new Result(ResultStatusCode.NOT_EXIST_USER_OR_ERROR_PWD);
    }


    
}

service

import com.modules.application.common.properties.JwtProperties;
import com.modules.application.common.service.CrudService;
import com.modules.application.system.dao.UserInfoMapper;
import com.modules.application.system.entity.UserInfo;
import com.modules.application.system.paramVo.UserInfoVO;
import com.modules.application.system.utils.CodecUtils;
import com.modules.application.system.utils.JwtUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

/**
 * 用戶信息Service
 * @author wcf
 * @version 2018-04-17
 */
@Service
@Transactional(readOnly = true)
public class UserInfoService extends CrudService<UserInfoMapper, UserInfo> {
	@Resource
	private JwtProperties jwtProperties;
	@Resource
	private UserInfoMapper userInfoMapper;


	/**
	 * 通過用戶名查詢用戶
	 * @param username
	 * @param password
	 * @return
	 */
	public UserInfo findUserByUserName(String username, String password) {
		//根據用戶名查詢到用戶
		UserInfo user = this.userInfoMapper.findUserByUsername(username);
		//是否存在判斷
		if (user == null) {
			return null;
		}
		//使用用戶內的鹽加請求的密碼進行加密處理,再與數據庫內的密碼進行判斷
		String md5Hex = CodecUtils.md5Hex(password, user.getSalt());
		if (!org.apache.commons.lang3.StringUtils.equals(user.getPassword(), md5Hex)) {
			return null;
		}
		return user;
	}

	/**
     * 用戶登錄
	 * @param username
     * @param password
     * @return
     */
	public String login(String username, String password) {
		//根據用戶名查詢到用戶
		UserInfo user = findUserByUserName(username,password);
		//是否存在判斷
		if (user == null) {
			return null;
		}
		try {
			//生成token邏輯
			UserInfoVO userInfo = new UserInfoVO();
			userInfo.setId(user.getId());
			userInfo.setUsername(user.getUsername());
			String token = JwtUtils.generateToken(userInfo, jwtProperties.getPrivateKey(), jwtProperties.getExpire());
			return token;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
}

dao層接口

import com.modules.application.common.persistence.CrudDao;
import com.modules.application.system.entity.UserInfo;

/**
 * 用戶信息DAO接口
 * @author wcf
 * @version 2018-04-17
 */
public interface UserInfoMapper extends CrudDao<UserInfo> {

    public UserInfo findUserByUsername(String username);

}

xml文件

	<select id="findUserByUsername" resultType="com.modules.application.system.entity.UserInfo">
		SELECT
		<include refid="userInfoColumns"/>
		FROM `user` a
		<include refid="userInfoJoins"/>
		WHERE a.name=#{username}
	</select>

5、返回結果類


/**
 * api接口返回的數據模型
 * @author chunqiu
 *
 */
public class Result {
	private int code;		//返回的代碼,0表示成功,其他表示失敗
    private String msg;		//成功或失敗時返回的錯誤信息
    private Object data;	//成功時返回的數據信息

	public Result(ResultStatusCode resultStatusCode, Object data){
		this.code = resultStatusCode.getCode();
		this.msg = resultStatusCode.getMsg();
		this.data = data;
	}

    public Result(int code, String msg, Object data){
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public Result(int code, String msg){
    	this(code, msg, null);
	}
	public Result(ResultStatusCode resultStatusCode){
    	this(resultStatusCode, null);
	}

	public int getCode() {
		return code;
	}
	public void setCode(int code) {
		this.code = code;
	}
	public String getMsg() {
		return msg;
	}
	public void setMsg(String msg) {
		this.msg = msg;
	}
	public Object getData() {
		return data;
	}
	public void setData(Object data) {
		this.data = data;
	}


}

狀態類


/**
 * @param
 * @author chen
 * @description 錯誤代碼
 * @return
 */
public enum ResultStatusCode {
    OK(0, "OK"),
    BAD_REQUEST(400, "參數解析失敗"),
    INVALID_TOKEN(401, "無效的授權碼"),
    INVALID_CLIENTID(402, "無效的密鑰"),
    METHOD_NOT_ALLOWED(405, "不支持當前請求方法"),
    SYSTEM_ERR(500, "服務器運行異常"),
    NOT_EXIST_USER_OR_ERROR_PWD(10000, "該用戶不存在或密碼錯誤");


    private int code;
    private String msg;

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    private ResultStatusCode(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

6、實現攔截器

import com.modules.application.common.config.JwtConfig;
import com.modules.application.common.properties.JwtProperties;
import com.modules.application.common.utils.CookieUtils;
import com.modules.application.common.utils.StringUtils;
import io.jsonwebtoken.Claims;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 請求api服務器時,對請求進行攔截判斷,有效則可以訪問接口,否則返回錯誤
 *
 * @author Win7
 */
@Component
public class InterceptorJWT extends HandlerInterceptorAdapter {
    @Resource
    private JwtConfig jwtConfig;
    @Resource
    private JwtProperties jwtProperties;


    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        // 地址過濾
        String uri = request.getRequestURI();
        if (uri.contains("/login")) {
            return true;
        }
        // Token 驗證
        String token = CookieUtils.getCookie(request, jwtProperties.getCookieName());
        if (StringUtils.isEmpty(token)) {
            token = request.getParameter(jwtConfig.getHeader());
        }
        if (StringUtils.isEmpty(token)) {
            throw new Exception(jwtConfig.getHeader() + "不能爲空");
        }
        Claims claims = jwtConfig.getTokenClaim(token);
        if (claims == null || jwtConfig.isTokenExpired(claims.getExpiration())) {
            throw new Exception(jwtConfig.getHeader() + "失效,請重新登錄");
        }
        //設置 identityId 用戶身份ID
        request.setAttribute("identityId", claims.getSubject());
        return true;
    }
}

通過攔截器攔截請求,驗證用戶是否處於登錄狀態,成功則放行,否則進行攔截,讓用戶重新登陸。

總結

這套流程中主要通過JWT機制將特定的信息通過RSA加密方式生成token傳遞給前端的Cookie,用戶每次請求時都會攜帶該Cookie,然後後端將獲得的Cookie進行解密,獲得token,然後獲得對應的用戶信息,可以執行後續操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章