1 Jwt组成
一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。
2.1 头部(Header)
头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以
被表示成一个JSON对象。
{“typ”:“JWT”,“alg”:“HS256”}
在头部指明了签名算法是HS256算法。 我们进行BASE64编
码http://base64.xpcha.com/,编码后的字符串如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2
的6次方等于64,所以每6个比特为一个单元,对应某个可打印字符。三个字节有24
个比特,对应于4个Base64单元,即3个字节需要用4个可打印字符来表示。JDK 中
提供了非常方便的 BASE64Encoder 和 BASE64Decoder,用它们可以非常方便的
完成基于 BASE64 的编码和解码
2.2 载荷(playload)
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分
(1)标准中注册的声明(建议但不强制使用)
(2)公共的声明
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.
但不建议添加敏感信息,因为该部分在客户端可解密.
(3)私有的声明
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64
是对称解密的,意味着该部分信息可以归类为明文信息。
定义一个payload:
{"sub":"1234567890","name":"John Doe","admin":true}
然后将其进行base64编码,得到Jwt的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
2.3 签证(signature)
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
header (base64后的)
payload (base64后的)
secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符
串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第
三部分。
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6I
kpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7Hg
Q
注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用
来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流
露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
3 token工具类
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.2</version>
</dependency>
编写token工具java代码
public class TokenUtils {
//设置过期时间
private static final long EXPIRE_DATE=30*60*100000;
//token秘钥
private static final String TOKEN_SECRET = "qianggezainaliwyywyasdaDQWCWQ%5ASAACACAssda";
/**
* 生成token
* @param username
* @param name
* @param phone
* @param url
* @param address
* @return
*/
public static String token (String name,String phone,String image){
String token = "";
try {
//过期时间
Date date = new Date(System.currentTimeMillis()+EXPIRE_DATE);
//秘钥及加密算法
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
//设置头部信息
Map<String,Object> header = new HashMap<>();
header.put("typ","JWT");
header.put("alg","HS256");
//携带username,password信息,生成签名
token = JWT.create()
.withHeader(header)
.withClaim("name",name)
.withClaim("phone",phone)
.withClaim("image",image)
.withExpiresAt(date)
.sign(algorithm);
}catch (Exception e){
e.printStackTrace();
return null;
}
return token;
}
/**
* 校验
* @param token
* @return
*/
public static boolean verify(String token){
/**
* @desc 验证token,通过返回true
* @params [token]需要校验的串
**/
try {
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
public static void main(String[] args) {
String token = token("wyy","110","12321");
//System.out.println(token);
//eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbWFnZSI6IjEyMzIxIiwicGhvbmUiOiIxMTAiLCJuYW1lIjoid3l5IiwiZXhwIjoxNjA3MjI3MDcyfQ.hsw2M6tHUAJXKWv0xNNB9iOK-_sLnanfcGYP29NoS_4
boolean b = verify("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbWFnZSI6IjEyMzIxIiwicGhvbmUiOiIxMTAiLCJuYW1lIjoid3l5IiwiZXhwIjoxNjA3MjI3MDcyfQ.hsw2M6tHUAJXKWv0xNNB9iOK-_sLnanfcGYP29NoS_4");
System.out.println(b);
}
}
4 .登录业务代码
service登入业务代码
@Override
public String login(HttpServletRequest request, String phone, String code) throws CustomerException {
if( request.getSession().getAttribute("employee")!=null){
throw new CustomerException("用户已经登录!");
}
Employee employee=employeeDao.findEmployeeByPhone(phone);
if (employee==null) {
throw new CustomerException("账户信息不存在!");
}
//比对验证码
String serverCode= (String) request.getSession().getAttribute("code");
if(serverCode==null){
throw new CustomerException("验证码失效!");
}
if(!serverCode.equals(code)){
throw new CustomerException("验证码输入错误!");
}
//服务器生成jwt令牌 重点在这!!!
> String token = TokenUtils.token(employee.getName(),
> employee.getPhone(), employee.getImage());
>
>
> return token;
}
}
controller层处理登入的代码
//处理登录
private void to(HttpServletRequest req, HttpServletResponse resp) {
Result result=null;
try {
String code = req.getParameter("code");
String phone = req.getParameter("phone");
//TODO 校验
//重点部分!!!!
> String token = employeeService.login(req, phone, code);
> result = new Result(0, "", null, token);
}
catch (CustomerException e){
result = new Result(500002, e.getMessage(), null, null);
}catch (Exception e){
result = new Result(50000, "系统开小差了 !", null, null);
}
ResponseUtils.responseJson(JsonUtils.objectToJson(result), resp);
}
5 认证代码
编写当前线程的工具类
public class UserThreadLocal {
private static ThreadLocal<Map> tl=new ThreadLocal<Map>();
/**
* 绑定用户信息到当前线程
* @param map
*/
public static void setUser(Map map){
tl.set(map);
}
/**
* 获取当前线程绑定的用户信息
* @return
*/
public static Map getUser(){
return tl.get();
}
/**
* 移除当前线程绑定的用户信息
*/
public static void removeUser(){
tl.remove();
}
}
过滤器代码
@WebFilter(filterName="loginFilter",value = "/*")
public class LoginFilter implements Filter {
@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;
//登录相关接口放行
//获取客户端请求的资源路径
String uri = request.getRequestURI();
System.out.println(uri+"-------------");
//不拦截的资源
if(uri.contains("/crm/login")){
//直接访问 放行
chain.doFilter(request,response);
return;
}
//!!!!!!!!!! 重点 jwt认证代码
> //必须要认证的资源
> //先从请求参数获取token
> String token=request.getParameter("token");
> if(token==null||token.trim().equals("")){
> token=request.getHeader("token");
> if(token==null||token.trim().equals("")) {
> Result result = new Result(50003, "用户没有登录,不允许访问!", null, null);
> ResponseUtils.responseJson(JsonUtils.objectToJson(result), response);
> return;
> }
> }
>
> //校验jwt令牌是否合法,是否过期
> if(!TokenUtils.verify(token)){
> //token不合法
> Result result = new Result(50003, "用户没有登录,不允许访问!", null, null);
> ResponseUtils.responseJson(JsonUtils.objectToJson(result), response);
> return;
> }
>
>
> //后面在servlet的业务代码中需要获取当前用户的登录信息?
> //解密载荷的信息
> DecodedJWT jwt = JWT.decode(token);
> Map<String, Claim> claims = jwt.getClaims();//获取载荷信息 用户登录的信息
>
> //绑定到当前线程
> UserThreadLocal.setUser(claims);
>
>
> //放行
> chain.doFilter(request,response);
>
>
> //移除当前线程绑定的用户信息
> UserThreadLocal.removeUser();
}
@Override
public void destroy() {
}
}
如何在业务中获取当前的登录信息?
public class JobServiceImpl implements JobService {
private JobDao jobDao=new JobDaoImpl();
@Override
public QueryResult findJobByPage(QueryInfo queryInfo) {
//!!!!重点 获取当前线程的代码
> Map user = UserThreadLocal.getUser();
> System.out.println(user+"------------------");
return jobDao.findJobByPage(queryInfo.getStartIndex(),queryInfo.getLimit());
}
}
6 前端登录处理
前端登录成功后把token设置到sessionStorage
// 进行登录操作
form.on('submit(login)', function (data) {
data = data.field;
$.ajax({
type: "POST",
url: "http://www.j236.com/crm/login?method=to",
data: data ,
success: function(res){
if(res.code==0){
//前端登录成功后把token设置到sessionStorage
sessionStorage.setItem("token",res.data)
layer.msg('登录成功', function () {
window.location = '../index.html';
});
}else{
layer.msg(res.msg);
}
}
});
return false;
});
前端在除了登录外的所有ajax请求都携带token给后端
$.ajax({
type: "POST",
url: "http://www.j236.com/crm/job?method=add",
data: filed ,
beforeSend: function (XMLHttpRequest) {
//前端携带的token信息
XMLHttpRequest.setRequestHeader("token", sessionStorage.token);
},
success: function(obj){
}
});