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;
}
}