ホームページ  >  記事  >  Java  >  Springboot は JWT をどのように統合して ID 認証を実現しますか?

Springboot は JWT をどのように統合して ID 認証を実現しますか?

王林
王林転載
2023-05-14 19:25:101527ブラウズ

1. JWT とは

JSON Web トークン (JWT) は、現在最も人気のあるクロスドメイン認証ソリューションです。現在のプロジェクト開発では一般的にフロントエンドとエンドエンドの分離が行われており、これにはクロスドメインと認証の問題が伴います。

2. JWT の構成

ヘッダー、ペイロード、署名の 3 つの部分で構成されます

Header :

ヘッダー情報1. トークンのタイプ、つまり JWT; 2. 使用される署名アルゴリズム (HMASSHA256 や RSA など);

{
  "alg": "HS256",
  "typ": "JWT"
}

この json では、typ 属性は、トークン全体がトークンであることを識別するために使用されます。トークン文字列は JWT 文字列です。その alg 属性は、この JWT を発行するときに使用される署名およびダイジェスト アルゴリズムを記述するために使用されます。typ 属性と alg 属性の完全な名前は、実際にはそれぞれ type アルゴリズムです。タイプとアルゴリズムの意味。 3文字で表す理由も、JWTの最終的な文字列サイズを考慮したもので、JWTの名前とも整合性があるため、すべて3文字で …typとalgは、で指定する属性です。 JWT 標準の名前。

ペイロード:

ペイロードは、送信されるデータを運ぶために使用されます。その json 構造は、実際には、JWT によって送信されるデータの宣言のセットです。 JWT 標準ではステートメントはクレームと呼ばれます。その「属性値ペア」の 1 つが実際にはクレーム (要件) であり、各クレームは特定の意味と機能を表します。

クレームにビジネス情報を含めることができます。

署名:

署名は、ヘッダーとペイロードに対応する JSON 構造を Base64URL でエンコードし、「英語のピリオド」で結合した後に得られる 2 つの文字列です。その後、ヘッダーの alg で指定された署名アルゴリズムに従って生成されます。
アルゴリズムが異なれば、署名結果も異なります。 alg: HS256 を例として、前の署名を取得する方法を説明します。

alg の利用可能な値に関する前述の説明によると、HS256 には実際には HMAC アルゴリズムと SHA256 アルゴリズムの 2 つのアルゴリズムが含まれています。前者はダイジェストの生成に使用され、後者はダイジェストのデジタル署名に使用されます。ダイジェスト。これら 2 つのアルゴリズムは、HMACSHA256

jwt データ構造図:

Springboot は JWT をどのように統合して ID 認証を実現しますか?

3. JWT の動作原理

1 と総称されることもあります。ログイン リクエストが送信されると、必然的にユーザー情報 uname と pwd

2 が送信されます。ユーザー情報 uname と pwd が正常にログインすると、ユーザー情報は jwt ツールを通じて暗号化された文字列に生成されます。 class

3. 暗号化された文字列は、応答ヘッダーの形式でフロントエンドに送信されます

#4. フロントエンド サーバーには、伝送される jwt 文字列をインターセプトするための応答インターセプターがあります。応答ヘッダーによって、それを Vuex

5 に追加します。2 回目のリクエスト時には、フロントエンド サーバーにリクエスト インターセプターがあり、Vuex の jwt 文字列をリクエスト ヘッダー request # に追加します。

##6. リクエストがクロスドメイン メソッドを通過してバックエンド サーバーに到達すると、バックエンド サーバーに別のフィルターがあり、リクエスト ヘッダーの jwt 文字列をインターセプトします。 jwt ツール クラスは、jwt 文字列を解析し、それをユーザー情報に解析し、最終的に検証します。バックグラウンド ログイン インターフェイスにアクセスしてログインします。最初にユーザー名とパスワードに基づいてユーザー テーブルにユーザーが存在するかどうかが判断されます。存在する場合、このユーザーに対して jwt 文字列が生成されます。jwt にビジネス情報を追加できます。文字列 (ログイン アカウント、ユーザーの本名など) を取得し、jwt 文字列をフロントエンドに返します。

現在のエンドは jwt 文字列を取得し、それをすべてのリクエストのヘッダーに挿入します。 as token=jwt string

すべてのリクエスト (ログインリクエストはまだ jwt を生成していないため、ログインリクエストを除く) をインターセプトするフィルターをバックエンドで開発し、リクエストのヘッダーから jwt を取得します (つまり、 、トークンの値)、jwt を確認して jwt 内のビジネス情報を取得し、バックエンド インターフェイスがヘッダーから直接ビジネス情報を取得できるように、リクエストのヘッダーにビジネス情報を置きます

フィルターの場合 jwt の有効期限が切れるか検証が失敗した場合、プロンプトがフロントエンドに返され、フロントエンドはログイン ページに戻ってユーザーが再度ログインできるようにします。

