参考:security https://www.jianshu.com/p/ca4cebefd1cc
代码地址: 两个模块 jwt 和 security https://github.com/yangyu0829/Springboot-Jwt-demo
1.什么是JWT
JWT(Json Web Token),是一种工具,格式为XXXX.XXXX.XXXX的字符串,JWT以一种安全的方式在用户和服务器之间传递存放在JWT中的不敏感信息。
2.为什么要用JWT
设想这样一个场景,在我们登录一个网站之后,再把网页或者浏览器关闭,下一次打开网页的时候可能显示的还是登录的状态,不需要再次进行登录操作,通过JWT就可以实现这样一个用户认证的功能。当然使用Session可以实现这个功能,但是使用Session的同时也会增加服务器的存储压力,而JWT是将存储的压力分布到各个客户端机器上,从而减轻服务器的压力。
3.JWT长什么样子
JWT由3个子字符串组成,分别为Header,Payload以及Signature,结合JWT的格式即:Header.Payload.Signature。(Claim是描述Json的信息的一个Json,将Claim转码之后生成Payload)。
Header
Header是由以下这个格式的Json通过Base64编码(编码不是加密,是可以通过反编码的方式获取到这个原来的Json,所以JWT中存放的一般是不敏感的信息)生成的字符串,Header中存放的内容是说明编码对象是一个JWT以及使用“SHA-256”的算法进行加密(加密用于生成Signature)
{
"typ":"JWT",
"alg":"HS256"
}
Claim
Claim是一个Json,Claim中存放的内容是JWT自身的标准属性,所有的标准属性都是可选的,可以自行添加,比如:JWT的签发者、JWT的接收者、JWT的持续时间等;同时Claim中也可以存放一些自定义的属性,这个自定义的属性就是在用户认证中用于标明用户身份的一个属性,比如用户存放在数据库中的id,为了安全起见,一般不会将用户名及密码这类敏感的信息存放在Claim中。将Claim通过Base64转码之后生成的一串字符串称作Payload。
{
"iss":"Issuer —— 用于说明该JWT是由谁签发的",
"sub":"Subject —— 用于说明该JWT面向的对象",
"aud":"Audience —— 用于说明该JWT发送给的用户",
"exp":"Expiration Time —— 数字类型,说明该JWT过期的时间",
"nbf":"Not Before —— 数字类型,说明在该时间之前JWT不能被接受与处理",
"iat":"Issued At —— 数字类型,说明该JWT何时被签发",
"jti":"JWT ID —— 说明标明JWT的唯一ID",
"user-definde1":"自定义属性举例",
"user-definde2":"自定义属性举例"
}
Signature
Signature是由Header和Payload组合而成,将Header和Claim这两个Json分别使用Base64方式进行编码,生成字符串Header和Payload,然后将Header和Payload以Header.Payload的格式组合在一起形成一个字符串,然后使用上面定义好的加密算法和一个密匙(这个密匙存放在服务器上,用于进行验证)对这个字符串进行加密,形成一个新的字符串,这个字符串就是Signature。
4.JWT实现认证的原理
服务器在生成一个JWT之后会将这个JWT会以Authorization : Bearer JWT 键值对的形式存放在cookies里面发送到客户端机器,在客户端再次访问收到JWT保护的资源URL链接的时候,服务器会获取到cookies中存放的JWT信息,首先将Header进行反编码获取到加密的算法,在通过存放在服务器上的密匙对Header.Payload 这个字符串进行加密,比对JWT中的Signature和实际加密出来的结果是否一致,如果一致那么说明该JWT是合法有效的,认证成功,否则认证失败。
认证流程如下:
<dependency>
<groupId>com.auth0groupId>
<artifactId>java-jwtartifactId>
<version>3.5.0version>
dependency>
** key 和 ttl 配置从配置文件里面读取**
/**
* @author: yu
* @description:
* @create: 2020-03-31 13:20
**/
@Component
@ToString
@Slf4j
public class JwtUtil {
private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);
/**
* 密钥
*/
@Value("${jwt.config.secret}")
private String secret;
/**
* 过期时间 半小时
**/
@Value("${jwt.config.ttl}")
private long ttl;
/**
* 生成用户token,设置token超时时间
*/
public String createToken(User user) {
//过期时间
Date expireDate = new Date(System.currentTimeMillis() + ttl * 1000);
Map<String, Object> map = new HashMap<>();
map.put("alg", "HS256");
map.put("typ", "JWT");
String token = JWT.create()
// 添加头部
.withHeader(map)
//可以将基本信息放到claims中
.withClaim("id", user.getUserId())
.withClaim("name", user.getName())
//超时设置,设置过期的日期
.withExpiresAt(expireDate)
//签发时间
.withIssuedAt(new Date())
//SECRET加密
.sign(Algorithm.HMAC256(secret));
return token;
}
/**
* 校验token并解析token
*
* @return
*/
public Map<String, Claim> verifyToken(String token) throws Exception {
try {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret)).build();
DecodedJWT jwt = verifier.verify(token);
return jwt.getClaims();
} catch (TokenExpiredException e) {
log.error("token已过期");
throw new Exception("token已过期");
} catch (JWTVerificationException e) {
log.error("token不存在或不正确");
throw new Exception("token不存在或不正确");
}
}
}
jwt:
config:
secret: haha # 加密的secret
ttl: 3000 # token超时时间 秒
@Data
public class User {
private String name;
private String userId;
private String password;
}
/**
* @author: yu
* @description:
* @create: 2020-04-01 14:01
**/
@RestController
@RequestMapping("/user")
public class LoginController {
String name = "admin";
String password = "1234";
String userId = "1024";
@Autowired
private JwtUtil jwtUtil;
@PostMapping("login")
public Res login(User user) {
String token = "";
// 例子没有用数据库,直接使用写死的用户
// 判断用户密码是否正确
if (name.equals(user.getName()) && password.equals(user.getPassword())) {
token = jwtUtil.createToken(user);
} else {
return Res.fail.data("用户名或密码不正确");
}
return Res.ok.data(token);
}
/**
* 此方法必须登录才能请求,测试token 拦截器用这个
*
* @return
*/
@GetMapping("getUserByInterceptor")
public Res getUserByInterceptor(HttpServletRequest request) {
String name = (String) request.getAttribute("name");
String userId = (String) request.getAttribute("userId");
User user = new User();
user.setName(name);
user.setUserId(userId);
return Res.ok.data(user);
}
/**
* 此方法必须登录才能请求,测试token 过滤器的
*
* @return
*/
@GetMapping("getUserByFilter")
public Res getUserByFilter(String name, String userId) {
User user = new User();
user.setName(name);
user.setUserId(userId);
return Res.ok.data(user);
}
}
import lombok.Data;
import java.io.Serializable;
/**
* 封装结果集
*
* @author:yu
* @date:2019-04-23
*/
@Data
public class Res<T> implements Serializable {
private int code = 0;
private T data;
private String msg;
public static Res ok = new Res().msg("成功");
public static Res fail = new Res().code(-1).msg("失败");
public static Res build() {
return new Res();
}
public static <T> Res build(T data) {
return new Res().data(data);
}
public Res code(int code) {
this.code = code;
return this;
}
public Res data(T data) {
this.data = data;
return this;
}
public Res msg(String msg) {
this.msg = msg;
return this;
}
}
需要在启动类加 @ServletComponentScan("filter扫描包路径“)
import com.alibaba.fastjson.JSON;
import com.auth0.jwt.interfaces.Claim;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
/**
* JWT过滤器,拦截 /secure的请求
*/
@Slf4j
@WebFilter(filterName = "JwtFilter", urlPatterns = "/*")
public class JwtFilter implements Filter {
private static final Set<String> ALLOWED_PATHS = Collections.unmodifiableSet(new HashSet<>(
Arrays.asList("/user/login")));
@Autowired
private JwtUtil jwtUtil;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
ParameterRequestWrapper requestWrapper = new ParameterRequestWrapper(request);
String path = request.getRequestURI().substring(request.getContextPath().length()).replaceAll("[/]+$", "");
// 地址是否不需要拦截
boolean allowedPath = ALLOWED_PATHS.contains(path);
if (!allowedPath) {
response.setCharacterEncoding("UTF-8");
//获取 header里的token
final String token = request.getHeader("token");
try {
// 添加参数
Map<String, Claim> userClaimMap = jwtUtil.verifyToken(token);
requestWrapper.addParameter("name", userClaimMap.get("name").asString());
requestWrapper.addParameter("userId", userClaimMap.get("id").asString());
} catch (Exception e) {
response.setCharacterEncoding("utf-8");
response.getWriter().write(JSON.toJSONString(Res.fail.data(e.getMessage())));
return;
}
}
chain.doFilter(requestWrapper, res);
}
@Override
public void destroy() {
}
}
因为我们要在拦截器获取token的user数据并传给后面的处理方法,所以需要更改request的参数,以便后面我们获取。
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
/**
* request.parameter
* @author yu
* 实现一个请求包装
*/
public class ParameterRequestWrapper extends HttpServletRequestWrapper {
private Map<String, String[]> params = new HashMap<>();
/**
* Constructs a request object wrapping the given request.
*
* @param request
* @throws IllegalArgumentException if the request is null
*/
public ParameterRequestWrapper(HttpServletRequest request) {
super(request);
//将参数表,赋予给当前的Map以便于持有request中的参数
this.params.putAll(request.getParameterMap());
}
/**
* 重载构造方法
*/
public ParameterRequestWrapper(HttpServletRequest request, Map<String, Object> extendParams) {
this(request);
//这里将扩展参数写入参数表
addAllParameters(extendParams);
}
/**
* 在获取所有的参数名,必须重写此方法,否则对象中参数值映射不上
*
* @return
*/
@Override
public Enumeration<String> getParameterNames() {
return new Vector(params.keySet()).elements();
}
/**
* 重写getParameter方法
*
* @param name 参数名
* @return 返回参数值
*/
@Override
public String getParameter(String name) {
String[] values = params.get(name);
if (values == null || values.length == 0) {
return null;
}
return values[0];
}
@Override
public String[] getParameterValues(String name) {
String[] values = params.get(name);
if (values == null || values.length == 0) {
return null;
}
return values;
}
/**
* 增加多个参数
*
* @param otherParams 增加的多个参数
*/
public void addAllParameters(Map<String, Object> otherParams) {
for (Map.Entry<String, Object> entry : otherParams.entrySet()) {
addParameter(entry.getKey(), entry.getValue());
}
}
/**
* 增加参数
*
* @param name 参数名
* @param value 参数值
*/
public void addParameter(String name, Object value) {
if (value != null) {
if (value instanceof String[]) {
params.put(name, (String[]) value);
} else if (value instanceof String) {
params.put(name, new String[]{(String) value});
} else {
params.put(name, new String[]{String.valueOf(value)});
}
}
}
}
import com.alibaba.fastjson.JSON;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* @author: yu
* @description:
* @create: 2020-04-01 16:07
**/
@Component
public class SecurityInterceptor extends HandlerInterceptorAdapter {
@Autowired
private JwtUtil jwtUtil;
/**
* 调用前处理
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
String token = request.getHeader("token");
Map<String, Claim> userClaimMap = jwtUtil.verifyToken(token);
request.setAttribute("name",userClaimMap.get("name").asString());
request.setAttribute("userId",userClaimMap.get("id").asString());
return true;
}catch (Exception e ){
response.setCharacterEncoding("utf-8");
response.getWriter().write(JSON.toJSONString(Res.fail.data(e.getMessage())));
}
return false;
}
}
package com.yu.security_jwt.demo;
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.WebMvcConfigurer;
/**
* @author: yu
* @description:
* @create: 2020-04-01 16:10
**/
@Configuration // 使用interceptor 时打开
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private SecurityInterceptor securityInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(securityInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/user/login");
}
}