史上最简单的JWT教程,看不懂来捶我!

史上最简单的Elasticsearch教程已经开始更新!

(帮到到您请点点关注,文章持续更新中!)

(个人Git主页:https://github.com/Mydreamandreality)

JWT鉴权机制:我放狠话出去,看不懂尽管来捶我(反正你也锤不到
如果有不懂的可以留言一起交流学习,关注一哈,之后会更新Elasticsearch,cloud Java
的干货,一起学习,博客是在印象笔记的记录,copy出来样式有一些乱,见谅

1. JWT是什么?

	1. JWT是一种基于JSON的令牌安全验证(在某些特定的场合可以替代Session或者Cookie)
2. JWT是什么样子的?

	1. JWT是由三个部分组成的,分别是:

		1. 头部信息(header)

			1. 作用:指定该JWT使用的签名
		2. 消息体(playload)

			1. 作用:JWT的请求数据
		3. 签名( signatrue)

			1. 签名是三个部分组合成的

				1. Base64编码后的头部信息,消息体,且加密后拼接而成,[,]分割
				2. 私有的Key计算,并且Base64编码
3. JWT该怎么用?

	1. JWT经常被用来保护服务器的资源,客户端一般通过HTTP/header的Authorzation把JWT发送给服务端
	2. 服务端使用自己保存的Key进行计算,验证签名JWT是否合法

生产项目中JWT的使用
代码:

/**
* Created by 張燿峰
* JWT常量
* @author 孤
* @date 2019/1/2
* @Varsion 1.0
*/
public interface JwtConstants {

    String AUTH_HEADER = "Authorization";

    String SECRET = "defaultSecret";

    String AUTH_PATH = "/attackApi/auth";

    Long EXPIRATION = 604800L;
}

//AUTH_HEADER    是HTTPHeader请求的参数
//SECRET         是具体的加密算法
//AUTH_PATH      是提供给客户端获取JWT参数的接口/需要提供正确的用户名以及密码
//EXPIRATION     是计算JWT过期时间需要用到的          
/**
* Created by 張燿峰
* RestApi鉴权拦截器
* @author 孤
* @date 2019/1/2
* @Varsion 1.0
*/
public class RestApiInteceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        //此处应是静态资源请求
        if (handler instanceof org.springframework.web.servlet.resource.ResourceHttpRequestHandler) {
            return true;
    }
        return check(request,response);
    }

    /**
     * toKen检查
     * @return
     */
    private boolean check(HttpServletRequest request, HttpServletResponse response) {
        //此处是指获取Token的接口
        if (request.getServletPath().equals(JwtConstants.AUTH_PATH)) {
            return true;
        }
        final String requestHeader = request.getHeader(JwtConstants.AUTH_HEADER);
        String authToken = null;
        if (requestHeader != null && requestHeader.startsWith("attackFind ")) {
            authToken = requestHeader.substring(11);
            try {
                //包含了验证jwt是否正确
                boolean isExpired = AuthUtil.isTokenExpired(authToken);
                if (isExpired) {
                    RenderUtil.renderJson(response, new ErrorResponseData(BizExceptionEnum.TOKEN_EXPIRED.getCode(), BizExceptionEnum.TOKEN_EXPIRED.getMessage()));
                    return false;
                }
            } catch (JwtException e) {
                //有异常就是token解析失败
                RenderUtil.renderJson(response, new ErrorResponseData(BizExceptionEnum.TOKEN_ERROR.getCode(), BizExceptionEnum.TOKEN_ERROR.getMessage()));
                return false;
            }
        } else {
            RenderUtil.renderJson(response, new ErrorResponseData(BizExceptionEnum.TOKEN_ERROR.getCode(), BizExceptionEnum.TOKEN_ERROR.getMessage()));
            return false;
        }
        return true;
    }
}

