传统Java项目基于session的登录,以及授权校验
缺点/问题:
1. 服务端保存了登录用户信息(用户量大,存储压力大,还要考虑分布式存储问题)
2. 服务端如未持久化用户登录session信息,每次服务端系统发布 ,登录用户都需要重新登录那么JWT完美解决以上问题,常用于中大型互联网项目。
原因:JWT在服务端压根不存储登录用户的信息,完全采用算法校验这个token
什么是JWT?以及原理,这种大家百度,和b站视频去学习,本章只讲实战项目应用
比如:https://blog.csdn.net/IBLiplus/article/details/82758881
视频:【编程不良人】JWT认证原理、流程整合springboot实战应用,前后端分离认证的解决方案!_哔哩哔哩_bilibili
我们项目代码:
springMvc登录接口:
// 平台登录接口
@RequestMapping("/login")
@ApiOperation("登录验证接口")
public String login(HttpServletRequest request,HttpServletResponse response) {
try {
String loginName = request.getParameter("loginName");
String password = request.getParameter("password");
String checkPhoneCode = request.getParameter("checkPhoneCode"); //验证码
// if(StringUtil.isEmpty(loginName) || StringUtil.isEmpty(password) || StringUtil.isEmpty(checkPhoneCode)){
// return "帐号或密码或者验证码不能为空!";
// }
if(StringUtil.isEmpty(loginName) || StringUtil.isEmpty(password)){
return "帐号或密码不能为空!";
}
CrmUserInfo crmUserInfo = crmUserInfoService.getCustomerInfoByLoginName(loginName);
if(crmUserInfo == null){
return "帐号不存在!";
}else{
if(!password.equals(crmUserInfo.getPassword())){
return "密码错误";
}
}
SessionCrmUserInfo sessionCrmUserInfo = new SessionCrmUserInfo();
sessionCrmUserInfo.setId(crmUserInfo.getId());
sessionCrmUserInfo.setOrgId(crmUserInfo.getOrgId());
sessionCrmUserInfo.setUsername(crmUserInfo.getUsername());
sessionCrmUserInfo.setPersonImgUrl(crmUserInfo.getPersonImgUrl());
sessionCrmUserInfo.setName(crmUserInfo.getName());
sessionCrmUserInfo.setSex(crmUserInfo.getSex());
sessionCrmUserInfo.setCardId(crmUserInfo.getCardId());
sessionCrmUserInfo.setAge(crmUserInfo.getAge());
sessionCrmUserInfo.setPhoneNumber(crmUserInfo.getPhoneNumber());
sessionCrmUserInfo.setPosition(crmUserInfo.getPosition());
sessionCrmUserInfo.setBirthDate(crmUserInfo.getBirthDate());
sessionCrmUserInfo.setOrgName(crmUserInfo.getOrgName());
sessionCrmUserInfo.setSystemName(crmUserInfo.getSystemName());
/* 以前的登录方式基于session
// 把登陆的用户信息放到session里面去
WebMainUtil.setSessionUserInfo(sessionCrmUserInfo);
// userId传到cookie里面需要经过des加密,然后用户访问所有url的时候需要加密这个userId去核对
WebMainUtil.setSessionUserId(loginName);
//CookieUtil.setCookie(request, response,SessionAttributeName.USER_ID, AESUtil.encrypt(loginName,CookieUtil.userIdPassw), 1);
CookieUtil.setCookie(request, response, SessionAttributeName.USER_ID, loginName, 1);
*/
//生成token,存到cookie
HashMap<String, String> map = new HashMap<String, String>();
map.put(JWTUtils.Token_name_key, JSON.toJSONString(sessionCrmUserInfo));
String token = JWTUtils.getToken(map);
CookieUtil.saveCookie(response,JWTUtils.Token_name_cookie,token,JWTUtils.Cookie_path); //设置成全局可用的cookie
return "success";
} catch (Exception e) {
e.printStackTrace();
LOG.error(e.getMessage());
return "服务异常";
}
}
登录之后,拦截每个请求校验,拦截器:
@Component
@Slf4j
public class TokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("---------进入登录验证拦截器-------------");
String token = CookieUtil.getCookie(request, JWTUtils.Token_name_cookie);
System.out.println("url:" + request.getRequestURL());
if (StringUtil.isEmpty(token)) {
System.out.println("token为空");
response.sendRedirect("/login-view");
return false;
} else {
try {
JWTUtils.verify(token);
System.out.println("token验证通过");
} catch (Exception e) {
System.out.println("token验证失败");
CookieUtil.deleteCookie(request, response, "token");
response.sendRedirect("/login-view");
return false;
}
}
return true;
}
}
拦截器配置类:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry){
//项目的 根目录和test 指定重跳转到/login(如果webapp下面有了index.html则下面不生效)
/* registry.addViewController("/").setViewName("redirect:/login");
registry.addViewController("/test").setViewName("redirect:/login");
*/
//把跟目录跳转到login-view 注释的话,默认跳转根目录的index.html
// registry.addViewController("/").setViewName("redirect:/login-view");
//把login-view跳转到 试图login(login加上前缀后缀(application.propertise配置的mvc)
// 这样就跳转到了 /WEB-INF/viewsJsp/login.jsp
registry.addViewController("/login-view").setViewName("../static/start/login.html");
}
//登录拦截器JWT
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TokenInterceptor())
.addPathPatterns("/**") //拦截所有请求
.excludePathPatterns("/"
,"/appResource/**"
,"/assets/**"
,"/h5/**"
,"/start/**"
,"/staticFile/**"
,"/templ/**"
,"/favicon.ico"
,"/index.html"
,"/MP_verify_m9OeemkWw7iC1UNd.txt"
,"/*.css"
,"/index.html"
,"/login/**"
,"/login-view"); //放行静态资源
}
}
从token里面获取该登录用户的信息:
public static SessionCrmUserInfo getSessionUserInfo(){
HttpServletRequest request = ((ServletRequestAttributes)
(RequestContextHolder.currentRequestAttributes())).getRequest();
String token = CookieUtil.getCookie(request,JWTUtils.Token_name_cookie);
try {
String json = (String) JWTUtils.getTokenInfo(token).get(JWTUtils.Token_name_key);
SessionCrmUserInfo sessionCrmUserInfo = JSON.parseObject(json,SessionCrmUserInfo.class);
return sessionCrmUserInfo;
}/*catch (SignatureVerificationException e){
e.printStackTrace();
System.out.println("无效前面");
}catch (TokenExpiredException e){
e.printStackTrace();
System.out.println("token过期");
}catch (AlgorithmMismatchException e){
e.printStackTrace();
System.out.println("token算法不一致");
}*/catch (Exception e){
e.printStackTrace();
System.out.println("token异常"+e.getMessage());
return null;
}
/*
以前的:
Object session = getSession().getAttribute(SessionAttributeName.USER_INFO);
if(session == null){
return null;
}else{
return (SessionCrmUserInfo)session;
}*/
}
Jwt核心处理类:
public class JWTUtils {
//private static final String SignStr = UUID.randomUUID().toString();//如果每次生成新的uuid,意味着每次系统发布,所有用户必须重新登录
private static final String SignStr = "43wfdgarehetyjutaaaabbbbbbccccc";
public static final String Token_name_cookie = "login_token";
public static final String Token_name_key = "userInfo";
public static final String Cookie_path = "/";
// private static final String algorithmType = SignatureAlgorithm.HS256.toString();
/**
* 生成token map是登陆个人信息
* @param map
* @return
*/
public static String getToken(Map<String,String> map) throws NoSuchAlgorithmException, InvalidKeyException {
// Calendar calendar = Calendar.getInstance();
// calendar.add(Calendar.DATE,3);//7天有效期
// JWTCreator.Builder builder = JWT.create();
// map.forEach((k,v) -> {
// builder.withClaim(k,v);
// });
// String token = builder.withExpiresAt(calendar.getTime())
// .sign(Algorithm.HMAC256(SignStr));
SecretKey key = Keys.hmacShaKeyFor(SignStr.getBytes(StandardCharsets.UTF_8)); //生成秘钥
String token = Jwts.builder()
.setExpiration(DateUtils.addDays(new Date(),7)) //有效时间7天
.setNotBefore(new Date()) //生效时间
.setIssuedAt(new Date()) //签发时间
.setClaims(map) //用户登录信息
.signWith(key,SignatureAlgorithm.HS256) //签名
.compressWith(CompressionCodecs.DEFLATE)
.compact();
System.out.println("生成token: " + token);
return token;
}
/**
* 验证token的合法性
* @param token
*/
public static void verify(String token){
SecretKey key = Keys.hmacShaKeyFor(SignStr.getBytes(StandardCharsets.UTF_8)); //生成秘钥
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}
/**
* 获取tokend里面的个人信息
* @param token
* @return
*/
public static Claims getTokenInfo(String token){
if(StringUtils.isEmpty(token)){
return null;
}else {
SecretKey key = Keys.hmacShaKeyFor(SignStr.getBytes(StandardCharsets.UTF_8)); //生成秘钥
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}
}
Cookie的工具类:
/**
* 使用cookie实现浏览历史记录
* @author shengfukang
* @createDate:2014-11-19
*/
public class CookieUtil {
private static Logger logger = LogManager.getLogger();
public static final String OVERTIME_COOKIE = "_overtime_cookie";
public static final long OVERTIME_COOKIE_VALUE = 5*60*1000;
public static final String userIdPassw = "sdfsdfssdfd2";
/**
* Cookie的名字
*/
public static final String COOKIE_HISTORYID = "cookies_history";
/**
* Cookie的存活时间
*/
public static final int COOKIE_LIFE_TIME = 14 * 24 * 60 * 60;
/**
* 最大的浏览历史记录条数
*/
public static final int HISTORY_COUNT = 5;
public boolean isOverTimeCookie(HttpServletRequest request, HttpServletResponse response) {
// TODO Auto-generated method stub
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date now = new Date();
String last = getCookie(request,CookieUtil.OVERTIME_COOKIE);
if (!StringUtil.isEmpty(last)) {
try {
Date lastTime = sdf.parse(last);
if((now.getTime()-lastTime.getTime())> CookieUtil.OVERTIME_COOKIE_VALUE){
//超时
//setCookie(response, null, SessionAttributeName.USER_ID, null,0);
// CookieUtils.setCookie(response, null, StockOptionConstants.OVERTIME_COOKIE, null,0);
//setCookie(response, ContextHolder.getRootPath(), ContextHolder.CFG_SID, null,0);
return true;
}else{
//未超时
//setCookie(response, null, CookieUtil.OVERTIME_COOKIE,sdf.format(now));
return false;
}
} catch (ParseException e) {
// TODO Auto-generated catch block
logger.error(e.getMessage());
return false;
}
} else {
//首次登陆,不做超时
//setCookie(response, null, CookieUtil.OVERTIME_COOKIE,sdf.format(now));
return false;
}
}
/**
* 获得指定cookie中的值
*
* @param request
* @param cookieName
* 要查找的cookie的名字
* @return 返回指定Cookie中的字符串值
*/
public static String getCookie(HttpServletRequest request, String cookieName) {
Cookie cookies[] = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie != null && cookie.getName().equals(cookieName)) {
return cookie.getValue();
}
}
}
return null;
}
/**
* 设置指定cookie中的值(1.得到原先cookie中的值;2.重新设置cookie的值;3.保存重置后的cookie)
*
* @param request
* @param response
* @param cookieName
* 要设置的cookie的名字
* @param goodsid
* 要添加到浏览历史中的 goodsID号
* @param count
* 总共可以显示的历史记录条数
*/
public static void setCookie(HttpServletRequest request,
HttpServletResponse response, String cookieName, String goodsid,
int count) {
// 得到指定的cookie
String ids = getCookie(request, cookieName);
//有值就不要再设置了
// 设置cookie中格的浏览记录
ids = setValue(ids, goodsid, count);
// 保存cookie
saveCookie(request, response, cookieName, ids);
}
// 测试方法
public static void main(String[] args) {
System.out.println(setValue(null, "1", 3));
System.out.println(setValue("1,2,3", "1", 3));
System.out.println(setValue("2,1,3", "1", 3));
System.out.println(setValue("2,1", "1", 3));
System.out.println(setValue("2,3,4", "1", 3));
System.out.println(setValue("2,4", "1", 3));
System.out.println(setValue("4,3,1", "1", 3));
}
/**
* 设置浏览历史字符串
*
* @param ids
* @param sbookid
* 最新浏览的id号
* @return 修改后的字符串
*/
private static String setValue(String ids, String bookid, int count) {
// 1、 没有任何记录--》直接添加 id
// 2、 1,2,3 4--》 4,1,2
// 3、 1,2,3 2 --》 2,1,3
// 4、1,2 3--》 3,1,2
// 如果不存在Cookie或者Cookie中没有值
StringBuffer sb = new StringBuffer();
if (ids == null) {
sb.append(bookid);
} else {
List<String> list = Arrays.asList(ids.split("\\,"));
LinkedList<String> idsList = new LinkedList<String>(list);
// 未浏览过
if (!idsList.contains(bookid)) {
if (idsList.size() < count) {
idsList.addFirst(bookid);
} else {
idsList.removeLast();
idsList.addFirst(bookid);
}
} else {
// 如果包含已浏览的
idsList.remove(bookid);
idsList.addFirst(bookid);
}
for (String id : idsList) {
sb.append(id).append(",");
}
if (sb != null && sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1);
}
}
return sb.toString();
}
/**
* 保存cookie的值
*
* @param request
* @param response
* @param cookieName
* 要保存的cookie的名字
* @param value
* 保存到cookie中的值
*/
public static void saveCookie(HttpServletRequest request,
HttpServletResponse response, String cookieName, String value) {
saveCookie(request, response, cookieName, value, COOKIE_LIFE_TIME);
}
/**
* 删除指定的cookie
*
* @param request
* @param response
* @param cookieName
* 要删除的cookie
*/
public static void deleteCookie(HttpServletRequest request,
HttpServletResponse response, String cookieName) {
saveCookie(request, response, cookieName, "", 0);
}
/**
* 保存cookie 并设置cookie存活的时间
*
* @param request
* @param response
* @param cookieName
* 要保存的cookie的名字
* @param value
* cookie中要存放的值
* @param time
* cookie存活时间
*/
public static void saveCookie(HttpServletRequest request,
HttpServletResponse response, String cookieName, String value,
int time) {
Cookie cookie = new Cookie(cookieName, value);
cookie.setMaxAge(time);// 设置Cookie的存活时间
//cookie.setPath(request.getServletContext().getContextPath());
cookie.setPath(JWTUtils.Cookie_path);
response.addCookie(cookie);// 保存cookie
}
/**
* 保存cookie,并可以设置cookie的保存路径
*
* @param path cookie保存路径
*
* @author LiaoTeng
* @date 2022-11-12
*/
public static void saveCookie(HttpServletResponse response, String cookieName, String value, String path) {
Cookie cookie = new Cookie(cookieName, value);
cookie.setPath(path);
cookie.setMaxAge(COOKIE_LIFE_TIME);// 设置Cookie的存活时间
response.addCookie(cookie);
}