首先需要搭建好一个Spring Boot + Swagger2的项目
因为之前有写过 Swagger2 点我 所以这里就不再重复一遍了。该文章将会在之前项目的基础上继续添加整合Jwt。
首先放下搭建完毕的项目目录结构
pom.xml
在之前的基础上添加Jwt依赖即可
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.7.0version>
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 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 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(){{
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 param = new HashMap();
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在用户本地,也会相对不安全点,怎么取舍就要看自己的项目了。