Springboot JWT实现接口鉴权

接口权限拦截器

import com.auth0.jwt.JWT;
import com.auth0.jwt.exceptions.JWTDecodeException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.security.Key;
import java.util.List;
import java.util.Map;


/**
 * 接口权限拦截器
 *
 */
@Slf4j
public class AuthenticationInterceptor implements HandlerInterceptor {

    public static final String jwtSecret = "ABCDEFGHIJKLMNOPQRSTUVMXYZABCDEFGHIJKLMNOPQRSTUVMXYZABCDEFGHIJKLMNOPQRSTUVMXYZABCDEFGHIJKLMNOPQRSTUVMXYZ";

    public static final String BASIC_TOKEN_PREFIX = "Basic ";

    public static final String NUC_COOKIE_NAME = "_a";

    public static final String HEADER = "UserInfo";

    public static final String HEAD_TOKEN = "Authorization";

    @Autowired
    AppService appService;

    protected Claims checkToken(HttpServletRequest req) {
        String[] headers = {HEADER, HEAD_TOKEN};
        String tokenString = getTokenString(req, headers, NUC_COOKIE_NAME);
        log.info("tokenString:" + tokenString);
        if (StringUtils.isNotEmpty(tokenString)) {
            return decryptJwtToken(tokenString);
        }
        return null;
    }

    /**
     * 获取token,优先从header获取,再从cookie获取
     *
     * @param req
     * @return
     */
    public static String getTokenString(HttpServletRequest req, String[] headerKeys, String cookieName) {
       
        for (String header : headerKeys) {
            String tokenString = req.getHeader(header);
            if (!StringUtils.isEmpty(tokenString)) {
                return tokenString;
            }
        }
        
        Cookie tokenCookie = getCookieByName(req, cookieName);
        if (tokenCookie != null) {
            return tokenCookie.getValue();
        }
        return null;
    }

    public static Cookie getCookieByName(HttpServletRequest req, String cookieName) {
        if (req.getCookies() != null) {
            Cookie[] var3 = req.getCookies();
            int var4 = var3.length;
            for (int var5 = 0; var5 < var4; ++var5) {
                Cookie cookie = var3[var5];
                if (StringUtils.equals(cookieName, cookie.getName())) {
                    return cookie;
                }
            }
        }
        
        return null;
    }

    public Claims decryptJwtToken(String jwt) {
        try {
            Claims claims = Jwts.parser()
                    .setSigningKey(generateKey())
                    .parseClaimsJws(jwt.substring(BASIC_TOKEN_PREFIX.length())).getBody();
            return claims;
        } catch (Exception e) {
            return null;
        }
    }

    private Key generateKey() {
        Key signingKey = new SecretKeySpec(jwtSecret.getBytes(), SignatureAlgorithm.HS512.getJcaName());
        return signingKey;
    }

    private boolean localCheckToken(HttpServletRequest request, Object object) {
        // 从 http 请求头中取出 token
        String token = request.getHeader("Token");
        log.info("Token串:" + token);
        // 如果不是映射到方法直接通过
        if (!(object instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) object;
        Method method = handlerMethod.getMethod();
        //检查是否有LoginToken注释,有则跳过认证
        if (method.isAnnotationPresent(LoginToken.class)) {
            LoginToken loginToken = method.getAnnotation(LoginToken.class);
            if (loginToken.required()) {
                return true;
            }
        }

        //检查有没有需要用户权限的注解
        if (method.isAnnotationPresent(CheckToken.class)) {
            CheckToken checkToken = method.getAnnotation(CheckToken.class);
            if (checkToken.required()) {
                // 执行认证
                if (token == null) {
                    throw new RuntimeException("无token,请重新获取!");
                }
                // 获取 token 中的 app id
                String appId;
                try {
                    appId = JWT.decode(token).getClaim("appId").asString();
                } catch (JWTDecodeException j) {
                    throw new RuntimeException("token解析异常");
                }
                AppQuery appQuery = new AppQuery();
                appQuery.setAppId(appId);
                PageBean<AppVO> pageBean = appService.queryApp(appQuery);
                if (pageBean.getList() == null || pageBean.getList().size() == 0 || appId == null) {
                    throw new RuntimeException("应用不存在,请先申请!");
                }
                Boolean verify = JwtUtil.isVerify(token, pageBean.getList().get(0));
                if (!verify) {
                    throw new RuntimeException("非法访问!");
                }
                
                return true;
            }
        }
        return true;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
          return localCheckToken(request, handler);
    }

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

    }

    @Test
    public void test() {
        String jwt = "5001062a21696331e6ad87e6f2f7a76db744a1239c86350038f266166728b86a11aa6c70d794c738745d012062912ff93d47";
        decryptJwtToken(jwt);
    }
}

jwt生成token工具类

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

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * jwt生成token工具类
 */
public class JwtUtil {