//preHandle:
//首先需要判断请求是否是静态资源
//如果是则不验证该请求,不是则执行check方法
//check中先验证该请求是否为获取JWT参数
//获取请求头中的Authorization参数信息
//调用我们接口的第三方约定好JWT之前追加attackFind ,如果没有这个信息那么JWT就验证失败
//isTokenExpired是检测JWT是否过期 true过期
/**
* Created by 張燿峰
* 

Jwt工具类

* jwt的claim里一般包含以下几种数据: * 1. iss -- token的发行者 * 2. sub -- 该JWT所面向的用户 * 3. aud -- 接收该JWT的一方 * 4. exp -- token的失效时间 * 5. nbf -- 在此时间段之前,不会被处理 * 6. iat -- jwt发布时间 * 7. jti -- jwt唯一标识,防止重复使用 * * @author 孤 * @date 2019/1/2 * @Varsion 1.0 */ public class AuthUtil { /** * 从Token中获取用户名称 * * @param token * @return 用户名称 */ public static String getUserNameFromToken(String token) { return getClaimsFromToken(token).getSubject(); } /** * 从Token中获取JWT发布时间 * * @param token * @return 发布时间 */ public static Date getIsseudAtDateFromToken(String token) { return getClaimsFromToken(token).getIssuedAt(); } /** * 从Token中获取JWT过期时间 * * @param token * @return 过期时间 */ public static Date getExPirationDateFromToken(String token) { return getClaimsFromToken(token).getExpiration(); } /** * 从Token中获取JWT接收者 * * @param token * @return 接收者 */ public static String getAyduebceFromToken(String token) { return getClaimsFromToken(token).getAudience(); } /** * 从Token中获取私有的JWT claim * * @param token * @param key * @return claim */ public static String getPrivateClaimsFromToken(String token, String key) { return getClaimsFromToken(token).get(key).toString(); } public static Claims getClaimsFromToken(String token) { return Jwts.parser() .setSigningKey(JwtConstants.SECRET) .parseClaimsJws(token) .getBody(); } /** * 检查Token是否正确 * * @param token */ public static void parseToken(String token) { Jwts.parser().setSigningKey(JwtConstants.SECRET).parseClaimsJws(token).getBody(); } /** * 检查Token是否过期 * * @param token * @return false未过期 true过期 */ public static boolean isTokenExpired(String token) { try { final Date expiration = getExPirationDateFromToken(token); return expiration.before(new Date()); } catch (ExpiredJwtException expiredJwtException) { return true; } } /** * 生成Token * * @param userId * @return Token */ public static String generateToken(String userId) { Map claims = new HashMap<>(); return doGeneratorToken(claims, userId); } private static String doGeneratorToken(Map claims, String subject) { final Date startDate = new Date(); final Date endDate = new Date(startDate.getTime() + JwtConstants.EXPIRATION * 1000); return Jwts.builder() .setClaims(claims) .setSubject(subject) .setIssuedAt(startDate) .setExpiration(endDate) .signWith(SignatureAlgorithm.HS512,JwtConstants.SECRET) .compact(); } /** * 获取混淆MD5签名用的随机字符串 */ public static String getRandomKey() { return ToolUtil.getRandomString(6); } //这个类中的代码都是JWT的工具 /** * 自动渲染当前用户信息登录属性 的过滤器 */ public class AttributeSetInteceptor extends HandlerInterceptorAdapter { @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { //没有视图的直接跳过过滤器 if (modelAndView == null || modelAndView.getViewName() == null) { return; } //视图结尾不是html的直接跳过 if (!modelAndView.getViewName().endsWith("html")) { return; } ShiroUser user = ShiroKit.getUser(); if (user == null) { throw new AuthenticationException("当前没有登录账号!"); } else { modelAndView.addObject("menus", user.getId()); modelAndView.addObject("name", user.getName()); } } } //业务代码无需关注
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private AttackProperties attackProperties;

    /**
     * 增加swagger的支持
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        if (attackProperties.getSwaggerOpen()) {
            registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
            registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
        }
    }

    /**
     * 增加对rest api鉴权的spring mvc拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        List NONE_PERMISSION_RES = CollectionUtil.newLinkedList
                ("/static/**", "/attackApi/**", "/login", "/global/sessionError", "/kaptcha","/cornemail","/init/start","/websocket/new_message","/global/error","/api/**");
        registry.addInterceptor(new RestApiInteceptor()).addPathPatterns("/attackApi/**");
        registry.addInterceptor(new AttributeSetInteceptor()).excludePathPatterns(NONE_PERMISSION_RES).addPathPatterns("/**");
    }

    /**
     * 默认错误页面,返回json
     */
    @Bean("error")
    public AttackErrorView error() {
        return new AttackErrorView();
    }
    ....................其他代码省略
}
//由于提供给第三方服务时项目是用ShiroSession会话管理管控的
//现在要在此基础上增加JWT鉴权
//所以过滤 attackApi/**的请求,[不通过Shiro认证,JWT拦截器会拦截attackApi/**的请求]
//此处没有使用Shiro就不用关注



