Java Web 开发 springboot 前后端分离以及身份验证

我先接触的前后端分离是.Net的webapi,特性路由什么的,所以想知道java中的webapi是什么样的,在网上直接查java webapi

得不到类似于C# 的webapi的资料,但是查java 前后端分离,就能找到类似于C# webapi的东西。

看了一篇文章,根据文章中提供的github地址拉取了源码,源码和文章中的代码很不一样,然后我就综合原文和拉取的代码,以

及运行的过程中发现的问题以及最终的解决方法整理了下面的文章。不过我看的那篇文章对前后端分离的部分的几乎没说什么,

主要讲了为什么要前后端分离以及不少身份验证的知识,生成token,验证token,拦截器什么的。


以前服务端为什么能识别用户呢?对,是session,每个session都存在服务端,浏览器每次请求都带着sessionId(就是一个字符串),于是服务器根据这个sessionId就知道是哪个用户了。 
那么问题来了,用户很多时,服务器压力很大,如果采用分布式存储session,又可能会出现不同步问题,那么前后端分离就很好的解决了这个问题。

前后端分离思想: 
在用户第一次登录成功后,服务端返回一个token回来,这个token是根据userId进行加密的,密钥只有服务器知道,然后浏览器每次请求都把这个token放在Header里请求,这样服务器只需进行简单的解密就知道是哪个用户了。这样服务器就能专心处理业务,用户多了就加机器。当然,如果非要讨论安全性,那又有说不完的话题了。

下面通过SpringBoot框架搭建一个后台,进行token构建

 

1.项目的概览

目录结构:

为了尽可能简单,就不连数据库了,登陆时用固定的。

原文并没有从头开始讲创建项目的过程,但是既然是创建springboot项目所以基本过程应该是:

File——New——Project——Spring Initializer,

点击next:

然后接着填写信息或更改信息

点击next:

然后选择依赖,这一步就很重要,整合mybatis,使用thymeleaf,开发web特性的项目等都可以在此时选好依赖。

创建前后端分离的springboot的后端项目,要添加哪些依赖呢?后面会提到pom.xml,那个代码是从拉取的源码中粘贴过来的,

依赖选好了,就可以直接点击next:

然后填写好项目名称以及项目位置,就可以点击finish了。

 

pom.xml里的依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.jimo</groupId>
    <artifactId>auth-jimo</artifactId>
    <version>2.0.0</version>
    <packaging>jar</packaging>

    <name>AuthServer</name>
    <description>auth server</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- JJWT -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

2.UserController类代码:
这里的加密密钥是:base64EncodedSecretKey

package com.jimo.controller;

import com.jimo.model.User;
import com.jimo.model.common.Result;
import com.jimo.security.JwtUtil;
import org.springframework.web.bind.annotation.*;

import javax.servlet.ServletException;

/**
 * @author jimo
 * @func controller
 * @date 2018/8/24 22:44
 */
@RestController
@RequestMapping("/user")//类似于C# Webapi中的特性路由
public class UserController {

    /**
     * @func 测试时先用死的用户名密码,请求使用JSON格式数据
     * @author wangpeng
     * @date 2018/8/24 22:45
     */
    @PostMapping("/login")     //类似于C# Webapi中的特性路由
    public Result login(@RequestBody User user) throws ServletException {
        if (!"admin".equals(user.getUsername())) {
            throw new ServletException("no such user");
        }
        if (!"1234".equals(user.getPassword())) {
            throw new ServletException("wrong password");
        }
        return new Result(JwtUtil.getToken(user.getUsername()));
    }

    /**
     * @func 用于客户端检查token是否合法
     * @author wangpeng
     * @date 2018/8/27 16:58
     */
    @PostMapping("/checkToken")
    public Result checkToken(String token) {
        return new Result(JwtUtil.isTokenOk(token));
    }

    @GetMapping("/success")
    public Result success() {
        return new Result("login success");
    }

    @GetMapping("/getEmail")
    public Result getEmail() {
        return new Result("jimo@qq.com");
    }
}

3.GlobalExceptionHandler类代码:

package com.jimo.exp;

import com.jimo.model.common.Result;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * @author jimo
 * @func 全局异常处理
 * @date 2018/8/24 22:44
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e) {
        return new Result(false, e.getMessage());
    }
}

4.Result类代码:

package com.jimo.model.common;

/**
 * @author jimo
 * @func 封装统一的返回数据
 * @date 2018/8/24 22:46
 */
public class Result {
    /**
     * 成功为true
     */
    private boolean ok;
    /**
     * 错误消息或其他提示
     */
    private String msg;
    /**
     * 数据
     */
    private Object data;

    public Result() {
        this(true, "", null);
    }

    public Result(Object data) {
        this(true, "", data);
    }

