JWT+SpringBoot+SpringMVC參數解析器

git地址: github.com/cladecs/JWT…java

簡介

你們之前都使用過session存儲信息,有的交給容器建立,有的存儲到mysql或者redis,此次項目用到了JWT,咱們把用戶的信息和登陸的過時時間都封裝到一個token字符串裏,客戶端每次請求只須要在頭信息裏攜帶token便可,話很少說,下面是目錄結構.mysql

一.annonation註解

package com.demo.annotation;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IgnoreLogin {
}

複製代碼

該註解主要做用是過濾掉請求攔截器,使用該註解就不會對該請求進行攔截(權限校驗),具體使用下面講.git

package com.demo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 登陸用戶信息
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {}
複製代碼

該註解做用是SpringMVC參數解析器,相似於RequestBody註解(但願你們瞭解springmvc的參數解析機制),和咱們後面的resolver相關聯.github

二.bean實體類

package com.demo.bean;

public class User {
    private long userId;
    private String userName;
    private String password;
    忽略get/set
}

複製代碼

咱們的用戶信息web

package com.demo.bean;

public class Business {
    private String str;
    private int num;
    忽略get/set
}

複製代碼

咱們的業務參數redis

三.config配置信息

package com.demo.config;

import com.demo.interceptor.AuthorizationInterceptor;
import com.demo.resolver.UserArgumentResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

/**
 * MVC配置
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private AuthorizationInterceptor authorizationInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authorizationInterceptor).addPathPatterns("/**");
        //注入咱們自定義的攔截器,攔截全部請求
    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new UserArgumentResolver());
        //注入咱們的用戶參數解析器
    }
}
複製代碼

四.controller

package com.demo.controller;

import com.demo.annotation.IgnoreLogin;
import com.demo.annotation.LoginUser;
import com.demo.bean.Business;
import com.demo.bean.User;
import com.demo.util.JwtUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;


@RestController
public class UserController {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private JwtUtils jwtUtils;

    @PostMapping(value = "/login")
    @IgnoreLogin
    public String login() {
        //在此 咱們不作登陸檢驗 假設檢驗成功

        User user = new User();
        user.setUserId(9527);
        user.setUserName("小星星");
        return jwtUtils.generateToken(user);//這裏只是爲了測試只返回token,(請求不含IgnoreLogin註解時須要將token放在頭信息裏)
    }

    @PostMapping("/business")
    public User business(@RequestBody Business business, @LoginUser User user) {//在業務邏輯能夠使用註解將咱們的user注入進來
        logger.info("用戶信息參數id:{},姓名:{}", user.getUserId(), user.getUserName());
        logger.info("咱們的業務參數:{},{}", business.getStr(), business.getNum());
        return user;
    }
}

複製代碼

能夠看到當咱們登錄成功後咱們能夠生成一個token字符串返回給客戶端,這個字符串包含了用戶信息和時間信息(jwt機制),同時咱們作了一個模仿業務的請求,business是咱們的業務參數,user是咱們根據客戶端上發的token解析出來的,下面會講到如何解析.能夠看到只要咱們須要user的參數,咱們就能夠直接使用LoginUser註解和User就能夠直接獲得,很是方便,客戶端並不須要將咱們的用戶信息參雜到咱們的業務參數中.相對安全。spring

五.exception

package com.demo.exception;

public class RRException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    private String msg;
    private int code = 500;

}

複製代碼

這裏我就不解析了,根據需求能夠和客戶端協商相應的錯誤碼sql

六.interceptor攔截器

package com.demo.interceptor;

import com.demo.annotation.IgnoreLogin;
import com.demo.exception.RRException;
import com.demo.util.JwtUtils;
import io.jsonwebtoken.Claims;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

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

/**
 * 權限(Token)驗證
 */
@Component
public class AuthorizationInterceptor extends HandlerInterceptorAdapter {

    @Autowired
    private JwtUtils jwtUtils;

    public static final String USER_KEY = "user";

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if (!(handler instanceof HandlerMethod) || ((HandlerMethod) handler).
                getMethodAnnotation(IgnoreLogin.class) != null) {
            //若是不是HandlerMethod或者忽略登陸
            logger.info("無需token校驗,handler:{}", handler);
            return true;
        }

        //獲取用戶憑證
        String token = request.getHeader(jwtUtils.getHeader());
        if (StringUtils.isBlank(token)) {
            token = request.getParameter(jwtUtils.getHeader());
        }

        //憑證爲空
        if (StringUtils.isBlank(token)) {
            throw new RRException(jwtUtils.getHeader() + "不能爲空", HttpStatus.UNAUTHORIZED.value());
        }

