2021-05-10 JWT集成

简介

JSON Web token简称JWT, 是用于对应用程序上的用户进行身份验证的标记。也就是说, 使用 JWTS 的应用程序不再需要保存有关其用户的 cookie 或其他session数据。此特性便于可伸缩性, 同时保证应用程序的安全。

在身份验证过程中, 当用户使用其凭据成功登录时, 将返回 JSON Web token, 并且必须在本地保存 (通常在本地存储中)。每当用户要访问受保护的路由或资源 (端点) 时, 用户代理(user agent)必须连同请求一起发送 JWT, 通常在授权标头中使用Bearer schema。后端服务器接收到带有 JWT 的请求时, 首先要做的是验证token。

JWT的格式

JWT就是一个字符串,经过加密处理与校验处理的字符串,形式为:A.B.C
A由JWT头部信息header加密得到
B由JWT用到的身份验证信息json数据加密得到
C由A和B加密得到,是校验部分

背景

项目需要APP端与服务端Token验证。由于服务端包含pc端服务,且只有APP端服务需要token验证,增加过滤器和拦截器配置,拦截APP端服务。

集成

1. Application.yml 配置属性jwt属性(secret 加密盐、expire过期时间等)
 
 
   io.jsonwebtoken
   jjwt
   0.9.1

