jwt 介绍就不多说了,下面通过代码演示开发过程中jwt 的使用。
(1)在pom.xml中引入对应的jar
io.jsonwebtoken
jjwt
0.7.0
(2)引入jwt 工具类:token的生成以及获取对应的token信息
/**
* @author : wl
* @Description :
* @date : 2020/7/3 13:25
*/
public class JwtUtil {
public static final String AUTHORIZATION_SECRET = "wlcoder";
private static final String UID = "uid";
private static final String USERNAME = "username";
private static final String PASSWORD = "password";
private static final String STATUS = "status";
//创建秘钥
public static Key getKeyInstance() {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
byte[] bytes = DatatypeConverter.parseBase64Binary(AUTHORIZATION_SECRET);
return new SecretKeySpec(bytes, signatureAlgorithm.getJcaName());
}
/**
* 生成token的方法
*
* @param user
* @param expire
* @return
*/
public static String generatorToken(SysUser user, int expire) {
return Jwts.builder().claim(UID, user.getId())
.claim(USERNAME, user.getUsername())
.claim(PASSWORD, user.getPassword())
.claim(STATUS, user.getStatus())
.setExpiration(DateTime.now().plusSeconds(expire).toDate())
.signWith(SignatureAlgorithm.HS256, getKeyInstance())
.compact();
}
/**
* 根据token获取token中的信息
*
* @param token
* @return
*/
public static SysUser getTokenInfo(String token) {
Jws claimsJws = Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return SysUser.builder().id((Integer) claims.get(UID))
.username((String) claims.get(USERNAME))
.password((String) claims.get(PASSWORD))
.status((Integer) claims.get(STATUS))
.build();
}
}
(3)添加注解 @NeedToken,@SkipToken ,加在方法上灵活处理对应的请求
/**
* @author : wl
* @Description : 需要token 验证
* @date : 2020/7/3 11:40
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedToken {
boolean required() default true;
}
/**
* @author : wl
* @Description :跳过token 验证
* @date : 2020/7/3 11:39
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SkipToken {
boolean required() default true;
}
(4)添加拦截器,验证前端请求是否需要token
/**
* @author : wl
* @Description :方法请求拦截
* @date : 2020/7/3 11:47
*/
@Slf4j
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
private SysUserService userService;
@Autowired
private RedisUtil redisUtil;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws ServletException, IOException {
String token = httpServletRequest.getHeader("token");
if (!(object instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) object;
Method method = handlerMethod.getMethod();
//检查有没有跳过token的注解
if (method.isAnnotationPresent(SkipToken.class)) {
SkipToken skipToken = method.getAnnotation(SkipToken.class);
if (skipToken.required()) {
log.info("该请求无须token验证。。。");
return true;
}
}
//检查有没有需要token的注解
if (method.isAnnotationPresent(NeedToken.class)) {
NeedToken needToken = method.getAnnotation(NeedToken.class);
if (needToken.required()) {
log.info("该请求需要token验证。。。");
if (Objects.isNull(token)) {
throw new BaseException("无token,请重新登录");
}
try {
JwtUtil.getTokenInfo(token);
} catch (ExpiredJwtException e) {
throw new BaseException("token超时");
}
// SysUser user = userService.findUser(sysUser.getUsername(), sysUser.getPassword());
// if (Objects.isNull(user)) {
// throw new BaseException("用户不存在,请重新登录");
// }
if (!Objects.equals(token, redisUtil.get("ms_notify_token"))) {
throw new BaseException("token异常,请重新登录");
}
}
}
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 {
}
}
(5)添加拦截器异常处理 :出现异常直接跳转到登录页面 ,这里有个坑,前端为ajax请求时候 使用转发或者重定向会失效。因此处理为 判断请求为ajax请求则设置返回一个状态码如:httpServletResponse.setStatus(666); 前端jquery.js中统一判断处理。
/**
* @author : wl
* @Description : 拦截异常处理
* @date : 2020/7/5 15:30
*/
@Slf4j
public class MyWebHandlerException implements HandlerExceptionResolver {
@SneakyThrows
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
log.info("请求出现异常:" + e.getMessage());
e.printStackTrace();
// return new ModelAndView("redirect:/login");
ModelAndView modelAndView = new ModelAndView();
String type = httpServletRequest.getHeader("X-Requested-With");
if (Objects.equals(type, "XMLHttpRequest")) {
//是ajax请求
httpServletResponse.setStatus(666);
httpServletResponse.setContentType("text/javascript; charset=utf-8");
httpServletResponse.getWriter().write(e.getMessage());
return modelAndView;
} else {
modelAndView.setViewName("/login");
return modelAndView;
}
}
}
jquery.js中添加对应状态码处理:
//自定义异常信息: 跳转到登录页面
jQuery.ajaxSetup({
statusCode:{
666:function(data){
alert(data.responseText);
window.location.href="/login";
}
}
})
(6) 添加webConfig配置 实现WebMvcConfigurer,引入自定义的token拦截以及异常处理
/**
* @author : wl
* @Description :web拦截器
* @date : 2020/7/3 11:46
*/
@Configuration
public class webConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/**");
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
@Bean
public WebMvcConfigurer webMvcConfigurer() {
WebMvcConfigurer adapter = new WebMvcConfigurer() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
}
};
return adapter;
}
/*
* 异常拦截处理
* */
@Override
public void extendHandlerExceptionResolvers(List resolvers) {
resolvers.add(new MyWebHandlerException());
}
}
到这里基本配置基本差不多了,web应用中怎么处理呢?
例如登录功能:
登录后端代码:
/**
* @author : wl
* @Description :
* @date : 2020/7/2 16:46
*/
@Controller
public class Login {
@Autowired
private SysUserService sysUserService;
@Autowired
private RedisUtil redisUtil;
@SkipToken
@RequestMapping("/login")
public String toLogin() {
System.out.println("跳转到登录页面");
return "login";
}
@SkipToken
@RequestMapping("/index")
public String toIndex() {
System.out.println("跳转到主页面");
return "index";
}
@SkipToken
@ResponseBody
@RequestMapping("/loginIn")
public ResultUtil loginIn(String username, String password) {
try {
SysUser user = sysUserService.findUser(username, password);
if (null != user) {
user.setPassword(password);
String token = JwtUtil.generatorToken(user, 60*60);
//token 保存在redis中
redisUtil.set("ms_notify_token", token);
return ResultUtil.ok().data("msg", token).message("登录成功");
} else {
return ResultUtil.error().data("msg", "error").message("用户不存在");
}
} catch (BaseException e) {
return ResultUtil.error().data("msg", e.getMessage()).message("登陆失败");
}
}
}
登录前端代码:这里需要注意的是 配置window.location.href 不生效 需要检查是否设置为form 表单。
前端接受到token存储在localStorage: localStorage.setItem("token",data.data.msg);
登录
请登录
若是需要token验证,前端对应的ajax 请求需要加上headers 如:
//禁用,启用
function disable_config(nid, status) {
$.ajax({
url: '/notify/updateStatus',
data: {'nid': nid, 'status': status},
type: "post",
headers: {"token": localStorage.getItem("token")},
success: function (data) {
if (data.success) {
location.reload();
alert(data.message);
} else {
alert(data.message + ":" + data.data.msg)
}
}
});
}
后端对应方法上需要添加注解@NeedToken 如:
/**
* 禁用 、启用
*/
@NeedToken
@ResponseBody
@SysLogAnnotation("禁用 、启用 配置")
@RequestMapping(value = "/updateStatus")
public ResultUtil updateStatus(HttpServletRequest request, String nid, int status) {
String config_status = (status == 1 ? "启用" : "禁用");
try {
notifyConfigService.updateStatus(nid, status);
} catch (BaseException e) {
return ResultUtil.error().data("msg", e.getMessage()).message(config_status + "配置失败");
}
return ResultUtil.ok().data("msg", "success").message(config_status + "配置成功");
}
设计流程基本如上述代码所示,详细代码可以参考 github地址 : https://github.com/wlcoder/ms-notify
该项目主要是实现自动配置定时发送邮件信息,如果觉得不错,可以star一下。有什么意见或者建议,可以指出来,大家相互学习,谢谢!