JWT实现登录-跨域+axios+servlet+filter+jose4j

说明

jwt使用 jose4j 实现

准备

maven依赖

<dependency>
    <groupId>org.bitbucket.b_c</groupId>
    <artifactId>jose4j</artifactId>
    <version>0.6.3</version>
</dependency>

依赖jar

  • jose4j-0.7.9.jar
  • log4j-1.2.17.jar
  • slf4j-api-1.7.25.jar
  • slf4j-log4j12-1.7.25.jar
  • jackson-annotations-2.3.1.jar
  • jackson-core-2.3.1.jar
  • jackson-databind-2.3.1.jar

java代码

跨域过滤器(重要)

详见:
https://www.malaoshi.top/show_1IX46QGBvoNw.html

CharsetFilter

处理中文乱码的过滤器,详见:
https://www.malaoshi.top/show_1IX40rYGj8Ud.html

JWT(重要)

该类封装了生成 JWT 和 校验 JWT

package top.malaoshi.util;
import java.security.PrivateKey;
import java.util.UUID;

import org.jose4j.json.JsonUtil;
import org.jose4j.jwk.RsaJsonWebKey;
import org.jose4j.jwk.RsaJwkGenerator;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.NumericDate;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.lang.JoseException;

public class JWT {
    private static String keyId = "fa677d525c0e4ee485a61543937794af";
    private static String privateKeyStr = "{\"kty\":\"RSA\",\"kid\":\"fa677d525c0e4ee485a61543937794af\",\"alg\":\"RS256\",\"n\":\"ki6NKW2ow53FBjWf21xNGF0v-Fzv9R4-vu5tz7LHz4vZ7TE2Lp7Xx0N2vFIH-HLZPLfAYW35W5iV29sW_MkbhVlh6f0q4AeCIeYrVjBGbcYTK5g-Sb8i9sO78DkGivryKTU4tUnOjqar2bfobwXScFhAgc4-BjIcvZ9V8LEzcAW76he500-sqekXqvYv7LbxIMlbadGEwbBqjscE83hiYjk1KSFrEeNWKP6E0X_cHVGEEGys8IKlBcfwOOCgaJ0sCFxvN3M54V33jSUknFzHAi1qJRsOI87-Fk1oYS-aniQOTfm5y5x1syTIgWEX9JvXCQgTxjp2kMItuoL2G2faoQ\",\"e\":\"AQAB\",\"d\":\"OmSUCN-AEZv9LwzOrW6CcWAQIHLne4-4WsadYOE2hcaEqAYHcboL0dI2JOXTv0AJXQK9u22VtSwPeMJcvV-MOclJnpF9xf3Z0rbByuz_xSvhToHDJ-xNCCuJ8FynK28wuptC6s_vzfXwIckf9PFrbWsjYXbEOe9cobZ7Ould9boHkEq-x0eKkDFfcQbevuQeM54FCk5FGl_D4wpbuqudZiT8FYCJPO7m-MTnND6xzdEVuApuQUPCjAin34ygp9QPk2VfQaM1z-ZHchTaXtsv5gyLwu3bqA91vugmrtGuSBG8pFaN6mYL_ivt1Q_7QDLqXZ5_WBhkoXO5DZ4-6ApLvQ\",\"p\":\"58fTzOMjU1zJ42ZdUkJjSpRlZXdlWx3ILStrvVw7kCHH1iBZ4Lckd7SdqJ9DUfdIBFTNPaAKfDqpoz7fWOUZyyUpy7_8qaDagFi53pAjuCGUuKnOV5hbSPBIOvGi9iTHwLNz6Om5eRV6lBXoTRJgT7g8u88HeSyl29RmYt-N7rs\",\"q\":\"oXTxPC0BFDxpFjk2Jpuj6hBkioD3Kiv7HEJjxRoOL3TEAAc3811f7V7bO5FWK2L_2g8I5YiZhsyh0SyxjPBfuIzcbAfGSTAsFqKiIOGZ7fqiFTOV319FJIkcMOrT2dbvGxNeGgTJpWB1vcX6C8i3ytzPFto1H9Bl3xAPVFflHFM\",\"dp\":\"RU-eaLCryav_u37K_WRY6N6Di9oudxbq24cWiuPf8_QGHGREPEzIHPvoAZrOuN4nrRPm5DzNpeStAeI1TBIGqpcMbp-U4Oz3KlZeDs4vwEpafPZafBtVgPRJxUapIs5Q5bFEQixSiIEBzPLYKuQJ5Q0FLGx2oafWWWykyYBsoy0\",\"dq\":\"SS2_yQ581rcqyi_UI1uXx5b2evBJFowonH5ayhMtKsU5sOmUqnE_8U50_2K4M6IDZMo7tg1byIUnMq-XKdIpEHSH008SyElVMk00PsMCCaL3o7Rl0YBUzmJ2rJVCwBFy_kqg9BoHazV1KDZ7RqwK4Z-DHVB5k5nZEmktCYVtCpE\",\"qi\":\"jDbVP3xGaYx2wgAGJT7KNjdoC4dYbl5ajp9saRCgAih3TYr1CjZSrLRm9L90UukgwajU0pHaffP74epVx0RBhnS5GtZhCoGitpLwYSDkZ9qTMTVnFPHrg6M1OYEEXjZ_UKlnYCrzrDe6tfHcO1UTzMzsuOgHjRAV7IS6ImfzwVw\"}";
    private static String publicKeyStr = "{\"kty\":\"RSA\",\"kid\":\"fa677d525c0e4ee485a61543937794af\",\"alg\":\"RS256\",\"n\":\"ki6NKW2ow53FBjWf21xNGF0v-Fzv9R4-vu5tz7LHz4vZ7TE2Lp7Xx0N2vFIH-HLZPLfAYW35W5iV29sW_MkbhVlh6f0q4AeCIeYrVjBGbcYTK5g-Sb8i9sO78DkGivryKTU4tUnOjqar2bfobwXScFhAgc4-BjIcvZ9V8LEzcAW76he500-sqekXqvYv7LbxIMlbadGEwbBqjscE83hiYjk1KSFrEeNWKP6E0X_cHVGEEGys8IKlBcfwOOCgaJ0sCFxvN3M54V33jSUknFzHAi1qJRsOI87-Fk1oYS-aniQOTfm5y5x1syTIgWEX9JvXCQgTxjp2kMItuoL2G2faoQ\",\"e\":\"AQAB\"}";

