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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/youbitch1/article/details/86248270

史上最简单的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 張燿峰
* <p>Jwt工具类</p>
* 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<String, Object> claims = new HashMap<>();
        return doGeneratorToken(claims, userId);
    }

    private static String doGeneratorToken(Map<String, Object> 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<String> 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<String, String> hashMap = new LinkedHashMap<>();
List<String> 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<String, Object> 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参数
//验证成功

猜你喜欢

转载自blog.csdn.net/youbitch1/article/details/86248270
今日推荐