        Claims claims = jwtUtils.getClaimByToken(token);
        if (claims == null || jwtUtils.isTokenExpired(claims.getExpiration())) {
            throw new RRException(jwtUtils.getHeader() + "失效,請從新登陸", HttpStatus.UNAUTHORIZED.value());
        }

        //設置userId到request裏,後續根據userId,獲取用戶信息
        request.setAttribute(USER_KEY, jwtUtils.getUser(claims));
        return true;
    }
}

複製代碼

咱們會過濾掉不是HandlerMethod的請求和帶有IgnoreLogin的註解(並非全部方法都須要校驗,例如登陸請求,支付回調請求),咱們會取出客戶端發出的token,解析出來並判斷是否過時,沒有token或者已過時咱們能夠須要返回一個錯誤碼給客戶端而後從新登陸,當咱們校驗成功後咱們會取出用戶信息放入到request裏(後面會在參數解析器裏解析出來),這也是這個攔截器的精髓,既能校驗又能獲取用戶的信息.apache

七.resolver參數解析器

package com.demo.resolver;

import com.demo.annotation.LoginUser;
import com.demo.bean.User;
import com.demo.interceptor.AuthorizationInterceptor;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.http.HttpServletRequest;

/**
 * 用戶參數解析器
 */
public class UserArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(LoginUser.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        User user = (User) request.getAttribute(AuthorizationInterceptor.USER_KEY);
        return user;
    }
}

複製代碼

springmvc的參數解析器,須要繼承HandlerMethodArgumentResolver,有兩個方法,第一個就是支持什麼類型的參數,能夠看到咱們支持擁有LoginUser註解的參數,第二個方法是從request裏取出咱們在攔截器中放入的user並返回,這樣就實現了user對象的注入.json

八.JwtUtils

package com.demo.util;

import com.alibaba.fastjson.JSONObject;
import com.demo.bean.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * jwt工具類
 */
@Component
@ConfigurationProperties(prefix = "jwt")
public class JwtUtils {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private long   expire;
    private String secret;
    private String header;

    /**
     * 生成jwt token
     */
    public String generateToken(User user) {
        Date nowDate = new Date();

        //過時時間
        Date expireDate = new Date(nowDate.getTime() + expire * 1000);
        return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setSubject(JSONObject.toJSONString(user))
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /**
     * 解析出來claim
     * @param token
     * @return
     */
    public Claims getClaimByToken(String token) {
        try {
            return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            logger.debug("validate is token error ", e);
            return null;
        }
    }

    /**
     * 獲得user
     * @param claims
     * @return
     */
    public User getUser(Claims claims) {
        return JSONObject.parseObject(claims.getSubject(), User.class);
    }

    /**
     * token是否過時
     * @return true:過時
     */
    public boolean isTokenExpired(Date expiration) {
        return expiration.before(new Date());
    }

    public String getSecret() {
        return secret;
    }

    public void setSecret(String secret) {
        this.secret = secret;
    }

    public long getExpire() {
        return expire;
    }

    public void setExpire(long expire) {
        this.expire = expire;
    }

    public String getHeader() {
        return header;
    }

    public void setHeader(String header) {
        this.header = header;
    }
}

複製代碼

expire過時時間,secret密鑰,header頭信息名稱 這些數據在application.yml裏,這裏咱們會根據User對象生成一個token字符串,根據token取出claims對象,這裏就包含了咱們的過時時間和以前咱們所存的user信息.

九.springboot啓動和yml參數

package com.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootStart {

    public static void main(String[] agrs) {
        SpringApplication.run(SpringBootStart.class, agrs);
    }
}

application.yml
jwt:
    #加密祕鑰
    secret: f4e2e5203fg45sf45g4de581c0f9eb5
    #token,單位秒
    expire: 6000
    header: token


複製代碼

十.總結

代碼隨少,五臟俱全,在這裏咱們梳理一下流程.

1:用戶上發登陸請求,咱們會返回一個token(包含校驗信息和用戶信息).

2:客戶端上發請求須要攜帶token到頭信息裏服務器須要驗證.

3:服務器攔截請求取出token並進行解析,把user信息存入到request中.

4:在springmvc咱們增長了參數解析器,將user從request取出並返回,這時候就完成了參數的解析和注入.

5:能夠在controller邏輯代碼使用咱們的user對象了.

第一次寫,表達不清楚望你們見諒...

git地址: github.com/cladecs/JWT…

相關文章
相關標籤/搜索