Json web token 详解

下面我们用springmvc和jwt的类库来实现一个例子。

要使用jwt,需要pom.xml中添加如下依赖

<dependency>  
    <groupId>com.auth0</groupId>  
    <artifactId>java-jwt</artifactId>  
    <version>3.2.0</version>  
</dependency>  

 

下面贴一个完整的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>org.jstudioframework</groupId>  
    <artifactId>jstudio-jwt</artifactId>  
    <version>1.0-SNAPSHOT</version>  
  
    <properties>  
        <!-- 设置编码字符集 -->  
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
        <!-- 集中定义依赖版本号 -->  
        <junit.version>4.12</junit.version>  
        <spring.version>4.3.7.RELEASE</spring.version>  
        <jackson.version>2.8.4</jackson.version>  
        <java.jwt.version>3.2.0</java.jwt.version>  
        <jstl.version>1.2</jstl.version>  
        <servlet-api.version>2.5</servlet-api.version>  
        <jsp-api.version>2.2</jsp-api.version>  
    </properties>  
  
    <dependencies>  
        <dependency>  
            <groupId>junit</groupId>  
            <artifactId>junit</artifactId>  
            <version>${junit.version}</version>  
            <scope>test</scope>  
        </dependency>  
        <dependency>  
            <groupId>com.auth0</groupId>  
            <artifactId>java-jwt</artifactId>  
            <version>${java.jwt.version}</version>  
        </dependency>  
        <dependency>  
            <groupId>com.fasterxml.jackson.core</groupId>  
            <artifactId>jackson-databind</artifactId>  
            <version>${jackson.version}</version>  
        </dependency>  
        <dependency>  
            <groupId>com.fasterxml.jackson.core</groupId>  
            <artifactId>jackson-core</artifactId>  
            <version>${jackson.version}</version>  
        </dependency>  
        <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-core</artifactId>  
            <version>${spring.version}</version>  
        </dependency>  
        <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-beans</artifactId>  
            <version>${spring.version}</version>  
        </dependency>  
        <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-context</artifactId>  
            <version>${spring.version}</version>  
        </dependency>  
        <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-context-support</artifactId>  
            <version>${spring.version}</version>  
        </dependency>  
        <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-web</artifactId>  
            <version>${spring.version}</version>  
        </dependency>  
        <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-webmvc</artifactId>  
            <version>${spring.version}</version>  
        </dependency>  
        <!-- JSP相关 -->  
        <dependency>  
            <groupId>javax.servlet</groupId>  
            <artifactId>jstl</artifactId>  
            <version>${jstl.version}</version>  
        </dependency>  
        <dependency>  
            <groupId>javax.servlet</groupId>  
            <artifactId>servlet-api</artifactId>  
            <version>${servlet-api.version}</version>  
            <scope>provided</scope>  
        </dependency>  
        <dependency>  
            <groupId>javax.servlet.jsp</groupId>  
            <artifactId>jsp-api</artifactId>  
            <version>${jsp-api.version}</version>  
            <scope>provided</scope>  
        </dependency>  
    </dependencies>  
  
    <build>  
        <!-- 配置插件 -->  
        <plugins>  
            <plugin>  
                <groupId>org.apache.tomcat.maven</groupId>  
                <artifactId>tomcat7-maven-plugin</artifactId>  
                <configuration>  
                    <port>8080</port>  
                    <path>/</path>  
                    <url>http://127.0.0.1:8080/manager/text</url>  
                    <username>tomcat</username>  
                    <password>tomcat</password>  
                </configuration>  
            </plugin>  
        </plugins>  
    </build>  
</project>  

 

一。定义一个jwt的工具类,具有加密和解密token的功能

import java.util.HashMap;  
import java.util.Map;  
  
/** 
 * JwtToken 
 */  
public class JwtToken {  
  
    //密钥  
    private static final String SECRET = "secret";  
  
    //jackson  
    private static ObjectMapper mapper = new ObjectMapper();  
  
    /** 
     * header数据 
     * @return 
     */  
    private static Map<String, Object> createHead() {  
        Map<String, Object> map = new HashMap<String, Object>();  
        map.put("typ", "JWT");  
        map.put("alg", "HS256");  
        return map;  
    }  
  