Map hashMap = new LinkedHashMap<>();

List NONE_PERMISSION_RES = CollectionUtil.newLinkedList
        ("/static/**", "/attackApi/**", "/login", "/global/sessionError", "/kaptcha","/cornemail","/init/start","/websocket/new_message","/global/error","/api/**");
        
for (String nonePermissionRe : NONE_PERMISSION_RES) {
    hashMap.put(nonePermissionRe, "anon");
}

hashMap.put("/**", "user");
shiroFilter.setFilterChainDefinitionMap(hashMap);
/**
* Created by 張燿峰
*
* @author 孤
* @date 2019/1/2
* @Varsion 1.0
*/
@RestController
@RequestMapping(value = "/attackApi")
public class AttackApi extends BaseController {
    @Autowired
    private UserMapper userMapper;

    @RequestMapping("/auth")
    public Object auth(@RequestParam("username") String username,
                       @RequestParam("password") String password) {

        //封装请求账号密码为shiro可验证的token
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password.toCharArray());

        //获取数据库中的账号密码,准备比对
        User user = userMapper.getByAccount(username);
        if (user == null) {
            return new ErrorResponseData(BizExceptionEnum.ACCOUNT_OR_PWD_ERROR.getCode(), BizExceptionEnum.ACCOUNT_OR_PWD_ERROR.getMessage());
        }
        String credentials = user.getPassword();
        String salt = user.getSalt();
        ByteSource credentialsSalt = new Md5Hash(salt);
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
                new ShiroUser(), credentials, credentialsSalt, "");

        //校验用户账号密码
        HashedCredentialsMatcher md5CredentialsMatcher = new HashedCredentialsMatcher();
        md5CredentialsMatcher.setHashAlgorithmName(ShiroKit.hashAlgorithmName);
        md5CredentialsMatcher.setHashIterations(ShiroKit.hashIterations);
        boolean passwordTrueFlag = md5CredentialsMatcher.doCredentialsMatch(
                usernamePasswordToken, simpleAuthenticationInfo);

        if (passwordTrueFlag) {
            HashMap result = new HashMap<>();
            result.put("token", AuthUtil.generateToken(String.valueOf(user.getId())));
            return result;
        } else {
            return new ErrorResponseData(BizExceptionEnum.ACCOUNT_OR_PWD_ERROR.getCode(), BizExceptionEnum.ACCOUNT_OR_PWD_ERROR.getMessage());
        }
    }

    /**
     * 测试接口是否走鉴权
     */
    @GetMapping(value = "/test")
    public Object test() {
        return SUCCESS_TIP;
    }



//首先访问attackApi/auth接口,用正确的用户密码获取JWT
//请求attackApi/test 接口 什么都不携带会抛出令牌验证失败的异常
//在Header中携带Authorization:attackFind jwt参数
//验证成功

你可能感兴趣的:(springboot干货)