史上最简单的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参数
//验证成功