    /** 
     * 生成token 
     * 
     * @param obj    对象数据 
     * @param maxAge 有效期 
     * @param <T> 
     * @return 
     */  
    public static <T> String sign(T obj, long maxAge) throws UnsupportedEncodingException, JsonProcessingException {  
        JWTCreator.Builder builder = JWT.create();  
  
        builder.withHeader(createHead())//header  
                .withSubject(mapper.writeValueAsString(obj));  //payload  
  
        if (maxAge >= 0) {  
            long expMillis = System.currentTimeMillis() + maxAge;  
            Date exp = new Date(expMillis);  
            builder.withExpiresAt(exp);  
        }  
  
        return builder.sign(Algorithm.HMAC256(SECRET));  
    }  
  
    /** 
     * 解密 
     * @param token   token字符串 
     * @param classT  解密后的类型 
     * @param <T> 
     * @return 
     */  
    public static <T> T unsign(String token, Class<T> classT) throws IOException {  
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();  
        DecodedJWT jwt = verifier.verify(token);  
  
        Date exp = jwt.getExpiresAt();  
        if(exp!=null&&exp.after(new Date())){  
            String subject = jwt.getSubject();  
            return mapper.readValue(subject, classT);  
        }  
  
        return null;  
    }  
  
}  

 

二。 登录时根据用户传来的username和password验证身份,如果合法,便给该用户jwt加密生成token

package jwt.controller;  
  
import com.fasterxml.jackson.core.JsonProcessingException;  
import jwt.JwtToken;  
import jwt.model.User;  
import jwt.util.ResponseEntity;  
import org.springframework.stereotype.Controller;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RequestParam;  
import org.springframework.web.bind.annotation.ResponseBody;  
  
import javax.servlet.http.HttpServletRequest;  
import java.io.UnsupportedEncodingException;  
  
/** 
 * 用户登录 
 */  
  
@Controller  
public class LoginController {  
  
    @RequestMapping(value="login", produces = "application/json; charset=utf-8")  
    @ResponseBody  
    public ResponseEntity login(HttpServletRequest request, @RequestParam( "username") String username,  
                                @RequestParam("password") String password) throws UnsupportedEncodingException, JsonProcessingException {  
        ResponseEntity responseEntity = ResponseEntity.ok();  
  
        if("admin".equals(username) && "admin".equals(password)) {  
            //模拟用户数据,真实环境需要到数据库验证  
            User user = new User();  
            user.setId(123456);  
            user.setUsername(username);  
            //给用户jwt加密生成token  
            String token = JwtToken.sign(user, 60L * 1000L * 30L);  
            //封装成对象返回给客户端  
            responseEntity.putDataValue("userId", user.getId());  
            responseEntity.putDataValue("token", token);  
            responseEntity.putDataValue("user", user);  
        }  
        else{  
            responseEntity =  ResponseEntity.customerError();  
        }  
        return responseEntity;  
    }  
  
}  

 

User用户类如下

package jwt.model;  
  
/** 
 * User bean 
 */  
public class User {  
    private long id;  
    private String username;  
    private String password;  
  
    public long getId() {  
        return id;  
    }  
  
    public void setId(long id) {  
        this.id = id;  
    }  
  
    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;  
    }  
}  

 

返回给用户端的一个工具类

package jwt.util;  
  
import java.util.HashMap;  
import java.util.Map;  
  
public class ResponseEntity {  
  
    public static final String ERRORS_KEY = "errors";  
  
    private final String message;  
    private final int code;  
    private final Map<String, Object> data = new HashMap();  
  
    public String getMessage() {  
        return message;  
    }  
  
    public int getCode() {  
        return code;  
    }  
  
    public Map<String, Object> getData() {  
        return data;  
    }  
  
    public ResponseEntity putDataValue(String key, Object value) {  
        data.put(key, value);  
        return this;  
    }  
  
    private ResponseEntity(int code, String message) {  
        this.code = code;  
        this.message = message;  
    }  
  
    public static ResponseEntity ok() {  
        return new ResponseEntity(200, "Ok");  
    }  
  
    public static ResponseEntity notFound() {  
        return new ResponseEntity(404, "Not Found");  
    }  
  
    public static ResponseEntity badRequest() {  
        return new ResponseEntity(400, "Bad Request");  
    }  
  
