赞
踩
JWT,JSON Web Token,开放的、行业标准(RFC 7519),用于网络应用环境间安全传递声明。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的业务逻辑所须的声明信息。
特点:
具体来说:
JWT的工作流程
JWT的token是三段由小数点分隔组成的字符串:header.payload.signature
,即头部、载荷与签名。Base64编码各个字符串。Base64用64个字符来表示任意二进制数据的方法,常用于URL、Cookie、网页中传输少量二进制数据。
头部包含两部分:声明类型和使用的散列算法(通常直接使用HMAC SHA256,就是HS256)
{
"typ": "JWT",
"alg": "HS256"
}
将头部进行base64编码构成第一部分。
也称为JWT claims,放置需要传输的信息,有三类:
JWT规定7个官方字段,供选用:
支持定义私有字段,示例:
{
"iss": "jwt.io",
"exp": 1496199995458,
"name": "johnny",
"role": "admin","
}
JWT默认是不加密的,任何人都可以读到,所以不要把隐私敏感信息放在这个部分。
需要采用编码的header、编码的payload、secret,使用header中指定的算法进行签名。
对头部以及载荷内容进行签名。
引入类库jjwt(jwt的Java实现版):
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
JwtUtil.java
工具类,有3个工具类方法:根据用户名(一般是域账户)和用户ID生成jwt token,解析JWT token,根据登录态解析并获取该token对应的用户信息。记住,需要替换base64Security、clientId、jwtName3个变量,
package com.aaa.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;
public class JwtUtil {
private final static String base64Security = "";
private final static String clientId = "";
private final static String jwtName = "";
/**
* 过期时间,2天
*/
private final static long TTLMillis = 172800 * 1000;
/**
* 解析jwt
*/
public static Claims parseJWT(String token) {
if (StringUtils.isBlank(token) || !token.contains(".")) {
return null;
}
try {
return Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(base64Security))
.parseClaimsJws(token).getBody();
} catch (Exception e) {
return null;
}
}
public static String createJWT(String name, Integer userId) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
//生成签名密钥
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(base64Security);
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
// 添加构成JWT的参数
JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")
.claim("unique_name", name)
.claim("userid", userId)
.setIssuer(jwtName)
.setAudience(clientId)
.signWith(signatureAlgorithm, signingKey);
// 添加Token过期时间
if (TTLMillis >= 0) {
long expMillis = nowMillis + TTLMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp).setNotBefore(now);
}
// 生成JWT
return builder.compact();
}
public static Claims getUserInfo() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
final String authHeader = request.getHeader("authorization");
final String token = authHeader.substring(7);
try {
return JwtUtil.parseJWT(token);
} catch (Exception e) {
return null;
}
}
}
JWTInterceptor.java
,登录态拦截器,request为空或非法,则跳转到登录页面;随后,request解析JWT token失败,依然是跳转到登录页面,解析成功,将JWT token信息再通过request.setAttribute()
存入Request中。
package com.aaa.filter;
import com.aaa.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
final String authHeader = request.getHeader("authorization");
String url = request.getRequestURL().toString();
String[] urls = url.split("/");
String root = urls[0] + "/" + urls[1] + "/" + urls[2];
// OPTIONS方法放行
if ("OPTIONS".equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return true;
} else {
boolean checkAuth = null == authHeader || !authHeader.startsWith("Bearer") || authHeader.length() < 7;
if (checkAuth) {
response.sendRedirect(root);
return false;
}
}
final String token = authHeader.substring(7);
try {
final Claims claims = JwtUtil.parseJWT(token);
if (claims == null) {
response.sendRedirect(root);
return false;
}
request.setAttribute("CLAIMS", claims);
return true;
} catch (final Exception e) {
response.sendRedirect(root);
return false;
}
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
}
}
UserServiceImpl.java
用户登录类,先根据LDAP来校验用户名和密码信息,校验通过后,将用户的域名信息和用户ID信息生成JWT token。
package com.aaa.service.user.impl;
import com.alibaba.druid.util.StringUtils;
import com.alibaba.fastjson.JSONObject;
import com.aaa.dao.UserMapper;
import com.aaa.model.User;
import com.aaa.service.user.UserService;
import com.aaa.utils.DomainUtil;
import com.aaa.utils.JwtUtil;
import com.aaa.utils.ServiceUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public String login(JSONObject jsonObject) {
String name = (String) jsonObject.get("userName");
String pass = (String) jsonObject.get("password");
if (StringUtils.isEmpty(name) || StringUtils.isEmpty(pas)) {
return JSONObject.toJSONString(ServiceUtil.returnError("用户名或密码不能为空!"));
}
try {
User user = new User(name, "1", true);
// 手动在db里面配置新增用户
User userInfo = userMapper.selectBySelectiveFields(user);
if (userInfo == null) {
return JSONObject.toJSONString(ServiceUtil.returnError("用户名不存在!"));
}
Boolean status = DomainUtil.checkDomain("CORP\\" + name, pass);
if (status) {
String jwtToken = JwtUtil.createJWT(userInfo.getUserName(), userInfo.getId());
JSONObject data = new JSONObject();
data.put("jwtToken", jwtToken);
data.put("roleId", userMapper.getUserRole(userInfo.getId()));
return JSONObject.toJSONString(ServiceUtil.returnSuccessData(data));
} else {
return JSONObject.toJSONString(ServiceUtil.returnError("用户名或者密码错误!"));
}
} catch (Exception e) {
return JSONObject.toJSONString(ServiceUtil.returnError("系统异常,请稍后再试!"));
}
}
}
DomainUtil.checkDomain()
方法,根据LDAP协议服务来校验用户的登录认证(域账户和电脑开机密码,此密码一般情况下会依据公司的安全部门要求3个月更新一次)
/**
* 内网ldap账户认证
*/
public static Boolean checkDomain(String userName, String password) {
String url = "ldap://" + ldapIp + ":" + ldapPort;
Hashtable<String, String> env = new Hashtable<>();
javax.naming.directory.DirContext ctx;
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.PROVIDER_URL, url);
env.put(Context.SECURITY_PRINCIPAL, userName);
env.put(Context.SECURITY_CREDENTIALS, password);
try {
// 初始化上下文
ctx = new javax.naming.directory.InitialDirContext(env);
ctx.close();
// 验证成功返回name
return true;
} catch (javax.naming.AuthenticationException e) {
logger.error("认证失败:" + e.getMessage());
return false;
} catch (Exception e) {
logger.error("认证出错:" + e.getMessage());
return false;
}
}
token的认证和session认证
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。