    public static long accessTokenExpirationTime = 60 * 60 * 24;

    //jws创建token
    public String createToken(String account) {
        try {
            //Payload
            JwtClaims claims = new JwtClaims();
            claims.setGeneratedJwtId();
            claims.setIssuedAtToNow();
            //expire time
            NumericDate date = NumericDate.now();
            date.addSeconds(accessTokenExpirationTime);
            claims.setExpirationTime(date);
            claims.setNotBeforeMinutesInThePast(1);
            claims.setSubject("YOUR_SUBJECT");
            claims.setAudience("YOUR_AUDIENCE");
            //添加自定义参数,必须是字符串类型
            claims.setClaim("account", account);

            //jws
            JsonWebSignature jws = new JsonWebSignature();
            //签名算法RS256
            jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
            jws.setKeyIdHeaderValue(keyId);
            jws.setPayload(claims.toJson());
                /*
                RsaJsonWebKey jwk = null;
                try {
                    jwk = RsaJwkGenerator.generateJwk(2048);
                    } catch (JoseException e) {
                        e.printStackTrace();
                    }
                    jwk.setKeyId(keyId); */
            //PrivateKey privateKey = jwk.getPrivateKey();
            PrivateKey privateKey = new RsaJsonWebKey(JsonUtil.parseJson(privateKeyStr)).getPrivateKey();
            jws.setKey(privateKey);

            //get token
            String idToken = jws.getCompactSerialization();
            return idToken;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * jws校验token
     *
     * @param token
     * @return 返回 用户账号
     * @throws JoseException
     */
    public String verifyToken(String token) {
        try {
            JwtConsumer consumer = new JwtConsumerBuilder()
                    .setRequireExpirationTime()
                    .setMaxFutureValidityInMinutes(5256000)
                    .setAllowedClockSkewInSeconds(30)
                    .setRequireSubject()
                    //.setExpectedIssuer("")
                    .setExpectedAudience("YOUR_AUDIENCE")
                    /*
                    RsaJsonWebKey jwk = null;
                    try {
                        jwk = RsaJwkGenerator.generateJwk(2048);
                        } catch (JoseException e) {
                            e.printStackTrace();
                        }
                        jwk.setKeyId(keyId); */
                    //.setVerificationKey(jwk.getPublicKey())
                    .setVerificationKey(new RsaJsonWebKey(JsonUtil.parseJson(publicKeyStr)).getPublicKey())
                    .build();

            JwtClaims claims = consumer.processToClaims(token);
            if (claims != null) {
                System.out.println("认证通过!");
                String account = (String) claims.getClaimValue("account");
                System.out.println("token payload携带的自定义内容:用户账号account=" + account);
                return account;
            }
        }  catch (JoseException e) {
            e.printStackTrace();
        }  catch (InvalidJwtException e) {
            e.printStackTrace();
        }catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 创建jwk keyId , 公钥 ,秘钥
     */
    public static void createKeyPair(){
        String keyId = UUID.randomUUID().toString().replaceAll("-", "");
        RsaJsonWebKey jwk = null;
        try {
            jwk = RsaJwkGenerator.generateJwk(2048);
        } catch (JoseException e) {
            e.printStackTrace();
        }
        jwk.setKeyId(keyId);
        //采用的签名算法 RS256
        jwk.setAlgorithm(AlgorithmIdentifiers.RSA_USING_SHA256);
        String publicKey = jwk.toJson(RsaJsonWebKey.OutputControlLevel.PUBLIC_ONLY);
        String privateKey = jwk.toJson(RsaJsonWebKey.OutputControlLevel.INCLUDE_PRIVATE);

        System.out.println("keyId="+keyId);
        System.out.println();
        System.out.println("公钥 publicKeyStr="+publicKey);
        System.out.println();
        System.out.println("私钥 privateKeyStr="+privateKey);
    }

    public static void main(String[] args){
//            createKeyPair();
        JWT jwt=new JWT();
        String token=jwt.createToken("李雷");
        System.out.println(token);

        String username=jwt.verifyToken(token);
        System.out.println("username:"+username);
    }
}

JsonResult

详见:
https://www.malaoshi.top/show_1IX2rn0kwlcB.html

UserInfo

package top.malaoshi.bean;

public class UserInfo {
    private String id;
    private String username;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

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

登录 servlet(重要)

登录成功后,将 用户id、用户名、转成 JWT,并返回给前端

package top.malaoshi.servlet;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.jose4j.json.JsonUtil;
import top.malaoshi.bean.JsonResult;
import top.malaoshi.bean.UserInfo;
import top.malaoshi.util.JWT;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(urlPatterns = "/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        UserInfo ui=new UserInfo();
        ui.setId("100");
        ui.setUsername("李雷");
        ObjectMapper om=new ObjectMapper();
        String uiJSON=om.writeValueAsString(ui);
        System.out.println(uiJSON);

        JWT jwt=new JWT();
        String token=jwt.createToken(uiJSON);

        System.out.println(token);

        String json=JsonResult.success("登录成功",token);

        resp.getWriter().write(json);
//        resp.setHeader("token",token);
    }
}

测试通过header获取用户信息 TestJWT

前端发请求,从请求 header 中取出 auth 数据(JWT格式),并解析出数据

package top.malaoshi.servlet;

import com.fasterxml.jackson.databind.ObjectMapper;
import top.malaoshi.bean.UserInfo;
import top.malaoshi.util.JWT;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(urlPatterns = "/testJWT")
public class TestJWT extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String auth=req.getHeader("auth");
        System.out.println("auth:"+auth);

        JWT jwt=new JWT();
        String authText=jwt.verifyToken(auth);
        System.out.println("authText:"+authText);

        ObjectMapper om=new ObjectMapper();
        //解析出UserInfo对象
        UserInfo ui=om.readValue(authText, UserInfo.class);

        System.out.println();
    }
}

html代码

需要准备 axios.js

在另一个服务器中,实现跨域

注意:页面不要放在 tomcat 下,否则就不是跨域了

登录页面

跨域访问 登录Servlet,从返回数据中得到 JWT,然后存储到 localStorage

注意:现在是 多页面应用,需要用 localStorage

创建页面 login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="js/axios.js"></script>
    <title>Document</title>
</head>
<body>
    <script>
        axios.post('http://localhost:8080/web_war_exploded/login', {
        })
        .then(function (res) {
            console.log(res.data);
            console.log(res.data.data)
            localStorage.setItem('auth', res.data.data);
            console.log(localStorage.getItem('auth'))
        })
        .catch(function (error) {// 请求失败处理
            console.log(error);
        });
        </script>
    </script>
</body>
</html>

测试页面

该页面跨域访问 testJWTServlet,axios 发请求时,从 localStorage 中取出 JWT,然后将 JWT 放入头信息中

创建页面 testJWT.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="js/axios.js"></script>
    <title>Document</title>
</head>
<body>
    <script>
        console.log(localStorage.getItem('auth'))
        //post方式
        // axios.post('http://localhost:8080/web_war_exploded/testJWT', {
        // },{
        //     headers: {
        //         'auth': localStorage.getItem('auth')
        //     }
        // })
        // .then(res => {
        //     console.log(res);
        // })
        // .catch(erro => {// 请求失败处理
        //     console.log(error);
        // });
        //get方式
        axios.get('http://localhost:8080/web_war_exploded/testJWT',{
            headers: {
                'auth': localStorage.getItem('auth')
            }
        })
        .then(res => {
            console.log(res);
        })
        .catch(error => {// 请求失败处理
            console.log(error);
        });
        </script>
    </script>
</body>
</html>