前后端分离之Springboot后端

时间:2021-09-19 21:06:39

这是上一篇博客前后端分离之Java后端的重写.
源码
前后端分离的后端主要解决的就2个问题 : 跨域访问(CORS)token校验,下面快速说明.

1.项目环境

使用Intellij IDE.
项目结构:
前后端分离之Springboot后端

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里,如下:
前后端分离之Springboot后端

这种请求放在Axios这样的请求框架很好实现,特别是在React或Vue里.