    public static ResponseEntity forbidden() {  
        return new ResponseEntity(403, "Forbidden");  
    }  
  
    public static ResponseEntity unauthorized() {  
        return new ResponseEntity(401, "unauthorized");  
    }  
  
    public static ResponseEntity serverInternalError() {  
        return new ResponseEntity(500, "Server Internal Error");  
    }  
  
    public static ResponseEntity customerError() {  
        return new ResponseEntity(1001, "Customer Error");  
    }  
}  

 

三。在用户登录后获得userId和token,以后用户每次请求时,都得带上这两个参数,后台拿到token后解密出userId,与用户传递过来的userId比较,如果相同,则说明用户身份合法。这里用springmvc的拦截器实现验证。

注意:测试使用Params传递参数的,建议正式环境在header里添加参数

package jwt.interceptor;  
  
import com.fasterxml.jackson.databind.ObjectMapper;  
import jwt.JwtToken;  
import jwt.model.User;  
import jwt.util.ResponseEntity;  
import org.springframework.web.servlet.HandlerInterceptor;  
import org.springframework.web.servlet.ModelAndView;  
  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  
import java.io.PrintWriter;  
  
/** 
 * Token验证拦截器 
 */  
public class UserTokenInterceptor implements HandlerInterceptor {  
  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {  
        String token = request.getParameter("token");  
        ResponseEntity responseData = ResponseEntity.ok();  
        //token不存在  
        if (token != null) {  
            User user = JwtToken.unsign(token, User.class);  
            String userId = request.getParameter("userId");  
            //解密token后的userId与用户传来的userId不一致,大多是因为token过期  
            if (user != null && userId != null && Integer.parseInt(userId) == user.getId()) {  
                return true;  
            }  
        }  
  
        responseData = ResponseEntity.forbidden();  
        response.setContentType("application/json; charset=utf-8");  
        ObjectMapper mapper = new ObjectMapper();  
        String json = mapper.writeValueAsString(responseData);  
        PrintWriter out = response.getWriter();  
        out.print(json);  
        out.flush();  
        out.close();  
        return false;  
    }  
  
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {  
  
    }  
  
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {  
  
    }  
}  

 

四。springmvc的配置

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
       xmlns:context="http://www.springframework.org/schema/context"  
       xmlns:mvc="http://www.springframework.org/schema/mvc"  
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd  
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">  
  
    <context:component-scan base-package="jwt.controller"/>  
  
    <mvc:annotation-driven />  
  
    <!-- 拦截器设置 -->  
    <mvc:interceptors>  
        <mvc:interceptor>  
            <!-- 匹配的是url路径, 如果不配置或/**,将拦截所有的Controller -->  
            <mvc:mapping path="/**" />  
            <!-- /register 和 /login 不需要拦截-->  
            <mvc:exclude-mapping path="/register" />  
            <mvc:exclude-mapping path="/login" />  
            <bean class="jwt.interceptor.UserTokenInterceptor"></bean>  
        </mvc:interceptor>  
        <!-- 当设置多个拦截器时,先按顺序调用preHandle方法,然后逆序调用每个拦截器的postHandle和afterCompletion方法 -->  
    </mvc:interceptors>  
  
    <!-- 默认的视图解析器 在上边的解析错误时使用 (默认使用html)- -->  
    <bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">  
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>  
        <property name="contentType" value="text/html"/>  
        <property name="prefix" value="/WEB-INF/jsp/"/>  
        <property name="suffix" value=".jsp"/>  
        <property name="order" value="1"/>  
    </bean>  
  
</beans>  

 

五。 使用Chrome插件Postman进行简单的测试

1.登录,http://localhost:8080/login

记下 token和userId

 

 

2. 测试token鉴权  http://localhost:8080/info

输入正确的userId和token

注意:测试使用Params传递参数的,建议正式环境在header里添加参数

 

输入错误的userId或token,在拦截器那里就被拦截了

 

原文地址:https://blog.csdn.net/zsg88/article/details/76892551

posted on 2018-06-14 13:53  让代码飞  阅读(729)  评论(0)    收藏  举报

导航

一款免费在线思维导图工具推荐:https://www.processon.com/i/593e9a29e4b0898669edaf7f?full_name=python