传统session,cookie的认证方式,第一次请求是会把用户信息存到服务器内存中,并返回一个JSESSIONId给请求端,之后每次请求都需要携带jsessionId来做认证
传统session,cookie的不足:
JSON Web Token Libraries - jwt.io
官方案例:
pom坐标
com.auth0 java-jwt 3.18.3
payload中信息可能被拦截到且可以用base64解码,所以中仅仅放非敏感信息,例如用户名 、appId。而不是用户密码等敏感信息
另外加密过程中的秘钥不能暴露
- public final class JWTCreator {
-
-
- private final Algorithm algorithm;
- // 头json
- private final String headerJson;
- // 负载json
- private final String payloadJson;
-
-
- public String sign(Algorithm algorithm) throws IllegalArgumentException, JWTCreationException {
- if (algorithm == null) {
- throw new IllegalArgumentException("The Algorithm cannot be null.");
- }
- // 给header中赋值
- headerClaims.put(PublicClaims.ALGORITHM, algorithm.getName());
- if (!headerClaims.containsKey(PublicClaims.TYPE)) {
- headerClaims.put(PublicClaims.TYPE, "JWT");
- }
- String signingKeyId = algorithm.getSigningKeyId();
- if (signingKeyId != null) {
- withKeyId(signingKeyId);
- }
- // 先调用创建jwtCreator对象方法,在调用sign生成token
- return new JWTCreator(algorithm, headerClaims, payloadClaims).sign();
- }
-
- // 创建jwtCreator对象方法
- private JWTCreator(Algorithm algorithm, Map
headerClaims, Map payloadClaims) throws JWTCreationException { - this.algorithm = algorithm;
- try {
- // map转String 默认值:"{"typ":"JWT","alg":"HS256"}"
- headerJson = mapper.writeValueAsString(headerClaims);
- payloadJson = mapper.writeValueAsString(new ClaimsHolder(payloadClaims));
- } catch (JsonProcessingException e) {
- throw new JWTCreationException("Some of the Claims couldn't be converted to a valid JSON format.", e);
- }
- }
-
-
- // 签名获取token
- private String sign() throws SignatureGenerationException {
- String header = Base64.getUrlEncoder().withoutPadding().encodeToString(headerJson.getBytes(StandardCharsets.UTF_8));
- String payload = Base64.getUrlEncoder().withoutPadding().encodeToString(payloadJson.getBytes(StandardCharsets.UTF_8));
-
- byte[] signatureBytes = algorithm.sign(header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8));
- String signature = Base64.getUrlEncoder().withoutPadding().encodeToString((signatureBytes));
-
- // 格式 header.payload.signature
- return String.format("%s.%s.%s", header, payload, signature);
- }
-
- }
- public class JWTUtils {
-
-
- private static final String PAYLOAD_ISSUER = "xiaozhen";
- private static final String PAYLOAD_COMPANY_ID = "company_id";
- private static final String PAYLOAD_APP_ID = "app_id";
-
- /**
- * 生成token timestamp代表sign的生成时间
- * token格式:由三部分组成header.payload.signature,中间用点分割
- * 案例:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ5ZGwiLCJjb21wYW55X3VpZCI6IjEyIiwiZXhwIjoxNjY5MDI0ODYxLCJhcHBfaWQiOiJzZGYiLCJpYXQiOjE2NjkwMTc2NjF9.cy3KN5bOMzzYh4pcTR0pbfFeMD7cl5jqeFXYgxz4JeY
- */
- public static TokenDTO generateToken(String appId, String sign, Long timestamp) {
-
- // 查业务数据库 根据appId查询appSecret等秘钥信息 (秘钥不能暴露否则校验就没用了)
- String appSecret = "数据库存储的秘钥";
-
- private static final Algorithm TOKEN_ALGORITHM = Algorithm.HMAC256(appSecret);
-
-
- // 检查签名
- String assertSign = DigestUtils.md5Hex(appId + appSecret);
- if (!sign.equals(assertSign)) {
- throw new XiaoZhenException("code", "签名错误");
- }
-
- // 是否过期
- boolean expired = (System.currentTimeMillis() - timestamp) > 7200000;
- if (expired) {
- throw new XiaoZhenException("code", "签名过期");
- }
-
-
- // 设置Token过期时间
- long issuedAt = System.currentTimeMillis();
- long expiresAt = issuedAt + 7200000;
-
- // 生成Token, with开头的方法均是在设置payload
- String token = JWT.create()
- .withIssuer(PAYLOAD_ISSUER)
- .withClaim(PAYLOAD_COMPANY_ID, "主体id")
- .withClaim(PAYLOAD_APP_ID, "appId")
- .withIssuedAt(new Date(issuedAt))
- .withExpiresAt(new Date(expiresAt))
- .sign(TOKEN_ALGORITHM);
- log.info("generateToken token: {}", token);
- return new TokenDTO()
- .setAccessToken(token)
- .setExpiresAt(expiresAt);
- }
-
-
- /**
- * 返回sign,timestamp生成时间
- */
- public Map
getSign (String appId) { - // 查业务数据库 根据appId查询appSecret等秘钥信息 (秘钥不能暴露否则校验就没用了)
- String appSecret = "数据库存储的秘钥";
- // 根据appSecret,appId 生产sign
- map.put("sign",sign);
- map.put(Constants.TIMESTAMP,String.valueOf(timeStamp));
- return map;
-
- }
-
- }
- @Component
- public class OpenInterceptor extends HandlerInterceptorAdapter {
-
- private static final Logger log = LoggerFactory.getLogger(OpenInterceptor.class);
-
- @Override
- public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
- // 获取token
- String token = req.getHeader("Authorization");
- if (StringUtils.isEmpty(token)) {
- resp.setStatus(HttpStatus.UNAUTHORIZED.value());
- return false;
- }
-
- Boolean result = false;
- try {
- result = bgTokenFacade.parseToken(token);
- // jwt验证器
- JWTVerifier verifier = JWT.require(TOKEN_ALGORITHM)
- .withIssuer(TOKEN_ISSUER)
- .build();
-
- /**
- * 验证token
- * 源码原理:把token按hearder.payload.signature格式拆分,
- * 重新用header和payload生成签名, 和入参中token的signature部分比较,
- * 如果一致说明请求合法,否则为非法请求
- * (这样不论header、payload、signature哪个被篡改都不会验证通过)
- */
- verifier.verify(accessToken);
-
- } catch (Exception e) {
- log.error("解析token异常", e);
- resp.setStatus(HttpStatus.UNAUTHORIZED.value());
- return false;
- }
-
- return result;
- }
-
- }
- @RestController
- @RequestMapping("/v1/open/oauth")
- @Slf4j
- public class BgAuthController {
-
-
- @RequestMapping(value = "/getSign", method = RequestMethod.GET)
- @ApiOperation(value = "获取验签")
- public Map
getSign(@RequestParam(value = "appKey",required = true)String appId) { - return JWTUtils.getSign(appId);
-
- }
-
-
- /**
- * 获取 accessToken
- */
- @ApiOperation(value = "获取 accessToken")
- @PostMapping("/token")
- public TokenDTO token(@RequestBody @Valid TokenReq req) {
- return JWTUtils.generateToken(req.getAppId(),req.getSign, req.getTimestamp());
- }
- }
-
-
格式 header.payload.signature
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ5ZGwiLCJjb21wYW55X3VpZCI6IjEyIiwiZXhwIjoxNjY5MDI0ODYxLCJhcHBfaWQiOiJzZGYiLCJpYXQiOjE2NjkwMTc2NjF9.cy3KN5bOMzzYh4pcTR0pbfFeMD7cl5jqeFXYgxz4JeY
- @Configuration
- public class CustomWebMvcConfigurer implements WebMvcConfigurer {
-
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- WebMvcConfigurer.super.addInterceptors(registry);
- //token验证api拦截器
- registry.addInterceptor(openInterceptor)
- .addPathPatterns("/v1/open/**") // 此contorller业务api均拦截
- .excludePathPatterns("/v1/open/oauth/**"); //生成token的api不需拦截
- }
- }
-
-
- // 自定义实现拦截器,拦截器中校验token
-
- @Component
- public class OpenInterceptor extends HandlerInterceptorAdapter {
-
-
- @Override
- public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
-
- // 获取token
- String token = req.getHeader("Authorization");
- // 解析token
- Boolean result = JWTUtils.verifierToken();
-
- // 可定义ThreadLocal 把请求头中的用户信息保存起来,供本线程全局使用
- // todo private static final ThreadLocal
THREAD_LOCAL = new ThreadLocal<>(); - return result;
- }
- }