    /**
     * 用户登录成功后生成Jwt
     * 使用Hs256算法  私匙使用用户密码
     *
     * @param ttlMillis jwt过期时间
     * @param app      登录成功的app对象
     * @return
     */
    public static String createJWT(long ttlMillis, AppVO app) {
        //指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        //生成JWT的时间
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        //创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
        Map<String, Object> claims = new HashMap<String, Object>();
        claims.put("appId", app.getAppId());
        claims.put("appKey", app.getAppKey());
        claims.put("appSecret", app.getAppSecret());

        //生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
        String key = app.getAppSecret();
        //生成签发人
        String subject = app.getAppKey();

        //下面就是在为payload添加各种标准声明和私有声明了
        //这里其实就是new一个JwtBuilder,设置jwt的body
        JwtBuilder builder = Jwts.builder()
                //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
                .setId(UUID.randomUUID().toString())
                //iat: jwt的签发时间
                .setIssuedAt(now)
                //代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。
                .setSubject(subject)
                //设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, key);
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date exp = new Date(expMillis);
            //设置过期时间
            builder.setExpiration(exp);
        }
        return builder.compact();
    }


    /**
     * Token的解密
     * @param token 加密后的token
     * @param app  应用对象
     * @return
     */
    public static Claims parseJWT(String token, AppQuery app) {
        //签名秘钥,和生成的签名的秘钥一模一样
        String key = app.getAppSecret();

        //得到DefaultJwtParser
        Claims claims = Jwts.parser()
                //设置签名的秘钥
                .setSigningKey(key)
                //设置需要解析的jwt
                .parseClaimsJws(token).getBody();
        return claims;
    }


    /**
     * 校验token
     * 在这里可以使用官方的校验,我这里校验的是token中携带的密码于数据库一致的话就校验通过
     * @param token
     * @param app
     * @return
     */
    public static Boolean isVerify(String token, AppVO app) {
        //签名秘钥,和生成的签名的秘钥一模一样
        String key = app.getAppSecret();

        //得到DefaultJwtParser
        Claims claims = Jwts.parser()
                //设置签名的秘钥
                .setSigningKey(key)
                //设置需要解析的jwt
                .parseClaimsJws(token).getBody();

        if (claims.get("appSecret").equals(app.getAppSecret())) {
            return true;
        }

        return false;
    }

}
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
## 查询应用Bean
/**
 * 应用
 */
@Data
@ApiModel
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown=true)
public class AppQuery {
    @ApiModelProperty(value = "应用ID")
    private String appId;
    @ApiModelProperty(value = "应用key")
    private String appKey;
    @ApiModelProperty(value = "应用秘钥")
    private String appSecret;
    @ApiModelProperty(value = "系统标识")
    private String systemKey;
    @ApiModelProperty(value = "系统名称")
    private String systemName;
}

应用返回的bean

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * 应用
 * 
 */
@Data
@ApiModel
public class AppVO {
    @ApiModelProperty(value = "应用Id")
    private String appId;
    @ApiModelProperty(value = "应用账号")
    private String appKey;
    @ApiModelProperty(value = "应用秘钥")
    private String appSecret;
    @ApiModelProperty(value = "系统标识")
    private String systemKey;
    @ApiModelProperty(value = "系统名称")
    private String systemName;
    @ApiModelProperty(value = "创建人")
    private String createdUser;
    @ApiModelProperty(value = "更新人")
    private String updatedUser;
    @ApiModelProperty(value = "创建日期")
    private String createdDate;
    @ApiModelProperty(value = "更新日期")
    private String updatedDate;
    @ApiModelProperty(value = "是否有效")
    private String isValid;
}

接口权限注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 接口权限
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckToken {
    boolean required() default true;
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 接口权限注解
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginToken {
    boolean required() default true;
}

Controller

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.jra.Put;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.Model;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Api(tags = "提供业务系统接口")
@RestController
@RequestMapping({"/api/v1/"})
public class ExternalController {
    private Logger logger = LoggerFactory.getLogger(ExternalController.class);

    @ApiOperation("获取token")
    @PostMapping(value = "getToken")
    public WebResponse token(@RequestBody AppQuery app) {
        AppVO appVO = appService.queryAppByAppKey(app);
        if (appVO == null) {
            return WebResponse.buildFail("appKey或appSecret校验不通过!");
        }
        String token = JwtUtil.createJWT(900000, appVO);
        return WebResponse.buildSuccess(token, "获取token成功!");
    }

 
    @ApiOperation("查询用户")
    @PostMapping(value = "users", consumes = {"application/json"})
    @CheckToken
    public WebResponse getUserList(
            @RequestBody @Validated WebRequest<UserRequestDTO> body) {
        return new WebResponse(thirdService.getUserList(body.getBody()));
    }
}

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot是一个基于Spring框架的快速开发的工具,它简化了Spring应用程序的配置和部署。 Spring Security是一个用于身份验证和授的强大框架,它提供了一种灵活的方式来保护应用程序的安全性。 JWT(JSON Web Token)是一种用于安全传输信息的开放标准,它使用JSON对象进行安全传输。它是一种无状态的方式,服务器不需要存储用户的状态信息。 在使用Spring Boot和Spring Security进行登录时,可以借助JWT来进行身份验证。 首先,需要配置Spring Security来处理用户的登录请求和验证。可以使用Spring Security提供的身份验证过滤器来进行用户名和密码的验证。验证成功后,可以生成一个JWT,并返回给客户端。 在客户端接收到JWT后,将其存储在本地(通常是在前端的Cookie或LocalStorage中)。在进行后续的请求时,需要将JWT作为请求的头部信息中的Authorization字段发送给服务器。 服务器在接收到请求时,会先验证JWT的合法性,验证通过后可以根据JWT中的信息来进行后续的操作。 可以在服务器端配置一个自定义的JWT过滤器,用于验证JWT的合法性,并根据JWT中的信息来进行操作。可以根据需要从JWT中解析出用户的角色和限信息,并根据这些信息来进行接口的访问控制。 通过以上的配置,可以实现基于Spring Boot、Spring Security和JWT的登录机制。这样可以保证系统的安全性,同时也能提高开发效率和灵活性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值