Spring Boot实战之Filter实现使用JWT进行接口认证

时间:2020-12-23 18:28:35

Spring Boot实战之Filter实现使用JWT进行接口认证


jwt(json web token)

用户发送按照约定,向服务端发送 Header、Payload 和 Signature,并包含认证信息(密码),验证通过后服务端返回一个token,之后用户使用该token作为登录凭证,适合于移动端和api


jwt使用流程

Spring Boot实战之Filter实现使用JWT进行接口认证


本文示例接上面几篇文章中的代码进行编写,请阅读本文的同时可以参考前面几篇文章


1、添加依赖库jjwt,本文中构造jwt及解析jwt都使用了jjwt库

               

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>



2、添加登录获取token时,所需要的认证信息类LoginPara.java

package com.xiaofangtech.sunt.jwt;

public class LoginPara {
private String clientId;
private String userName;
private String password;
private String captchaCode;
private String captchaValue;

public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getCaptchaCode() {
return captchaCode;
}
public void setCaptchaCode(String captchaCode) {
this.captchaCode = captchaCode;
}
public String getCaptchaValue() {
return captchaValue;
}
public void setCaptchaValue(String captchaValue) {
this.captchaValue = captchaValue;
}
}


3、添加构造jwt及解析jwt的帮助类JwtHelper.java

package com.xiaofangtech.sunt.jwt;

import java.security.Key;
import java.util.Date;

import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

public class JwtHelper {
public static Claims parseJWT(String jsonWebToken, String base64Security){
try
{
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(base64Security))
.parseClaimsJws(jsonWebToken).getBody();
return claims;
}
catch(Exception ex)
{
return null;
}
}

public static String createJWT(String name, String userId, String role,
String audience, String issuer, long TTLMillis, String base64Security)
{
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);

//生成签名密钥
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(base64Security);
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

//添加构成JWT的参数
JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")
.claim("role", role)
.claim("unique_name", name)
.claim("userid", userId)
.setIssuer(issuer)
.setAudience(audience)
.signWith(signatureAlgorithm, signingKey);
//添加Token过期时间
if (TTLMillis >= 0) {
long expMillis = nowMillis + TTLMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp).setNotBefore(now);
}

//生成JWT
return builder.compact();
}
}


4、添加token返回结果类AccessToken.java

package com.xiaofangtech.sunt.jwt;

public class AccessToken {
private String access_token;
private String token_type;
private long expires_in;
public String getAccess_token() {
return access_token;
}
public void setAccess_token(String access_token) {
this.access_token = access_token;
}
public String getToken_type() {
return token_type;
}
public void setToken_type(String token_type) {
this.token_type = token_type;
}
public long getExpires_in() {
return expires_in;
}
public void setExpires_in(long expires_in) {
this.expires_in = expires_in;
}
}


5、添加获取token的接口,通过传入用户认证信息(用户名、密码)进行认证获取

package com.xiaofangtech.sunt.jwt;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.xiaofangtech.sunt.bean.UserInfo;
import com.xiaofangtech.sunt.repository.UserInfoRepository;
import com.xiaofangtech.sunt.utils.MyUtils;
import com.xiaofangtech.sunt.utils.ResultMsg;
import com.xiaofangtech.sunt.utils.ResultStatusCode;

@RestController
public class JsonWebToken {
@Autowired
private UserInfoRepository userRepositoy;

@Autowired
private Audience audienceEntity;

@RequestMapping("oauth/token")
public Object getAccessToken(@RequestBody LoginPara loginPara)
{
ResultMsg resultMsg;
try
{
if(loginPara.getClientId() == null
|| (loginPara.getClientId().compareTo(audienceEntity.getClientId()) != 0))
{
resultMsg = new ResultMsg(ResultStatusCode.INVALID_CLIENTID.getErrcode(),
ResultStatusCode.INVALID_CLIENTID.getErrmsg(), null);
return resultMsg;
}

//验证码校验在后面章节添加


//验证用户名密码
UserInfo user = userRepositoy.findUserInfoByName(loginPara.getUserName());
if (user == null)
{
resultMsg = new ResultMsg(ResultStatusCode.INVALID_PASSWORD.getErrcode(),
ResultStatusCode.INVALID_PASSWORD.getErrmsg(), null);
return resultMsg;
}
else
{
String md5Password = MyUtils.getMD5(loginPara.getPassword()+user.getSalt());

if (md5Password.compareTo(user.getPassword()) != 0)
{
resultMsg = new ResultMsg(ResultStatusCode.INVALID_PASSWORD.getErrcode(),
ResultStatusCode.INVALID_PASSWORD.getErrmsg(), null);
return resultMsg;
}
}

//拼装accessToken
String accessToken = JwtHelper.createJWT(loginPara.getUserName(), String.valueOf(user.getName()),
user.getRole(), audienceEntity.getClientId(), audienceEntity.getName(),
audienceEntity.getExpiresSecond() * 1000, audienceEntity.getBase64Secret());

//返回accessToken
AccessToken accessTokenEntity = new AccessToken();
accessTokenEntity.setAccess_token(accessToken);
accessTokenEntity.setExpires_in(audienceEntity.getExpiresSecond());
accessTokenEntity.setToken_type("bearer");
resultMsg = new ResultMsg(ResultStatusCode.OK.getErrcode(),
ResultStatusCode.OK.getErrmsg(), accessTokenEntity);
return resultMsg;

}
catch(Exception ex)
{
resultMsg = new ResultMsg(ResultStatusCode.SYSTEM_ERR.getErrcode(),
ResultStatusCode.SYSTEM_ERR.getErrmsg(), null);
return resultMsg;
}
}
}