jwt:
  secret: A0B1C2D3E4F5G6H*********************** # 加密yan
  expire: 300000 # tocken 过期时间,单位秒
  authorised-urls: /app/** # 需要认证的url,多个URL使用英文逗号,分割
2. 启动时配置类JwtConfig获取jwt属性并注册JwtUtil Bean
@Configuration("myJwtConfig")
public class JwtConfig {
    // 加密盐
    @Value("${jwt.secret}")
    private String secret;

    // 过期时间
    @Value("${jwt.expire}")
    private long expire;

    // 授权路径
    @Value("${jwt.authorised-urls}")
    private String[] authorisedUrls;

    @Bean
    public JwtUtil jwtUtilBean() {
        return new JwtUtil(secret, expire);
    }

    @Bean
    public FilterRegistrationBean basicFilterRegistrationBean() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        JwtFilter filter = new JwtFilter(jwtUtilBean(), authorisedUrls);
        registrationBean.setFilter(filter);
        List urlPatterns = new ArrayList<>();
        urlPatterns.add("/app/*"); // 拦截路径
        registrationBean.setUrlPatterns(urlPatterns);
        return registrationBean;
    }
}
public class JwtUtil {
    private Long EXPIRATION_TIME;
    private String SECRET;

    public JwtUtil(String secret, long expire) {
        this.EXPIRATION_TIME = expire;
        this.SECRET = secret;
    }

    /**
     * 为指定用户生成token
     *
     * @param claims 用户信息
     * @return token
     */
    public JSONObject generateToken(Map claims) {
        JSONObject json = new JSONObject();
        json.put(AppConstants.HEADER_TOKEN, AppConstants.TOKEN_PREFIX + " " + generateJwt(claims));
        return json;
    }

    /**
     * 为指定用户生成jwt
     * @param claims 用户信息
     * @return jwt
     */
    public String generateJwt(Map claims) {
        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(new Date())
                // 计算过期时间
                .setExpiration(new Date(System.currentTimeMillis() + this.EXPIRATION_TIME * 1000))
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .compact();
    }

    /**
     * 从token中获取claim
     */
    public Claims getClaimsFromToken(String token) {
        System.out.println("token is:" + token);
        if (token == null) {
            return null;
        }
        return Jwts.parser()
                .setSigningKey(SECRET)
                .parseClaimsJws(token.replace(AppConstants.TOKEN_PREFIX, ""))
                .getBody();
    }

    /**
     * 获取token的过期时间
     *
     * @param token token
     * @return 过期时间
     */
    public Date getExpirationDateFromToken(String token) {
        return getClaimsFromToken(token)
                .getExpiration();
    }

    /**
     * 判断token是否过期
     *
     * @param token token
     * @return 已过期返回true,未过期返回false
     */
    private Boolean isTokenExpired(String token) {
        Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    /**
     * 判断token是否非法
     *
     * @param token token
     * @return 未过期返回true,否则返回false
     */
    public Boolean validateToken(String token) {
        return !isTokenExpired(token);
    }

//  public static void main(String[] args) throws Exception {
//      // 1. 初始化
//      JwtUtil jwtOperator = new JwtUtil("aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrsssttt", 1209600L);
//
//      // 2.设置用户信息
//      HashMap objectObjectHashMap = new HashMap();
//      objectObjectHashMap.put("id", "1");
//
//      // 测试1: 生成token
//      String token = jwtOperator.generateToken(objectObjectHashMap);
//      // 会生成类似该字符串的内容: eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEiLCJpYXQiOjE1NjU1ODk4MTcsImV4cCI6MTU2Njc5OTQxN30.27_QgdtTg4SUgxidW6ALHFsZPgMtjCQ4ZYTRmZroKCQ
//      System.out.println(token);
//
//      // 将我改成上面生成的token!!!
//      String someToken = "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEiLCJpYXQiOjE1NjU1ODk4MTcsImV4cCI6MTU2Njc5OTQxN30.27_QgdtTg4SUgxidW6ALHFsZPgMtjCQ4ZYTRmZroKCQ";
//      // 测试2: 如果能token合法且未过期,返回true
//      Boolean validateToken = jwtOperator.validateToken(someToken);
//      System.out.println(validateToken);
//
//      // 测试3: 获取用户信息
//      Claims claims = jwtOperator.getClaimsFromToken(someToken);
//      System.out.println(claims);
//
//      // 将我改成你生成的token的第一段(以.为边界)
//      String encodedHeader = "eyJhbGciOiJIUzI1NiJ9";
//      // 测试4: 解密Header
//      byte[] header = Base64.decodeBase64(encodedHeader.getBytes());
//      System.out.println(new String(header));
//
//      // 将我改成你生成的token的第二段(以.为边界)
//      String encodedPayload = "eyJpZCI6IjEiLCJpYXQiOjE1NjU1ODk1NDEsImV4cCI6MTU2Njc5OTE0MX0";
//      // 测试5: 解密Payload
//      byte[] payload = Base64.decodeBase64(encodedPayload.getBytes());
//      System.out.println(new String(payload));
//
//      // 测试6: 这是一个被篡改的token,因此会报异常,说明JWT是安全的
//      jwtOperator.validateToken("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEiLCJpYXQiOjE1NjU1ODk3MzIsImV4cCI6MTU2Njc5OTMzMn0.nDv25ex7XuTlmXgNzGX46LqMZItVFyNHQpmL9UQf-aUx");
//  }
}
3. AppLoginController调用JwtUtil的方法生成Token(可以携带一些必要参数,如登录人信息,单位信息等)
Map claims = new HashMap<>();
// 部门信息
Organization orgInfo = RightProxy.getPartyDataService().getEmployeeOrg(CommonTool.getPersonPartyId(resultUser.getLoginId()), CommonUtil.getDateTime());
claims.put("loginId", resultUser.getLoginId());
claims.put("loginCode", resultUser.getLoginCode());
claims.put("organizationId", orgInfo.getOrganizationId());
claims.put("personName", resultUser.getPersonName());
JSONObject json = new JSONObject();
json.put(AppConstants.HEADER_TOKEN, AppConstants.TOKEN_PREFIX + " " + jwtUtil.generateJwt(claims));
4. 通过InterceptorConfig配置拦截目录和自定义拦截器JwtInterceptor
@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor())
                .addPathPatterns("/app/**");
    }
    @Bean
    public JwtInterceptor jwtInterceptor() {
        return new JwtInterceptor();
    }
}