1. pom.xml に依存関係を導入します

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

2. jwt 生成ツール クラスを開発します。コードは次のとおりです:

package com.lsl.exam.utils;
 
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.lsl.exam.entity.TabUser;
 
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
 
public class JwtUtil {
 
    private static final long EXPIRE_TIME = 1000 * 60 * 60 *24;
 
    //设置私钥
    private static final String TOKEN_SECRET = "aa082c-66rt89-29sr3t-y9t7b8";
 
 
    /**
     * 创建携带自定义信息和声明的自定义私钥的jwt
     * @param user  用户信息表
     * @return  jwt串
     */
    public static String creatJwt(TabUser user){
        //构建头部信息
        Map<String,Object> header = new HashMap<>();
        header.put("typ","JWT");
        header.put("alg","HS256");
 
        //根据私钥构建密钥信息
        Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
 
        //根据当前用户密码构建密钥信息
//        Algorithm algorithm = Algorithm.HMAC256(user.getUserpwd());
 
        //设置过期时间为当前时间一天后
        Date nowDate = new Date();
        Date expireDate = new Date(System.currentTimeMillis() + EXPIRE_TIME);
 
        String jwt = JWT.create().withHeader(header)
                .withClaim("account",user.getAccount())//业务信息:员工号
                .withClaim("username",user.getUsername())//业务信息:员工姓名
                .withClaim("rolename",user.getRoleName())//业务信息:角色
                .withIssuer("SERVICE")//声明,签名是有谁生成 例如 服务器
                .withNotBefore(new Date())//声明,定义在什么时间之前,该jwt都是不可用的
                .withExpiresAt(expireDate)//声明, 签名过期的时间
                .sign(algorithm);//根据algorithm生成签名
 
        return jwt;
 
    }
}

3. バックエンド ログイン インターフェイスロジックは次のとおりです:

package com.lsl.exam.controller;
 
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lsl.exam.entity.TabUser;
import com.lsl.exam.entity.backresult.ResultVO;
import com.lsl.exam.service.ITabRoleService;
import com.lsl.exam.service.IUserService;
import com.lsl.exam.utils.Base64Util;
import com.lsl.exam.utils.JwtUtil;
import com.lsl.exam.utils.ResultVoUtil;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
 
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
@RestController
@RequestMapping("/exam")
public class UserController {
    private static final Logger LOG = org.slf4j.LoggerFactory.getLogger("UserController");
 
    @Autowired
    IUserService userService;
 
    @Autowired
    ITabRoleService roleService;
 
    @PostMapping(value = "login",produces = "application/json;charset=UTF-8")
    @ResponseBody
    public ResultVO<?> login(@RequestBody Map params){
        Map reuslt = new HashMap();
 
        String account = params.get("account") == null ? "" : params.get("account").toString();
        String pwd = params.get("pwd") == null ? "" : params.get("pwd").toString();
 
        if ("".equals(account) || "".equals(pwd)){
            return ResultVoUtil.error(30000,"用户名或者密码不能为空!");
        }
 
        //pwd解密
        String decodePwd = Base64Util.decode(pwd);
        if ("".contains(decodePwd)){
            return ResultVoUtil.error(30000,"密码错误!");
        }
 
        TabUser user = userService.getOne(new QueryWrapper<TabUser>()
                .eq("account",account)
                .eq("userpwd",decodePwd));
        if (null == user){
            return ResultVoUtil.error(30000,"用户名或者密码错误");
        }
        
        //获取当前用户拥有的角色
        String userId = user.getId();
        Map roleMap = new HashMap();
        roleMap.put("userId",userId);
        List<Map> roleList = roleService.qryRoleInfoByUserId(roleMap);
        List<String> roleNames = new ArrayList<>();
        for(Map role : roleList){
            roleNames.add(role.get("role").toString());
        }
        user.setRoleName(JSON.toJSONString(roleNames));
 
        //生成带有业务信息的jwt串
        String jwt = JwtUtil.creatJwt(user);
        
        //把jwt和当前用户信息返给前端
        reuslt.put("jwt",jwt);
        reuslt.put("roleNames",roleNames);
        reuslt.put("username",user.getUsername());
        reuslt.put("account",user.getAccount());
        
        return ResultVoUtil.success(reuslt);
    }
 
    @PostMapping(value = "qryUser",produces = "application/json;charset=UTF-8")
    @ResponseBody
    public Object qryUser(HttpServletRequest request){
 
        //这里header中的信息是filter中放进去的
        String account = request.getHeader("account");
        String username = request.getHeader("username");
        String rolename = request.getHeader("rolename");
 
        List<TabUser> list = userService.list();
        return ResultVoUtil.success(list);
    }
}

4. フィルターを開発し、JWT 検証を実行します

package com.lsl.exam.filter;
 
