天天看點

Springboot--整合jwt實作token令牌認證

JWT請求的流程:

1.使用者輸入賬号密碼登入,前端傳輸給後端認證

2.認證成功後,後端用jwt建立一個token令牌

3.後端傳給前端token令牌,前端把這個令牌儲存下來

4.前端每次通路後端資源的時候都要帶上之前儲存的token令牌給後端認證

5.驗證成功後傳回資源給前端

JWT組成

JWT有3部分組成:

Header(頭部):兩部分組成,令牌的中繼資料,簽名和/或加密算法的類型

Payload(載體):JWT中的一些資料比如簽發者、過期時間,簽發時間之類

Signature(簽名):由base64對header部分編碼後加上.和base64對Payload編碼後組成的字元串,再用一個密鑰sercet對組合成的字元串用由頭部聲明的加密方式加密組成

一個JWT如下:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxIiwiZXhwIjoxNTc4MzA0NzI2LCJpYXQiOjE1NzgzMDExMjZ9.Vvh49ThZlFNsoTVj09QA4Wb51s7-Jk1-LyQHPuVpZCA

Springboot中整合JWT

先在 pom.xml 中引入jar包

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

然後建立兩個注解 PassToken(有此注解的接口不需要驗證Token)和UserLoginToken(有此注解的接口需要驗證Token)

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
    boolean required() default true;
}
           
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
    boolean required() default true;
}
           

建立一個token工具類,我在後面的token建立中Payload中隻放了使用者的ID,TOKEN建立時間,有效時間。是以在這個工具類中寫了個擷取使用者ID的方法。

TokenUtil.java

public class TokenUtil {
	//從token中拿到使用者的ID
    public static String getTokenUserId() {
        String token = getRequest().getHeader("token");// 從 http 請求頭中取出 token
        String userId = JWT.decode(token).getAudience().get(0);
        return userId;
    }

    /**
     * 擷取request
     *
     * @return
     */
    public static HttpServletRequest getRequest() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        return requestAttributes == null ? null : requestAttributes.getRequest();
    }
}
           

建立一個TokenService 來建立Token,也就是頒發Token。

TokenService.java

@Service
public class TokenService {
	//頒發Token
    public String getToken(User user){
        Date start = new Date();
        long current=System.currentTimeMillis()+60*60*1000;     //1小時有效期
        Date end=new Date(current);

        String token="";
        
        token= JWT.create().withAudience(user.getUserid()).withIssuedAt(start).withExpiresAt(end)
                //這裡用了HMAC加密,密鑰是使用者的密碼
                .sign(Algorithm.HMAC256(user.getPassword()));
        return token;
    }
}
           

然後建立一個攔截器,這個攔截器攔截所有請求,然後判斷請求的接口有沒有上面自定義的注解@UserLoginToken 注解,有該注解表示需要Token,若是遇到@PassToken 注解 則表示不需要Token,直接放行。

AuthenticationInterceptor.java

public class AuthenticationInterceptor implements HandlerInterceptor {
    @Autowired
    UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
        String token = httpServletRequest.getHeader("token");

        if (!(object instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) object;
        Method method = handlerMethod.getMethod();
        //判斷是否有注解PassToken
        if (method.isAnnotationPresent(PassToken.class)) {
            PassToken passtoken = method.getAnnotation(PassToken.class);
            if (passtoken.required())
                return true;
        }
		//判斷是否有注解UserLoginToken
        if (method.isAnnotationPresent(UserLoginToken.class)) {
            UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
			
            if (userLoginToken.required()) {
                if (token == null)
                    throw new RuntimeException("無token,請重新登入");
                String userid;
                try {
                    userid = JWT.decode(token).getAudience().get(0);
                    boolean time= JWT.decode(token).getExpiresAt().before(new Date());
                } catch (JWTDecodeException j) {
                    throw new RuntimeException("401");
                }
                //此處通過使用者ID獲得使用者
                User user = userService.selectByPrimaryKey(userid);
                if (user == null)
                    throw new RuntimeException("使用者不存在,請重新登入");
                JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
                try {
                    jwtVerifier.verify(token);
                } catch (Exception j) {
                    throw new RuntimeException("401");
                }
                return true;
            }
        }
        return true;
    }

    @Override
        public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
    }
}
           

最後需要把我們剛才建立的攔截器進行注冊。

InterceptorConfig.java

@Configuration
public class InterceptorConfig  implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationInterceptor())
                .addPathPatterns("/**");    // 攔截所有請求,通過判斷是否有 @UserLoginToken注解 決定是否需要登入
    }
    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();// 自己寫的攔截器
    }
}

           

至此,Springboot整合jwt實作token的過程完成。

接下來要進行測試,建立一個測試用的User Bean

User.java

public class User {

    private String userid;


    private String account;


    private String password;
	//省略get和set
}
           

controller層建立測試api

TestController.java

@Controller
public class TestController {
    @Autowired
    TokenService tokenService;

    @RequestMapping(value = "/login/{account}/{password}" ,method = RequestMethod.GET)
    @ResponseBody
    public String login(HttpServletResponse response, @PathVariable String account, @PathVariable String password){
        User userForBase = new User();
        userForBase.setUserid("1");
        userForBase.setPassword("123");
        userForBase.setAccount("qwe");

        if (!userForBase.getPassword().equals(password)) {
            return "登入失敗,密碼錯誤";
        } else {
            String token = tokenService.getToken(userForBase);
            //建立一個名為token的cookie,供前端調用
            Cookie cookie = new Cookie("token", token);
            cookie.setPath("/");
            response.addCookie(cookie);
            return token;
        }
    }

    @UserLoginToken
    @RequestMapping("/verify")
    @ResponseBody
    public String verify() {
        // 取出token中帶的使用者id 進行操作
        System.out.println(TokenUtil.getTokenUserId());
        return "你已認證驗證";
    }
}

           

使用postmen來測試

Springboot--整合jwt實作token令牌認證

下面傳回的内容就是Token

這是傳回的cookie

Springboot--整合jwt實作token令牌認證

然後請求 localhost:8080/verify 接口 帶上前面傳回的token參數

Springboot--整合jwt實作token令牌認證
Springboot--整合jwt實作token令牌認證

如上圖 表示帶上的token驗證通過,且在控制台輸出了使用者的ID