常见的认证机制与JWT

常见的认证机制

http basic oauth

HTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和 password,

简言之,Basic Auth是配合RESTful API 使用的最简单的认证方式,只需提供 用户名密码即可,但由于有把用户名密码暴露给第三方客户端的风险,在生产环境下被 使用的越来越少

因此,在开发对外开放的RESTful API时,尽量避免采用HTTP Basic Auth

cookie oauth

在这里插入图片描述

OAuth2.0

在这里插入图片描述

token auth

防伪、业务信息、用户名、权限等都是存储在token中的
在这里插入图片描述

在这里插入图片描述

Token机制相对于Cookie机制又有什么好处呢?

  1. 支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提 是传输的用户认证信息通过HTTP头传输.

  2. 无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,因为 Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储 状态信息.

  3. 更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript, HTML,图片等),而你的服务端只要提供API即可.

  4. 去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在 你的API被调用的时候,你可以进行Token生成调用即可.

  5. 更适用于移动应用: 当你的客户端是一个原生平台(iOS, Android,Windows 8等) 时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认 证机制就会简单得多。

  6. CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防 范。

  7. 性能: 一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256 计算 的Token验证和解析要费时得多.

  8. 不需要为登录页面做特殊处理: 如果你使用Protractor 做功能测试的时候,不再需要 为登录页面做特殊处理.

  9. 基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在 多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如: Firebase,Google, Microsoft).

基于JWT的Token认证机制实现

什么是JWT

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用 户和服务器之间传递安全可靠的信息。

jwt由三个部分组成,其实就是一个字符串,三个部分组成,每个部分中间用 . 分隔

  1. 头部
    公共数据的声明,例如token的类型,签名(安全,防伪)算法的计算

头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以 被表示成一个JSON对象。

{"typ":"JWT","alg":"HS256"}
  1. 载荷
    业务信息,用户名,用户权限等
    1. 标准中注册的声明(建议但不强制使用)
      iss: jwt签发者
      sub: jwt所面向的用户
      aud: 接收jwt的一方
      exp: jwt的过期时间,这个过期时间必须要大于签发时间
      nbf: 定义在什么时间之前,该jwt都是不可用的.
      iat: jwt的签发时间
      jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
    2. 其他声明(公共声明和私有声明)
      可以添加任何信息,但不建议添加敏感信息,因为该部分在客户端可以解密。
// 定义一个payload
{"sub":"1234567890","name":"John Doe","admin":true}
  1. 签名
    防伪,保证生成的token是有效的,没有被篡改

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header(头部)
  • payload(载荷)
  • secret(加密使用的盐)

其中头部和载荷都是经过base64加密的,头部加载荷组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分

将这三部分用.连接成一个完整的字符串,构成了最终的jwt.

注意:
secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用 来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流 露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

JJWT

JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界 面,隐藏了它的大部分复杂性。

依赖

<dependency> 
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt</artifactId> 
	<version>0.6.0</version> 
</dependency>

创建及解析jwt

package test;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.Test;

import java.util.*;

public class JjwtTest {

    //使用jjwt创建jwt
    @Test
    public void test() {
        //jwt不能再服务器端注销token 一旦token被其他人拿到,其他人就可以用我的身份操作

        //一般情况,都会对jwt进行失效时间的设置
        //获取当前时间
        long now = System.currentTimeMillis();
        //创建失效时间,一分钟后失效
        Date exp = new Date(now + 60000l);


        JwtBuilder jwtBuilder = Jwts.builder().setId("123456")//jti唯一标识符
                .setIssuedAt(new Date())//iat:jwt签发时间
                .claim("username", "zhangsan")//存放自定义业务信息
                .claim("roles", "admin")
                .setExpiration(exp)//设置失效时间
                .signWith(SignatureAlgorithm.HS256, "itcast");//第一个参数是签名的算法,第二个参数是secret(盐)

        //生成jwt
        String jwt = jwtBuilder.compact();

        System.out.println(jwt);
    }

