这是上一篇博客前后端分离之Java后端的重写.
源码
前后端分离的后端主要解决的就2个问题 : 跨域访问(CORS)和token校验,下面快速说明.
1.项目环境
使用Intellij IDE.
项目结构:
2.跨域访问
解决跨域很简单,翻一下官方文档很容易解决,我们就使用全局的通过注解实现的方式:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
//允许全部请求跨域
registry.addMapping("/**");
}
}
3.Token验证
这节分为2个部分,一是原理,二是代码实现.
3.1 原理
在第一篇文章里,我是这样说的:
在用户第一次登录成功后,服务端返回一个token回来,这个token是根据userId进行加密的,密钥只有服务器知道,然后浏览器每次请求都把这个token放在Header里请求,这样服务器只需进行简单的解密就知道是哪个用户了。
3.2 代码实现
避免重复造*,我们依然使用JWT,这个标准在2015年提出,查看RFC文档,它的一个实现JJWT
<!-- JJWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
我们要做的很简单 :登录时生成Token,拦截每次请求检查token.
3.2.1 生成Token与验证
详情查看代码注释
public class JwtUtil {
final static String base64EncodedSecretKey = "base64EncodedSecretKey";//私钥
final static long TOKEN_EXP = 1000 * 60;//过期时间,测试使用60秒
public static String getToken(String userName) {
return Jwts.builder()
.setSubject(userName)
.claim("roles", "user")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + TOKEN_EXP)) /*过期时间*/
.signWith(SignatureAlgorithm.HS256, base64EncodedSecretKey)
.compact();
}
/**
* @Date:17-12-12 下午6:21
* @Author:root
* @Desc:检查token,只要不正确就会抛出异常
**/
public static void checkToken(String token) throws ServletException {
try {
final Claims claims = Jwts.parser().setSigningKey(base64EncodedSecretKey).parseClaimsJws(token).getBody();
} catch (ExpiredJwtException e1) {
throw new ServletException("token expired");
} catch (Exception e) {
throw new ServletException("other token exception");
}
}
}
3.2.2 拦截token
在spring里很好实现全局拦截,过滤器,拦截器,AOP都可以实现.
因为filter是对资源过滤,我们这里没有资源了,只有URL,而AOP着重处理过程.综合考虑,这里选择拦截器比较合适.
我们的拦截器:先检查header,取出token,验证.
public class JwtInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new ServletException("invalid Authorization header");
}
//取得token
String token = authHeader.substring(7);
try {
JwtUtil.checkToken(token);
return true;
} catch (Exception e) {
throw new ServletException(e.getMessage());
}
}
}
注册拦截器:将登录排除
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
//允许全部请求跨域
registry.addMapping("/**");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//添加拦截器
registry.addInterceptor(new JwtInterceptor()).excludePathPatterns("/user/login");
}
}
全局异常处理:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public String handleException(Exception e) {
return "err:" + e.getMessage();
}
}
登录:
@PostMapping("/login")
public String login(User user) throws ServletException {
String name = user.getUsername();
String pass = user.getPassword();
if (!"admin".equals(name)) {
throw new ServletException("no such user");
}
if (!"1234".equals(pass)) {
throw new ServletException("wrong password");
}
return JwtUtil.getToken(name);
}
其他根据需要可以查看源码.
当然,整个系统我没有使用RESTful的统一API,可以自定义一个类去处理,这里不重要.
4.如何请求
将得到的token封装在header里,如下:
这种请求放在Axios这样的请求框架很好实现,特别是在React或Vue里.