    public Result(boolean ok, String msg) {
        this(ok, msg, null);
    }

    public Result(boolean ok, String msg, Object data) {
        this.ok = ok;
        this.msg = msg;
        this.data = data;
    }

    public boolean isOk() {
        return ok;
    }

    public void setOk(boolean ok) {
        this.ok = ok;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

5.User类代码:

package com.jimo.model;

public class User {
   private String username;
   private String password;

   public String getUsername() {
      return username;
   }

   public void setUsername(String username) {
      this.username = username;
   }

   public String getPassword() {
      return password;
   }

   public void setPassword(String password) {
      this.password = password;
   }
}

6.JwtInterceptor类代码:

package com.jimo.security;

import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author jimo
 * @func 拦截token并验证,不通过则抛出异常
 * @date 2018/8/24 22:38
 */
public class JwtInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("prehandle");
        final String authorization = request.getParameter("Authorization");
        /*String authHeader = request.getHeader("Authorization");*/
        if (authorization == null || !authorization.startsWith("Bearer ")) {
            throw new ServletException("invalid Authorization header,请重新登陆");
        }
        //取得token
        String token = authorization.substring(7);
        try {
            JwtUtil.checkToken(token);
            return true;
        } catch (Exception e) {
            throw new ServletException(e.getMessage());
        }
    }
}

7.JwtUtil类代码:

package com.jimo.security;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import javax.servlet.ServletException;
import java.util.Date;

/**
 * @author jimo
 * @func Jwt相关
 * @date 17-12-12 下午5:28
 */
public class JwtUtil {
    /**
     * 私钥
     */
    final static String base64EncodedSecretKey = "base64EncodedSecretKey";
    /**
     * 过期时间,测试使用20分钟
     */
    final static long TOKEN_EXP = 1000 * 60 * 20;

    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();

       //return  Jwts.builder().setSubject(userName).claim("roles", "user").setIssuedAt(new Date())
       //         .signWith(SignatureAlgorithm.HS256, "base64EncodedSecretKey").compact();


    }

    /**
     * @func 检查token, 只要不正确就会抛出异常
     * @author jimo
     * @date 17-12-12 下午6:21
     */
    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");
        }
    }

    /**
     * @func token ok返回true
     * @author wangpeng
     * @date 2018/8/27 16:59
     */
    public static boolean isTokenOk(String token) {
        try {
            Jwts.parser().setSigningKey(base64EncodedSecretKey).parseClaimsJws(token).getBody();
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

在调用login()方法后,会生成一个token,然后用这个token调用success方法应该会提示"login success",但是事情没有想的那么

顺利:

我用生成的token去调用success方法时,总是提示"invalid Authorization header,请重新登陆"。然后我发现,

上述代码在postman中输入http://localhost:8081/user/login以及admin和1234,每一次请求都会产生不同的Token,

所以我认为当输入http://localhost:8081/user/login,且填入新生成的token时,验证失败。而且我以为要想解决这个问题要先解决

下述问题:

怎么能在生成一个token后可以在一段相对较长的时间内使用这个token调用其他方法?

 

不过,很快我就发现还有一个问题,我发现或者说注意到,每次返回的信息都是:

{
    "ok": false,
    "msg": "invalid Authorization header,请重新登陆",
    "data": null
}

为什么不是提示token过期呢?

通过查看代码可以知道,“invalid Authorization header,请重新登陆”这条信息只有在token为空或者格式不正确的时候才会提示,

token过期应该提示的是“token expired”。而上述问题应该属于token过期啊。

 

上述两个问题有没有什么关系?

我添加了两行代码(都是在控制台输出token的),我想要看看能不能取到前端传来的token,如果能取到,是什么 样的?

public class JwtInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("prehandle");
        final String authorization = request.getParameter("Authorization");
        /*String authHeader = request.getHeader("Authorization");*/
        //test
        System.out.println(authorization);//输出的是null

        if (authorization == null || !authorization.startsWith("Bearer ")) {
            throw new ServletException("invalid Authorization header,请重新登陆");
        }
        //取得token
        String token = authorization.substring(7);
        //test
        System.out.println(token);//程序没能运行到这里
        try {
            JwtUtil.checkToken(token);
            return true;
        } catch (Exception e) {
            throw new ServletException(e.getMessage());
        }
    }
}

为什么会这样?输出是null没有得到数据!

代码里获取数据的代码是:request.getParameter("Authorization")

还要注释掉的获取数据的代码:request.getHeader("Authorization")

“Authorization”是在Parameter还是在Header,还是两者都不是,而是其他?

postman是这样的,Authorization是放在Headers里面的:

而代码里调用的确实getParameter()方法,如果改成getHeader()会如何?

问题解决了!而且是两个问题都解决了。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页