Spring Boot 使用 Jwt + Swagger2 搭建带身份验证的接口服务

首先需要搭建好一个Spring Boot + Swagger2的项目
因为之前有写过 Swagger2 点我 所以这里就不再重复一遍了。该文章将会在之前项目的基础上继续添加整合Jwt。

首先放下搭建完毕的项目目录结构

这里写图片描述

pom.xml
在之前的基础上添加Jwt依赖即可

<!--jwt-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.7.0</version>
</dependency>

添加JwtAuthenticationFilter.java文件
如名字,这是一个拦截器,当用户访问接口时将会被该拦截器拦截,我们可以通过修改isProtectedUrl函数中的范围来确定需要拦截的范围。
然后因为Jwt默认是用储存在header中的验证码来验证身份的,这对于Swagger2来说很难进行测试,需要对Swagger2进行修改,比较麻烦,所以这里我直接修改成了从Body中取,当然同理也可以替换成cookie,session中,都行。

package com.my.swagger2.filter;

import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import com.my.swagger2.util.JwtUtil;

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

import java.io.IOException;

public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private static final PathMatcher pathMatcher = new AntPathMatcher();

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //解决跨域问题
        response.setHeader("Access-Control-Allow-Origin", "*");
        try {
            if(isProtectedUrl(request)) {
                //因为jwt用来验证身份的验证码是储存在header中的,而用swagger2时header中的值不好设置,需要进行修改,因此这里改成了从body中获取
                //String token = request.getHeader("Authorization");
                String token = request.getParameter("Authorization");
                //检查jwt令牌, 如果令牌不合法或者过期, 里面会直接抛出异常, 下面的catch部分会直接返回
                JwtUtil.validateToken(token);
            }
        } catch (Exception e) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
            return;
        }
        //如果jwt令牌通过了检测, 那么就把request传递给后面的RESTful api
        filterChain.doFilter(request, response);
    }


    //我们只对地址 /api 开头的api检查jwt. 不然的话登录/login也需要jwt
    private boolean isProtectedUrl(HttpServletRequest request) {
        return pathMatcher.match("/api/**", request.getServletPath());
    }

}

添加Jwt配置文件
JwtUtil.java
这里定义了Jwt验证码的格式,用到的参数等等,还有有效时间,为了测试方便,这里定的是1000小时。

package com.my.swagger2.util;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

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

public class JwtUtil {
    static final String SECRET = "ThisIsASecret";

    public static String generateToken(String username) {
        HashMap<String, Object> map = new HashMap<>();
        //you can put any data in the map
        map.put("username", username);
        String jwt = Jwts.builder()
                .setClaims(map)
                .setExpiration(new Date(System.currentTimeMillis() + 3600_000_000L))// 1000 hour
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .compact();
        return "Bearer "+jwt; //jwt前面一般都会加Bearer
    }

    public static void validateToken(String token) {
        try {
            // parse the token.
            Map<String, Object> body = Jwts.parser()
                    .setSigningKey(SECRET)
                    .parseClaimsJws(token.replace("Bearer ",""))
                    .getBody();
        }catch (Exception e){
            throw new IllegalStateException("Invalid Token. "+e.getMessage());
        }
    }
}

然后添加登入接口
loginController.java
因为这只是个测试用例,所以也没连数据库,用户密码检测就意思一下。

package com.my.swagger2.controller;

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

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

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.my.swagger2.filter.JwtAuthenticationFilter;
import com.my.swagger2.util.JwtUtil;

import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;

@RestController
@RequestMapping(value="/login")
public class loginController {

    @RequestMapping(value="check", method=RequestMethod.POST)
    @ApiOperation(value="登入身份验证(JWT验证)", notes="登入")
    @ApiImplicitParams({
        @ApiImplicitParam(name = "username",value = "用户名称",required = true,paramType = "form",dataType = "string"),
        @ApiImplicitParam(name = "password",value = "密码",required = true,paramType = "form",dataType = "string")
    })
    public Object getLoginInfo(HttpServletResponse response,HttpServletRequest request) {
        Account account = new Account();
        account.setUsername(request.getParameter("username").toString());
        account.setPassword(request.getParameter("password").toString());
        if(isValidPassword(account)) {
            String jwt = JwtUtil.generateToken(account.username);
            return new HashMap<String,String>(){{
                put("token", jwt);
            }};
        }else {
            return new ResponseEntity(HttpStatus.UNAUTHORIZED);
        }
    }

    @Bean
    public FilterRegistrationBean jwtFilter() {
        final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        JwtAuthenticationFilter filter = new JwtAuthenticationFilter();
        registrationBean.setFilter(filter);
        return registrationBean;
    }

    //密码是否正确
    private boolean isValidPassword(Account ac) {
        Map<String,Object> param = new HashMap<String,Object>();
        param.put("userName", ac.getUsername());
        param.put("password", ac.getPassword());

        return ("test".equals(param.get("userName")))&&("test@123".equals(param.get("password")));
    }


    public static class Account {
        private String username;
        private String password;
        public String getUsername() {
            return username;
        }
        public void setUsername(String username) {
            this.username = username;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
    }   
}

然后修改下之前项目中的
myTestController.java
添加一个验证码参@ApiImplicitParam(name = "Authorization",value = "验证信息",required = true,paramType = "form",dataType = "string")

@ApiOperation(value="测试数据", notes="获取字符串1+字符串2")
    @ApiImplicitParams({
      @ApiImplicitParam(name = "s1", value = "字符串1", paramType = "form", dataType = "String"),
      @ApiImplicitParam(name = "s2", value = "字符串2", paramType = "form",required = true, dataType = "String"),
      @ApiImplicitParam(name = "Authorization",value = "验证信息",required = true,paramType = "form",dataType = "string")
    })
    @RequestMapping(value="/get/info", method=RequestMethod.POST)
    public String getInfo(HttpServletResponse response,HttpServletRequest request) {
        String s1 = request.getParameter("s1");
        String s2 = request.getParameter("s2");

        String result = s1+s2;
        return result;
    }

然后启动项目测试下
可以看到一个登入接口和一个应用接口
这里写图片描述

尝试下直接使用应用接口(因为验证码设了必填,所以随便填一个值,当然,也可以设置成非必填,然后不填值,反正最后结果都是一样的)
这里写图片描述

点击查询,可以看到报错,unauthorized,被Jwt拦截下来了。
这里写图片描述

然后尝试下登入接口,填入刚才在代码中设置的用户密码,点击查询
这里写图片描述

可以看到返回了验证码,复制验证码,填到应用接口里,尝试下查询
这里写图片描述

参数如上,点击查询
这里写图片描述

成功查询到信息了
这里写图片描述

实际使用中直接去request body中取验证码会相对不安全,也挺麻烦,可以减少验证码有效时间,然后将验证码存入session,cookie,或者header中,前两种可以在验证登入时在后台填入,然后拦截器就从session,cookie中去获取验证码,这样前台也能不传验证码了,相对也会安全点,毕竟数据传输这一环是最危险的,当然cookie在用户本地,也会相对不安全点,怎么取舍就要看自己的项目了。

  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值