import com.alibaba.fastjson.JSON;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.lsl.exam.entity.backresult.ResultVO;
import com.lsl.exam.utils.ResultVoUtil;
import org.apache.tomcat.util.http.MimeHeaders;
import org.springframework.stereotype.Component;
 
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
/**
 * jwt校验过滤器
 */
@Component
@WebFilter(filterName = "jwtFilter",urlPatterns = {"/*"})
public class AuthJwtFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
 
    }
 
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String url = httpServletRequest.getRequestURL().toString();
        
        //配置不进行jwt校验的请求路径
        List<String> urlList = new ArrayList<>();
        urlList.add("/exam/login");
 
        boolean flag = false;
        for (String strUrl : urlList){
            if (url.contains(strUrl)){
                flag = true;
            }
        }
 
        try {
            if (!flag){
                String token = httpServletRequest.getHeader("token");
                //校验token,jwt过期有jwt自行校验,如果超时了,会执行catch里代码
                DecodedJWT decodeJwt = JWT.require(Algorithm.HMAC256("aa082c-66rt89-29sr3t-y9t7b8")).build().verify(token);
                
                //获取jwt中的业务信息
                String account = decodeJwt.getClaim("account").asString();
                String username = decodeJwt.getClaim("username").asString();
                String rolename = decodeJwt.getClaim("rolename").asString();
                Map<String, String> headerMap = new HashMap<>();
                headerMap.put("account",account);
                headerMap.put("username",username);
                headerMap.put("rolename",rolename);
                
                //把业务信息添加到request的header
                addHeader(httpServletRequest,headerMap);
 
 
//                Class<?> superclass = servletRequest.getClass().getSuperclass().getSuperclass();
//                Field requestField = superclass.getDeclaredField("request");
//                requestField.setAccessible(true);
//                RequestFacade requestFacadeInstance = (RequestFacade) requestField.get(servletRequest);
                RequestFacade requestFacadeInstance = (RequestFacade)superclass3;
//                Field requestField1 = requestFacadeInstance.getClass().getDeclaredField("request");
//                requestField1.setAccessible(true);
//                Object requestInstance =  requestField1.get(requestFacadeInstance);
//                Field coyoteRequestField = requestInstance.getClass().getDeclaredField("coyoteRequest");
//                coyoteRequestField.setAccessible(true);
//
//                Object coyoRequestInstance =  requestField1.get(requestInstance);
//                Field headersField = coyoRequestInstance.getClass().getDeclaredField("headers");
//                headersField.setAccessible(true);
//
//                MimeHeaders headers = (MimeHeaders) headersField.get(coyoRequestInstance);
//                headers.removeHeader("token");
//                headers.addValue("account").setString(account);
//                headers.addValue("username").setString(username);
//                headers.addValue("roleid").setString(roleid);
//
 
            }
        } catch (Exception e) {
            //jwt校验失败,返给前端的code=1,前端要重定向到登录页面
            PrintWriter writer = null;
            servletResponse.setCharacterEncoding("UTF-8");
            servletResponse.setContentType("text/html; charset=utf-8");
 
            try {
                writer = servletResponse.getWriter();
                ResultVO vo = ResultVoUtil.successLogout();
                String msg = JSON.toJSONString(vo);
                writer.println(msg);
            } catch (IOException ex) {
 
            } finally {
                if (writer != null){
                    writer.close();
                }
                return;
            }
 
        }
 
        filterChain.doFilter(servletRequest,servletResponse);
    }
 
    /**
     * 向request的header中放业务信息
     * @param request
     * @param headerMap
     */
    private void addHeader(HttpServletRequest request, Map<String, String> headerMap) {
        if (headerMap==null||headerMap.isEmpty()){
            return;
        }
 
        Class<? extends HttpServletRequest> c=request.getClass();
        //System.out.println(c.getName());
        System.out.println("request实现类="+c.getName());
        try{
            Field requestField=c.getDeclaredField("request");
            requestField.setAccessible(true);
 
            Object o=requestField.get(request);
            Field coyoteRequest=o.getClass().getDeclaredField("coyoteRequest");
            coyoteRequest.setAccessible(true);
 
            Object o2=coyoteRequest.get(o);
            System.out.println("coyoteRequest实现类="+o2.getClass().getName());
            Field headers=o2.getClass().getDeclaredField("headers");
            headers.setAccessible(true);
 
            MimeHeaders mimeHeaders=(MimeHeaders) headers.get(o2);
            for (Map.Entry<String,String> entry:headerMap.entrySet()){
                mimeHeaders.removeHeader(entry.getKey());
                mimeHeaders.addValue(entry.getKey()).setString(entry.getValue());
            }
 
        }catch (Exception e){
            e.printStackTrace();
        }
 
    } 
    @Override
    public void destroy() {
 
    }
}

以上がSpringboot は JWT をどのように統合して ID 認証を実現しますか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。