二、使用token解决前端后端分离用户认证问题
2.1 用户提交帐号和密码到服务器的认证接口
-
login.html
-
doSubmit:function(){ console.log("~~~~~~~~~~~~~doSubmit"); axios.get("http://localhost:8080/user/login",{ params:{ username:this.username, password:this.password } }).then(function(res){ if(res.data.code==0){ var token = res.data.data; //在前端存储token document.cookie = "token="+token; //localStorage.setItem("token",token); //跳转到index.html location.href = "index.html"; }else{ //弹出提示框:提示登录失败 } }); }
2.2 认证接口
-
--> 接受帐号和密码进行认证
-
--> 生成token(如果是随机token则需要在服务器进行存储,如果是按照特定的协议生成则无需存储)
-
--> 将生成的token响应给前端
- UserController
-
@RequestMapping(value = "/login",method = RequestMethod.GET) @ApiOperation(value = "用户认证接口",notes = "调用此接口的注意事项") @ApiImplicitParams({ @ApiImplicitParam(paramType = "query",name="username", required = true,dataType = "Book"), @ApiImplicitParam(paramType = "query",name="password",required = true,dataType = "String") }) public ResultVO login(String username, String password, HttpServletRequest request) { try { User user = userService.checkLogin(username, password); if (user!=null){ //登录成功 //request.getSession().setAttribute("user",user); //b.生成token(自定义生成规则) String token = TokenUtil.createToken(username); //c.将token回传给前端 return new ResultVO(0,"success",token); } } catch (Exception e) { e.printStackTrace(); } return new ResultVO(1,"fail"); }
public class TokenUtil { public static String createToken(String username){ String f = username.substring(0,1); String l = username.substring(username.length()-1); String str = l+f+username+l+f; String token = username+"."+Base64.encode(str.getBytes()); return token; } }
2.3 前端获取并存储token(cookie,localstorage)
-
在登录页面的回调函数中
-
if(res.data.code==0){ var token = res.data.data; //在前端存储token document.cookie = "token="+token; //localStorage.setItem("token",token); //跳转到index.html location.href = "index.html"; }else{ //弹出提示框:提示登录失败 }
2.4 当前端再次请求服务器接口时必须携带token
-
当再次请求接口时,需要携带token
-
//【获取token】 //1.从cookie获取 var cks = document.cookie.split(","); for(var i=0; i<cks.length; i++){ if(cks[i].split("=")[0] == "token"){ this.token = cks[i].split("=")[1]; } } //2.从localStorage获取 this.token = localStorage.getItem("token"); //【axios发送请求,header携带token】 //1.设置axios的请求header中常驻token axios.defaults.headers.common["token"] = this.token; //2.在每次请求时设置token axios({ url:"", method:"", params:{}, data:{}, headers:{ token:this.token } }).then(res=>{ //处理响应结果 });
2.5 在服务器通过拦截器验证token
-
-
public class UnTokenException extends Exception { public UnTokenException(String message) { super(message); } }
- 创建拦截器
-
@Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if("options".equalsIgnoreCase(request.getMethod())){ return true; }else { String token = request.getHeader("token"); System.out.println(token); if (token != null && !"".equals(token)) { //验证token String username = token.split("[.]")[0]; String rightToken = TokenUtil.createToken(username); if (rightToken.equals(token)) { return true; } else { //抛出自定义异常,通过全局异常处理返回给前端一个VO(包含“请先登录”提示信息) throw new UnTokenException("非法token,请认证"); } } else { throw new UnTokenException("请求参数不正确(缺少token)"); } } } }
-
配置拦截器
-
@Configuration public class AppConfig implements WebMvcConfigurer { @Resource private LoginInterceptor loginInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns("/**") .excludePathPatterns("/user/**"); } }
- 通过全局异常处理,处理token不合法问题
-
@ControllerAdvice @CrossOrigin public class UnTokenExceptionHanlder { @ExceptionHandler @ResponseBody public ResultVO exceptionHanlder(Exception e){ return new ResultVO(1,e.getMessage()); } }
三、服务器跨域设置
-
跨域配置
-
@Configuration public class CrossConfig { @Bean public WebMvcConfigurer getWebMvcConfigurer() { WebMvcConfigurer webMvcConfigurer = new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedHeaders("*") .allowedMethods("*") .allowCredentials(false); } }; return webMvcConfigurer; } }
-
-
-
-
@Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if("options".equalsIgnoreCase(request.getMethod())){ return true; }else { String token = request.getHeader("token"); System.out.println(token); if (token != null && !"".equals(token)) { //验证token String username = token.split("[.]")[0]; String rightToken = TokenUtil.createToken(username); if (rightToken.equals(token)) { return true; } else { //抛出自定义异常,通过全局异常处理返回给前端一个VO(包含“请先登录”提示信息) throw new UnTokenException("非法token,请认证"); } } else { throw new UnTokenException("请求参数不正确(缺少token)"); } } } }
-
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.8.3</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
String token = Jwts.builder() .setSubject(user.getUsername()) //设置用户信息 .setId(user.getId()+"") //设置用户ID .setIssuedAt(new Date()) //设置token的创建时间 .setExpiration(new Date(System.currentTimeMillis()+60*1000)) //设置过期时间 .signWith(SignatureAlgorithm.HS256,"qianfeng") //加密方式及key .compact();
@Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws UnTokenException, ExpiredJwtException,SignatureException { if("options".equalsIgnoreCase(request.getMethod())){ return true; }else { String token = request.getHeader("token"); System.out.println(token); if (token != null && !"".equals(token)) { //校验token JwtParser parser = Jwts.parser(); parser.setSigningKey("qianfeng"); //解析token,只要不抛出异常表示token正常 Jws<Claims> claimsJws = parser.parseClaimsJws(token); //从token中获取信息 Claims body = claimsJws.getBody(); String subject = body.getSubject(); return true; } else { throw new UnTokenException("token为NULL"); } } } }