Rest安全接口的实现(Jwt)

1、Rest接口没有认证机制带来的问题?

我们编写的Restful接口,没有任何的校验机制。就意味着,任何人都可以访问。我们如何实现,必须要通过校验才可以访问我的服务接口呢?那这些接口安全吗?

所以,我们希望对应特定的api,需要进过登录认证后才可以访问,我们可以使用TOKEN机制来实现。

2、Token实现原理

1、首先用户登录
2、登录后,可以获得服务端返回的一串唯一的字符串(令牌)
3、发送请求的时候,将令牌一起发送到服务端校验
4、服务端根据令牌判断是否是认证用户发送的数据

3、如何实现Token实现接口安全认证呢?

可以使用JWT框架,来实现TOKEN的校验。Jwt框架的作用就是用于产生TOKEN(令牌)的。

4、Jwt是什么

JWT全称为JSON Web Token,是一个令牌处理框架 。用于实现令牌校验机制的实现。

Jwt的主体:

--头部信息
{ "typ":"JWT", "alg":"HS256" }

--载荷主体
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.(生效开始时间)
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

5、测试案例

导入依赖包

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

工具类测试

public class JwtUtils {
    private static String SECRET = "mysecret";
    public static final long EXPIRE = 1000*60*60*24*7;
    //秘钥
    public static final  String APPSECRET = "onehee666";

    public static String createToken(String id) throws UnsupportedEncodingException {
 
        Map<String, Object> header = new HashMap<String, Object>();
        header.put("alg", "HS256"); //使用的算法
        header.put("type", "JWT");//使用的类型
        JwtBuilder builder = Jwts.builder();
        //设置头信息
        builder.setHeader(header);
        //设置有效信息-自定义信息使用claim
        builder.claim("id", id);
        //设置有效信息,都有固定的方法, 固定选项需要理解
        builder.setSubject("LOGIN");
        builder.setIssuedAt(new Date());
        builder.setExpiration(new Date(System.currentTimeMillis() + EXPIRE));
        //认证信息
        builder.signWith(SignatureAlgorithm.HS256, APPSECRET);

        String token = builder.compact();

        return token;
    }
    public static Claims checkJWT(String token ){

        try{
            final Claims claims =  Jwts.parser().setSigningKey(APPSECRET).
                    parseClaimsJws(token).getBody();
            return  claims;

        }catch (Exception e){ }
        return null;

    }

     public static void main(String[] args) {
        String token=null;
        try {
            //token是服务器端生成的一个加密字符串,用户每次请求都会带过来。
            token= JwtUtils.createToken("2");
            System.out.println(token);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        
        //模拟用户提交过来的token,如果校验通过是可以获取数据的
        Claims claims = checkJWT(token);
        System.out.println(claims.get("id"));
    }

}

6、使用案例

认证流程

用户访问目标资源会先进入拦截器,检查请求头有没有key为token值,并将这个值通过工具类进行校验该token是否有效,并且跟session里面保存的token作为对比是否相等,如果相等就放行进入控制类执行方法,无效就拦截下来返回错误提示。

用户访问登录请求,直接放行,让用户进入控制类执行方法,根据用户提交过来的用户名和密码进行数据库匹配,如果找到了就构建一个map再使用工具类生成一个token,存到session,并告诉浏览器端保存一个头,下次访问的时候带过来进行校验。

第一步、导入依赖

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

第二步、编写工具类

--用户生成校验token值和校验token的方法
public class JwtUtils {

    //过期时间
    public static final long EXPIRE = 1000*60*60*24*7;
    //秘钥
    public static final  String APPSECRET = "onehee666";


    /**
     * 创建令牌
     * @param
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String createToken(Map<String,Object> info) throws UnsupportedEncodingException {
        Date iatDate = new Date();
        Calendar nowTime = Calendar.getInstance();
        nowTime.add(Calendar.MINUTE, 100);
        Date expiresDate = nowTime.getTime();

        Map<String, Object> header = new HashMap<String, Object>();
        header.put("alg", "HS256");
        header.put("type", "JWT");

        JwtBuilder builder = Jwts.builder();
        //设置头信息
        builder.setHeader(header);
        //设置有效信息-自定义信息使用claim
        builder.setClaims(info);
        //设置有效信息,都有固定的方法, 固定选项需要理解
        builder.setSubject("LOGIN");
        builder.setIssuedAt(new Date());
        builder.setExpiration(new Date(System.currentTimeMillis() + EXPIRE));

        //认证信息
        builder.signWith(SignatureAlgorithm.HS256, APPSECRET);
        String token = builder.compact();
        return token;
    }

    /**
     * 校验令牌
     * @param token
     * @return
     */
    public static Claims checkJWT(String token ){

        try{
            final Claims claims =  Jwts.parser().setSigningKey(APPSECRET).
                    parseClaimsJws(token).getBody();
            return  claims;
        }catch (Exception e){ }
        return null;

    }
}

第三步、编写token拦截器

@Slf4j
public class TokenInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //第一步:通过头信息获得token
            String token = request.getHeader("token");
            log.debug("TOKEN:" + token);

        //第二步:校验是否有对应的数据
            HttpSession session = request.getSession();
            User user =(User) session.getAttribute(token);
        //第三步:校验是否有效的token
        if (user!=null) {
            log.debug("用户信息:" + user.toString());
            Claims claims = JwtUtils.checkJWT(token);
            Object username = claims.get("username");
            //第四步、校验有效就放行
            if (username.equals(user.getUserName())) {
                return true;
            }
        }

        //没有token直接返回不让访问资源
        response.getWriter().println("{\"code\":10001,\"message\":\"error\"}");
        return false;
    }
}

第四步、编写配置类的拦截器和放行资源

@Configuration
public class MvcConfiguration implements WebMvcConfigurer {

    /**
     * 将拦截器对象加入到Spring容器里面
     * @return
     */
    @Bean
    public TokenInterceptor tokenInterceptor(){
        return new TokenInterceptor();
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(this.tokenInterceptor())
                .addPathPatterns("/**")
            //放行登录请求
                .excludePathPatterns("/login");

    }

}

第五步、编写控制器login

@RestController
@Slf4j
public class UserController {

    @PostMapping(value = "/login")
    public String login(User user, HttpSession session){

        log.debug(user.toString());
        try {
            //判断用户名,密码是否正确
            if ("zhangsan".equals(user.getUserName())&&"123456".equals("123456")){
                //构建token
                Map<String,Object> info=new HashMap<>();
                info.put("id",user.getUserId());
                info.put("username",user.getUserName());
                String token = JwtUtils.createToken(info);
                //将TOKEN和数据绑定,放在会话里面
                session.setAttribute(token, user);
                //省略一步保存cookie,下次访问的时候把服务端生成的token带过来进行校验
                return token;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;

    }

    /**
     * 保存用户
     * @param user
     * @return
     */
    @PostMapping(value = "/save")
    public User save(User user){
        //每次请求save都会先进入拦截器进行校验提交过来的头信息的token是否有效
        log.debug("用户信息"+user.toString());
        user.setCreateDate(new Date());
        return user;
    }
    
}