    //解析jwt
    @Test
    public void test2() {
        //String jwt = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMjM0NTYiLCJpYXQiOjE1NTUzMjAzMDl9.KCxQ8yTu76Gfnq8C5AXZWwsKZBc2mdujAtShL0i4KgE";
        //String jwt = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMjM0NTYiLCJpYXQiOjE1NTUzMjA2MDcsInVzZXJuYW1lIjoiemhhbmdzYW4iLCJyb2xlcyI6ImFkbWluIn0.bNWruRo93J-X5xPEa9KYpiniNeIFp5biHb095p0GiAk";
        String jwt = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMjM0NTYiLCJpYXQiOjE1NTUzMjA4MjUsInVzZXJuYW1lIjoiemhhbmdzYW4iLCJyb2xlcyI6ImFkbWluIiwiZXhwIjoxNTU1MzIwODg1fQ.kwMSiF6dH0tmKuNYpHMWp7notIjEwb1EtC1Jgg0gIWU";

        Claims claims = Jwts.parser().setSigningKey("itcast").parseClaimsJws(jwt).getBody();

        //https://note.youdao.com/ynoteshare1/index.html?id=d408c44d96f38ef0034872868d68189e&type=note#/
        //Map<Integer, List> map = new HashMap<>();
        //for (int i = 1; i < 10; i++) {
        //    map.put(i, new ArrayList());
        //}
        //map.get(3).add("");
        //
        //for (Map.Entry<Integer, List> entry : map.entrySet()) {
        //    if(entry.getKey()==...)
        //    for (Object o : entry.getValue()) {
        //
        //    }
        //}

        System.out.println("jti:" + claims.getId());
        System.out.println("iat:" + claims.getIssuedAt());
        System.out.println("username:" + claims.get("username"));
        System.out.println("roles:" + claims.get("roles"));
        System.out.println("exp:" + claims.getExpiration());
    }
}

jjwt工具类

package util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * Created by Administrator on 2018/4/11.
 */
@ConfigurationProperties("jwt.config")
public class JwtUtil {

    private String key ;

    private long ttl ;//一个小时

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public long getTtl() {
        return ttl;
    }

    public void setTtl(long ttl) {
        this.ttl = ttl;
    }

    /**
     * 生成JWT
     *
     * @param id 唯一标识符
     * @param subject 用户信息
     * @param 用户角色
     * @return
     */
    public String createJWT(String id, String subject, String roles) {
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        JwtBuilder builder = Jwts.builder().setId(id)
                .setSubject(subject)
                .setIssuedAt(now)
                .signWith(SignatureAlgorithm.HS256, key).claim("roles", roles);
        if (ttl > 0) {
            builder.setExpiration( new Date( nowMillis + ttl));
        }
        return builder.compact();
    }

    /**
     * 解析JWT
     * @param jwtStr
     * @return
     */
    public Claims parseJWT(String jwtStr){
        return  Jwts.parser()
                .setSigningKey(key)
                .parseClaimsJws(jwtStr)
                .getBody();
    }

}

springmvc拦截器将进行token传递

在拦截之后通过request将token传递给controller层,具体实现代码是下面那个

package com.tensquare.user.intercept;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

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

@Component
public class MyIntercept implements HandlerInterceptor {

    //之前,进入Controller方法之前执行
    //返回false表示拦截,不会再执行后面的Controller方法
    //返回true表示放行,继续执行后面的Controller方法
    //使用场景:登录拦截,权限处理
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("拦截器的preHandle");
        return true;
    }

    //之中,进入Controller,执行方法逻辑,但是在返回ModelAndView结果之前执行
    //使用场景: 存放公共数据,例如统一存放双十一活动数据
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("拦截器的postHandle");

    }

    //之后,进入Controller,执行方法逻辑,返回ModelAndView结果之后,最后执行
    //使用场景: 日志记录 ,异常处理
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("拦截器的afterCompletion");
    }
}

拦截器开放登录路径

不能所有方法都调用 jwt 来验证一遍权限

package com.tensquare.user.config;