/**
 * 不需要验证Token的方法、类
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {

    boolean required() default true;
}

public class JwtInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtUtil jwtUtil;

//  @Autowired
//  UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
        // 如果不是映射到方法直接通过
        if (!(object instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) object;
        Method method = handlerMethod.getMethod();
        //先检查方法是否有passtoken注释,有则跳过认证
        if (method.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if (passToken.required()) {
                return true;
            }
        }
        Class beanType = handlerMethod.getBeanType();
        //检查类是否有passtoken注释,有则跳过认证
        if (beanType.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = beanType.getAnnotation(PassToken.class);
            if (passToken.required()) {
                return true;
            }
        }
        String token = httpServletRequest.getHeader(AppConstants.HEADER_TOKEN);
        try {
            if (StringUtils.isBlank(token)) {
                LogUtil.getAppLoger().info("无token,请重新登录");
                printContent(httpServletResponse, ResultEnum.TOKEN_NULL);
                return false;
            } else {
                // 解析数据并放入httpServletRequest,以便业务使用相关信息
                Claims claims = jwtUtil.getClaimsFromToken(token);
                UserView userView = new UserView();
                // 组织信息
                Organization organization = new Organization();
                organization.setOrganizationId(Long.valueOf(claims.get("organizationId").toString()));
                // 人员信息
                UserLogin userLogin = new UserLogin();
                userLogin.setLoginId(Long.valueOf(claims.get("loginId").toString()));
                userLogin.setLoginCode((String) claims.get("loginCode"));
                userLogin.setPersonName((String) claims.get("personName"));
                userView.setLoginCode((String) claims.get("loginCode"));
                userView.setOrganization(organization);
                userView.setUserLogin(userLogin);
                userView.setToken(token);
                httpServletRequest.setAttribute("userView", userView);
                // 获取登录信息
            }
        } catch (ExpiredJwtException e) {
            LogUtil.getAppLoger().info("token已过期;" + e.getMessage());
            printContent(httpServletResponse, ResultEnum.TOKEN_EXPIRED);
            return false;
        } catch (Exception e) {
            // 错误信息: httpServletResponse. sendError(HttpServletResponse.SC_UNAUTHORIZED, "token非法");
            LogUtil.getAppLoger().info("token非法;" + e.getMessage());
            printContent(httpServletResponse, ResultEnum.TOKEN_ILLEGAL);
            return false;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest,
                           HttpServletResponse httpServletResponse,
                           Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest,
                                HttpServletResponse httpServletResponse,
                                Object o, Exception e) throws Exception {
    }

    /**
     * 将错误信息返回
     * @param response 响应
     * @param resultEnum 返回错误信息枚举
     */
    private static void printContent(HttpServletResponse response, ResultEnum resultEnum) {
        PrintWriter pw = null;
        try {
            response.reset();
            response.setContentType("application/json");
            response.setHeader("Cache-Control", "no-store");
            response.setCharacterEncoding("UTF-8");
            ResultVO resultVO = ResultUtil.error(resultEnum);
            String content = JSON.toJSONString(resultVO);
            pw = response.getWriter();
            pw.write(content);
            pw.flush();
        } catch (Exception e) {
            LogUtil.getAppLoger().info("拦截器输出流异常;" + e.getMessage());
        } finally {
            if (pw != null) {
                pw.close();
            }
        }
    }
}
5. 通过自定义拦截器JwtInterceptor 拦截指定请求路径,解析Token中的数据并放入HttpServletRequest
// 解析数据并放入httpServletRequest,以便业务使用相关信息
Claims claims = jwtUtil.getClaimsFromToken(token);
UserView userView = new UserView();
// 组织信息
Organization organization = new Organization();
organization.setOrganizationId(Long.valueOf(claims.get("organizationId").toString()));
// 人员信息
UserLogin userLogin = new UserLogin();
userLogin.setLoginId(Long.valueOf(claims.get("loginId").toString()));
userLogin.setLoginCode((String) claims.get("loginCode"));
userLogin.setPersonName((String) claims.get("personName"));
userView.setLoginCode((String) claims.get("loginCode"));
userView.setOrganization(organization);
userView.setUserLogin(userLogin);
userView.setToken(token);
httpServletRequest.setAttribute("userView", userView);
6. 各业务获取HttpServletRequest中的userView来获取Token中的登录人信息
@ResponseBody
@RequestMapping("/getuserinfo")
public ResultVO getUserInfo(HttpServletRequest request) {
    UserView userView = (UserView) request.getAttribute("userView");
    return ResultUtil.success(userView);
}

你可能感兴趣的:(2021-05-10 JWT集成)