6、添加使用jwt认证的filter

package com.xiaofangtech.sunt.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.xiaofangtech.sunt.jwt.Audience;
import com.xiaofangtech.sunt.jwt.JwtHelper;
import com.xiaofangtech.sunt.utils.ResultMsg;
import com.xiaofangtech.sunt.utils.ResultStatusCode;

public class HTTPBearerAuthorizeAttribute implements Filter{
@Autowired
private Audience audienceEntity;

@Override
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this,
filterConfig.getServletContext());

}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// TODO Auto-generated method stub

ResultMsg resultMsg;
HttpServletRequest httpRequest = (HttpServletRequest)request;
String auth = httpRequest.getHeader("Authorization");
if ((auth != null) && (auth.length() > 7))
{
String HeadStr = auth.substring(0, 6).toLowerCase();
if (HeadStr.compareTo("bearer") == 0)
{

auth = auth.substring(7, auth.length());
if (JwtHelper.parseJWT(auth, audienceEntity.getBase64Secret()) != null)
{
chain.doFilter(request, response);
return;
}
}
}

HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setCharacterEncoding("UTF-8");
httpResponse.setContentType("application/json; charset=utf-8");
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

ObjectMapper mapper = new ObjectMapper();

resultMsg = new ResultMsg(ResultStatusCode.INVALID_TOKEN.getErrcode(), ResultStatusCode.INVALID_TOKEN.getErrmsg(), null);
httpResponse.getWriter().write(mapper.writeValueAsString(resultMsg));
return;
}

@Override
public void destroy() {
// TODO Auto-generated method stub

}
}


7、在入口处注册filter

package com.xiaofangtech.sunt;

import java.util.ArrayList;
import java.util.List;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

import com.xiaofangtech.sunt.filter.HTTPBasicAuthorizeAttribute;
import com.xiaofangtech.sunt.filter.HTTPBearerAuthorizeAttribute;
import com.xiaofangtech.sunt.jwt.Audience;

@SpringBootApplication
@EnableConfigurationProperties(Audience.class)
public class SpringRestApplication {

public static void main(String[] args) {
SpringApplication.run(SpringRestApplication.class, args);
}

@Bean
public FilterRegistrationBean basicFilterRegistrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
HTTPBasicAuthorizeAttribute httpBasicFilter = new HTTPBasicAuthorizeAttribute();
registrationBean.setFilter(httpBasicFilter);
List<String> urlPatterns = new ArrayList<String>();
urlPatterns.add("/user/getuser");
registrationBean.setUrlPatterns(urlPatterns);
return registrationBean;
}

@Bean
public FilterRegistrationBean jwtFilterRegistrationBean(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
HTTPBearerAuthorizeAttribute httpBearerFilter = new HTTPBearerAuthorizeAttribute();
registrationBean.setFilter(httpBearerFilter);
List<String> urlPatterns = new ArrayList<String>();
urlPatterns.add("/user/getusers");
registrationBean.setUrlPatterns(urlPatterns);
return registrationBean;
}
}

8、添加获取md5的方法类MyUtils
package com.xiaofangtech.sunt.utils;

import java.security.MessageDigest;

public class MyUtils {
public static String getMD5(String inStr) {
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (Exception e) {

e.printStackTrace();
return "";
}
char[] charArray = inStr.toCharArray();
byte[] byteArray = new byte[charArray.length];

for (int i = 0; i < charArray.length; i++)
byteArray[i] = (byte) charArray[i];

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();
}
}

9、在返回信息类中补充添加错误码

        INVALID_CLIENTID(30003, "Invalid clientid"),
INVALID_PASSWORD(30004, "User name or password is incorrect"),
INVALID_CAPTCHA(30005, "Invalid captcha or captcha overdue"),
INVALID_TOKEN(30006, "Invalid token");



10、代码中涉及的Audience类,在上一篇文章中定义,本文不再重复说明


11、代码整体结构

Spring Boot实战之Filter实现使用JWT进行接口认证


12、测试

1) 获取token,传入用户认证信息

Spring Boot实战之Filter实现使用JWT进行接口认证


    认证通过返回token信息

Spring Boot实战之Filter实现使用JWT进行接口认证



2) 使用上面获取的token进行接口调用

     未使用token,获取token错误,或者token过期时

Spring Boot实战之Filter实现使用JWT进行接口认证


    使用正确的token时

Spring Boot实战之Filter实现使用JWT进行接口认证