import com.tensquare.user.intercept.MyIntercept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {

    @Autowired
    private MyIntercept myIntercept;

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myIntercept)
                .addPathPatterns("/**")//设置哪些路径被拦截器拦截,在进入请求路径之前,拦截器的三个方法都会走一遍
                .excludePathPatterns("/**/login");//登录请求不被拦截
    }
}

拦截器在进入controller前获取jwt token

@Component 
public class JwtFilter extends HandlerInterceptorAdapter 
	{ 
		@Autowired private JwtUtil jwtUtil; 

		@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception 
	 		{ 
	 			System.out.println("经过了拦截器"); 
	 			
	 			final String authHeader = request.getHeader("Authorization"); 
	 			
	 			if (authHeader != null && authHeader.startsWith("Bearer ")) 
	 				{ 
	 					final String token = authHeader.substring(7); // The part after "Bearer " 
	 					Claims claims = jwtUtil.parseJWT(token); 
						if (claims != null) 
							{ 
								if("admin".equals(claims.get("roles")))
									{//如果是管理员 
									// 这个方法是将属性设置到request中,以便controller层获取,还有另一种方式是通过ThreadLocal在下面										
									request.setAttribute("admin_claims", claims); }
									if("user".equals(claims.get("roles"))){//如果是用户 	
										request.setAttribute("user_claims", claims); } } }
											return true; } }

controller获取token

/*** 删除 
* @param id 
*/ 
@RequestMapping(value="/{id}",method= RequestMethod.DELETE) 
public Result delete(@PathVariable String id ){ 
	Claims claims=(Claims) request.getAttribute("admin_claims"); 
	if(claims==null)
		{ 
			return new Result(true,StatusCode.ACCESSRROR,"无权访问"); 
			}
			userService.deleteById(id); 
			return new Result(true,StatusCode.OK,"删除成功"); }

通过ThreadLocal传递token

package com.tensquare.user.util;

import io.jsonwebtoken.Claims;

public class ThreadLocalUtil {

    private static ThreadLocal<Claims> threadLocal = new ThreadLocal<>();

    public static void set(Claims claims) {
        threadLocal.set(claims);
    }

    public static Claims get() {
        Claims claims = threadLocal.get();
        return claims;
    }

    public static void clean() {
        threadLocal.remove();
    }
}

@Component
public class MyIntercept implements HandlerInterceptor {

    @Autowired
    private JwtUtil jwtUtil;

    //之前,进入Controller方法之前执行
    //返回false表示拦截,不会再执行后面的Controller方法
    //返回true表示放心,继续执行后面的Controller方法
    //使用场景:登录拦截,权限处理
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //System.out.println("拦截器的preHandle");
        //拦截器只是对token进行解析,没有进行相关的业务处理,因为业务是各种各样

        //获取到token,从请求头Authorization中获取数据
        String header = request.getHeader("Authorization");

        //判断获取到的数据是否为空
        if (header == null || "".equals(header)) {
            //如果为空表示没有token
            //不用传递claims,直接返回true,放行即可
            return true;
        }


        //判断数据是否Bearer 开头
        if (!header.startsWith("Bearer ")) {
            //如果不是要求的数据开头,token不合法
            //不用传递claims,直接返回true,放行即可
            return true;
        }

        try {
            //截取token内容,从7开始的字符串
            String token = header.substring(7);

            //使用jwt工具类,对token进行解析,获取claims
            Claims claims = jwtUtil.parseJWT(token);

            //具体的业务是多种多样的,可能需要用户id,用户名字,用户权限...
            //没有必要一定在拦截器中进行处理,直接返回claims即可
            //把claims传递给Controller方法
            //方法一:使用request把claims传递给Controller
            //request.setAttribute("claims", claims);

            //方法二:可以把claims放到线程中,进行传递,ThreadLocal
            ThreadLocalUtil.set(claims);

        } catch (Exception e) {
            e.printStackTrace();
        }


        //例如查看文章不需要身份认证,所全部放行,返回true
        return true;
    }

乱世之中,带兵的人说话就是王法。
饿时吃糠甜如蜜,饱时吃蜜都不甜。

鬼吹灯
天下霸唱

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值